new exporter version

This commit is contained in:
a.pivkin
2025-12-20 17:00:05 +03:00
parent bf8f562768
commit af4a1c46e1
171 changed files with 0 additions and 0 deletions
@@ -0,0 +1,3 @@
module error-handling
go 1.20
@@ -0,0 +1,91 @@
package main
import (
"errors"
"io/fs"
"log"
)
func main() {
log.SetFlags(0) // no time stamp
// Read a single file
log.SetPrefix("Reading a single file: ")
_, err := ReadFile("no/file")
if err != nil {
log.Println("err = ", err)
}
// Unwrap the error returned by os.Open()
log.Println("errors.Unwrap(err) = ", errors.Unwrap(err))
// Confirm that the error is, or wraps, an fs.ErrNotExist error
log.Println("err is fs.ErrNotExist:", errors.Is(err, fs.ErrNotExist))
// Confirm that the error is, or wraps, an fs.PathError.
// errors.As() assigns the unwrapped PathError to target.
// This allows reading PathError's Path field.
target := &fs.PathError{}
if errors.As(err, &target) {
log.Printf("err as PathError: path is '%s'\n", target.Path)
log.Printf("err as PathError: op is '%s'\n", target.Op)
}
// Read files concurrently - handling context errors
log.SetPrefix("Reading files concurrently: ")
_, err = ReadFilesConcurrently([]string{"no/file/a", "no/file/b", "no/file/c"})
log.Println("err = ", err)
// Read multiple files
log.SetPrefix("Reading multiple files: ")
// Passing an empty slice triggers a plain error
_, err = ReadFiles([]string{})
log.Println("err = ", err)
// Passing multiple paths of non-existing files triggers a joined error
_, err = ReadFiles([]string{"no/file/a", "no/file/b", "no/file/c"})
log.Println("joined errors = ", err)
// Unwrap the errors inside the joined error
// A joined error does not have the method "func Unwrap() error"
// because it does not wrap a single error but rather a slice of errors.
// Therefore, errors.Unwrap() cannot unwrap the errors and returns nil.
log.Println("errors.Unwrap(err) = ", errors.Unwrap(err))
// To unwrap a joined error, you can type-assert that err has
// an Unwrap() method that returns a slice of errors.
e, ok := err.(interface{ Unwrap() []error })
if ok {
log.Println("e.Unwrap() = ", e.Unwrap())
}
// Network errors
log.SetPrefix("Network errors: ")
err = connectToTCPServer()
log.Println("err = ", err)
// Recover from a panic
log.SetPrefix("Recovering from a panic: ")
// This example is at the end of main, because the panic
// causes main to exit. Only the deferred function is
// called before exiting.
defer func() {
// Is this func invoked from a panic?
if r := recover(); r != nil {
// Yes: recover from the panic
log.Printf("isValidPath panicked: error is '%v'\n", r)
// ...
}
}()
// isValidPath panics because of an invalid regexp.
if isValidPath("/path/to/file") {
_, _ = ReadFile("/path/to/file")
}
}
@@ -0,0 +1,36 @@
package main
import (
"errors"
"fmt"
"log"
"net"
)
// Connect to a TCP server and check the error. use errors.As() to unwrap the net.OpError, and test if the error is transient.
func connectToTCPServer() error {
var err error
var conn net.Conn
for retry := 3; retry > 0; retry-- {
conn, err = net.Dial("tcp", "127.0.0.1:12345")
if err != nil {
// Check if err is a net.OpError
opErr := &net.OpError{}
if errors.As(err, &opErr) {
log.Println("err is net.OpError:", opErr.Error())
// test if the error is temporary
if opErr.Temporary() {
log.Printf("Retrying...\n")
continue
}
retry = 0
}
}
}
if err != nil {
return fmt.Errorf("connect failed: %w", err)
}
defer conn.Close()
// send or receive data
return nil
}
@@ -0,0 +1,30 @@
package main
import (
"errors"
"fmt"
"io"
"os"
)
func ReadFile(path string) ([]byte, error) {
if path == "" {
// Create an error with errors.New()
return nil, errors.New("path is empty")
}
f, err := os.Open(path)
if err != nil {
// Wrap the error.
// If the format string uses %w to format the error,
// fmt.Errorf() returns an error that has the
// method "func Unwrap() error" implemented.
return nil, fmt.Errorf("open failed: %w", err)
}
defer f.Close()
buf, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("read failed: %w", err)
}
return buf, nil
}
@@ -0,0 +1,33 @@
package main
import (
"errors"
"fmt"
)
func ReadFiles(paths []string) ([][]byte, error) {
var errs error
var contents [][]byte
if len(paths) == 0 {
// Create a new error with fmt.Errorf() (but without using %w):
return nil, fmt.Errorf("no paths provided: paths slice is %v", paths)
}
for _, path := range paths {
content, err := ReadFile(path)
if err != nil {
// Join all errors that occur into errs.
// The returned error type implements method "func Unwrap() []error".
// (Note that the return type is a slice.)
errs = errors.Join(errs, fmt.Errorf("reading %s failed: %w", path, err))
continue
}
contents = append(contents, content)
}
// Some files may have been read, some may have failed to be read.
// Therefore, ReadFiles returns both return values, regardless
// of whether there have been errors.
return contents, errs
}
@@ -0,0 +1,62 @@
package main
import (
"context"
"fmt"
"log"
"math/rand"
"sync"
"time"
)
// Goal: Read multiple files concurrently. All files must be read successfully.
// If one goroutine fails, cancel all the other goroutines.
//
// The cancelled goroutines can inspect the error from canceling the context with ctx.Err()..
func ReadFilesConcurrently(paths []string) ([][]byte, error) {
var contents [][]byte
ctx, cancel := context.WithCancelCause(context.Background())
defer cancel(nil)
resCh := make(chan []byte)
wg := sync.WaitGroup{}
for _, path := range paths {
wg.Add(1)
go func(ctx context.Context, cancel context.CancelCauseFunc, p string, resCh chan<- []byte, wg *sync.WaitGroup) {
time.Sleep(time.Duration(rand.Intn(10)) * time.Microsecond) // simulate workload
select {
case <-ctx.Done():
log.Printf("ReadFilesConcurrently (goroutine): Context canceled for path %s: %v", p, ctx.Err())
wg.Done()
return
default:
// If an error occurs here, cancel all the other goroutines
content, err := ReadFile(p)
if err != nil {
cancel(fmt.Errorf("error reading %s: %w", p, err))
log.Printf("ReadFilesConcurrently (goroutine): Context canceled for path %s: %v", p, err)
wg.Done()
return
}
resCh <- content
}
}(ctx, cancel, path, resCh, &wg)
}
go func() {
wg.Wait()
close(resCh)
}()
for c := range resCh {
contents = append(contents, c)
}
if e := ctx.Err(); e != nil {
return nil, fmt.Errorf("ReadFilesConcurrently: %w", e)
}
return contents, nil
}
@@ -0,0 +1,8 @@
package main
import "regexp"
func isValidPath(p string) bool {
pathRe := regexp.MustCompile(`(invalid regular expression`)
return pathRe.MatchString(p)
}