Added plan of tests
This commit is contained in:
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user