From 1e3d033a0fcdba2680de71b4b2924751c85ddddf Mon Sep 17 00:00:00 2001 From: Spiegel Date: Sat, 9 May 2026 12:58:08 +0900 Subject: [PATCH] docs: clarify concurrency semantics and polish examples --- .github/copilot-instructions.md | 1 + README.md | 6 ++++++ errlist.go | 5 ++++- errs.go | 3 +++ example_test.go | 4 ++-- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b4b7f1f..a3c234c 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -36,5 +36,6 @@ ## API and Compatibility Notes - Do not introduce new usage of deprecated `Cause()`; prefer `errors.Is`/`errors.As` compatible flows and `Unwrap`/`Unwraps`. - Keep `errs.Join` behavior stable: ignore nil arguments and return nil when all arguments are nil. +- Keep thread-safety semantics clear: `errs.Errors` is container-safe, but contained error values (including `errs.Error`) are not guaranteed goroutine-safe. - Treat changes to exported symbols, function signatures, and observable error formatting behavior as potentially breaking. - Error string and JSON formatting are validated by tests; when output behavior changes, update README examples and test expectations in the same change. diff --git a/README.md b/README.md index 9340b46..404f6ec 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,12 @@ For multiple causes, it returns all causes as a slice. - If `WithCause` is given multiple times, the last cause is used - `errs.Join(...)` ignores `nil` arguments and returns `nil` if all arguments are `nil` +### Concurrency notes + +- `errs.Errors` is goroutine-safe for container operations such as `Add`, `ErrorOrNil`, and `Unwrap`. +- Errors stored in `errs.Errors` are not guaranteed to be goroutine-safe. +- `errs.Error` has mutable state (`Context` map), so avoid concurrent mutation while formatting or encoding the same instance. + ### Create new error instance with cause ```go diff --git a/errlist.go b/errlist.go index d25b9a5..af11000 100644 --- a/errlist.go +++ b/errlist.go @@ -11,6 +11,9 @@ import ( ) // Errors is multiple error instance. +// +// Errors protects concurrent access to the container itself, but each contained +// error may still be non-thread-safe. type Errors struct { mu sync.RWMutex errs []error @@ -167,7 +170,7 @@ func (es *Errors) Unwrap() []error { if len(es.errs) == 0 { return nil } - cpy := make([]error, len(es.errs), cap(es.errs)) + cpy := make([]error, len(es.errs)) copy(cpy, es.errs) return cpy } diff --git a/errs.go b/errs.go index 0600e2f..788a11f 100644 --- a/errs.go +++ b/errs.go @@ -18,6 +18,9 @@ const ( // Error type is a implementation of error interface. // This type is for wrapping cause error instance. +// +// Error is not goroutine-safe. Its Context field is a mutable map and can be +// modified by SetContext or direct field access. type Error struct { wrapFlag bool Err error diff --git a/example_test.go b/example_test.go index fefbd74..45472e5 100644 --- a/example_test.go +++ b/example_test.go @@ -65,9 +65,9 @@ func ExampleErrors() { }() } wg.Wait() - fmt.Println("error ount =", len(errlist.Unwrap())) + fmt.Println("error count =", len(errlist.Unwrap())) // Output: - // error ount = 100000 + // error count = 100000 } /* Copyright 2019-2023 Spiegel