diff --git a/go.mod b/go.mod index fc9807cb..177feb23 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect - github.com/olekukonko/errors v1.1.0 // indirect + github.com/olekukonko/errors v1.2.0 // indirect github.com/olekukonko/ll v0.1.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect diff --git a/go.sum b/go.sum index 0e1444e1..8907f2d1 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= -github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= -github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= +github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= github.com/olekukonko/ll v0.1.3 h1:sV2jrhQGq5B3W0nENUISCR6azIPf7UBUpVq0x/y70Fg= github.com/olekukonko/ll v0.1.3/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew= github.com/olekukonko/tablewriter v1.1.2 h1:L2kI1Y5tZBct/O/TyZK1zIE9GlBj/TVs+AY5tZDCDSc= diff --git a/vendor/github.com/olekukonko/errors/chain.go b/vendor/github.com/olekukonko/errors/chain.go index 5dc73a58..2bb390ef 100644 --- a/vendor/github.com/olekukonko/errors/chain.go +++ b/vendor/github.com/olekukonko/errors/chain.go @@ -527,46 +527,34 @@ func (c *Chain) wrapCallable(fn interface{}, args ...interface{}) (func() error, } // executeStep runs a single step, applying retries if configured. +// This version is synchronous and avoids the bugs caused by the previous goroutine-based implementation. func (c *Chain) executeStep(ctx context.Context, step *chainStep) error { + // First, check if the context has already been canceled before starting the step. + // This allows the chain to fail fast. select { case <-ctx.Done(): return ctx.Err() default: + // Context is still active, proceed. } + + // If the step has retry logic configured... if step.config.retry != nil { - retry := step.config.retry.Transform(WithContext(ctx)) - // Wrap step execution to respect context - wrappedFn := func() error { - type result struct { - err error - } - done := make(chan result, 1) - go func() { - done <- result{err: step.execute()} - }() - select { - case res := <-done: - return res.err - case <-ctx.Done(): - return ctx.Err() - } - } - return retry.Execute(wrappedFn) - } - // Non-retry case also respects context - type result struct { - err error - } - done := make(chan result, 1) - go func() { - done <- result{err: step.execute()} - }() - select { - case res := <-done: - return res.err - case <-ctx.Done(): - return ctx.Err() + // Create a new retry instance that is aware of the chain's context. + // The retry executor will be responsible for checking ctx.Done() between attempts. + retryExecutor := step.config.retry.Transform(WithContext(ctx)) + + // Execute the step's function directly. The retry mechanism will manage the loop, + // delays, and context cancellation checks. We pass step.execute without any + // extra goroutine wrappers. + return retryExecutor.Execute(step.execute) } + + // For a simple, non-retrying step, execute the function directly and synchronously + // in the current goroutine. This is the simplest, fastest, and most correct approach. + // It ensures that database connections are used and returned to the pool sequentially, + // preventing the deadlock issue. + return step.execute() } // enhanceError wraps an error with additional context from the step. diff --git a/vendor/github.com/olekukonko/errors/errors.go b/vendor/github.com/olekukonko/errors/errors.go index 4f6509da..6bf3bca6 100644 --- a/vendor/github.com/olekukonko/errors/errors.go +++ b/vendor/github.com/olekukonko/errors/errors.go @@ -95,6 +95,10 @@ type contextItem struct { // context, cause, and metadata like code and category. It is thread-safe and // supports pooling for performance. type Error struct { + // Fields used in atomic operations. Place them at the beginning of the + // struct to ensure proper alignment across all architectures. + count uint64 // Occurrence count for tracking frequency. + // Primary fields (frequently accessed). msg string // The error message displayed by Error(). name string // The error name or type (e.g., "AuthError"). @@ -103,7 +107,6 @@ type Error struct { // Secondary metadata. template string // Fallback message template if msg is empty. category string // Error category (e.g., "network"). - count uint64 // Occurrence count for tracking frequency. code int32 // HTTP-like status code (e.g., 400, 500). smallCount int32 // Number of items in smallContext. @@ -172,7 +175,7 @@ func newError() *Error { // // err := errors.Empty().With("key", "value").WithCode(400) func Empty() *Error { - return emptyError + return newError() } // Named creates an error with the specified name and captures a stack trace. @@ -213,10 +216,18 @@ func New(text string) *Error { // err := errors.Newf("query failed: %w", cause) // // err.Error() will match fmt.Errorf("query failed: %w", cause).Error() // // errors.Unwrap(err) == cause -func Newf(format string, args ...interface{}) *Error { +func Newf(f any, args ...interface{}) *Error { + var format string + switch v := f.(type) { + case string: + format = v + case fmt.Stringer: + format = v.String() + default: + panic("Newf: format must be a string or fmt.Stringer") + } err := newError() - // --- Start: Parsing and Validation (mostly unchanged) --- var wCount int var wArgPos = -1 var wArg error @@ -356,11 +367,10 @@ func Newf(format string, args ...interface{}) *Error { err.formatWrapped = false return err } - // --- End: Parsing and Validation --- - // --- Start: Processing Valid Format String --- + // Start: Processing Valid Format String if wCount == 1 && wArg != nil { - // --- Handle %w: Simulate for Sprintf and pre-format --- + // Handle %w: Simulate for Sprintf and pre-format err.cause = wArg // Set the cause for unwrapping err.formatWrapped = true // Signal that msg is the final formatted string @@ -397,10 +407,10 @@ func Newf(format string, args ...interface{}) *Error { // Store the final, fully formatted string, matching fmt.Errorf output err.msg = result } - // --- End %w Simulation --- + // End %w Simulation } else { - // --- No %w or wArg is nil: Format directly (original logic) --- + // No %w or wArg is nil: Format directly (original logic) result, fmtErr := FmtErrorCheck(format, args...) if fmtErr != nil { err.msg = fmt.Sprintf("errors.Newf: formatting error for format %q: %v", format, fmtErr) @@ -411,7 +421,7 @@ func Newf(format string, args ...interface{}) *Error { err.formatWrapped = false // Ensure false if no %w was involved } } - // --- End: Processing Valid Format String --- + // End: Processing Valid Format String return err } @@ -448,38 +458,6 @@ func FmtErrorCheck(format string, args ...interface{}) (result string, err error return result, nil } -// countFmtArgs counts format specifiers that consume arguments in a format string. -// Ignores %% and non-consuming verbs like %n. -// Internal use by Newf for argument validation. -func countFmtArgs(format string) int { - count := 0 - runes := []rune(format) - i := 0 - for i < len(runes) { - if runes[i] == '%' { - if i+1 < len(runes) && runes[i+1] == '%' { - i += 2 // Skip %% - continue - } - i++ // Move past % - for i < len(runes) && (runes[i] == '+' || runes[i] == '-' || runes[i] == '#' || - runes[i] == ' ' || runes[i] == '0' || - (runes[i] >= '1' && runes[i] <= '9') || runes[i] == '.') { - i++ - } - if i < len(runes) { - if strings.ContainsRune("vTtbcdoqxXUeEfFgGsp", runes[i]) { - count++ - } - i++ // Move past verb - } - } else { - i++ - } - } - return count -} - // Std creates a standard error using errors.New for compatibility. // Does not capture stack traces or add context. // Example: @@ -700,8 +678,8 @@ func (e *Error) Error() string { return e.msg // Return the pre-formatted fmt.Errorf-compatible string } - // --- Original logic for errors not created via Newf("%w", ...) --- - // --- or errors created via New/Named and then Wrap() called. --- + // Original logic for errors not created via Newf("%w", ...) + // or errors created via New/Named and then Wrap() called. var buf strings.Builder // Append primary message part (msg, template, or name) diff --git a/vendor/github.com/olekukonko/errors/helper.go b/vendor/github.com/olekukonko/errors/helper.go index 06c2adc5..2f4af34e 100644 --- a/vendor/github.com/olekukonko/errors/helper.go +++ b/vendor/github.com/olekukonko/errors/helper.go @@ -430,3 +430,8 @@ func Wrapf(err error, format string, args ...interface{}) *Error { e.cause = err return e } + +// Err creates a new Error with the given message and wraps the provided error as its cause. +func Err(msg string, err error) *Error { + return New(msg).Wrap(err) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index d2c8241b..17b407aa 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -67,7 +67,7 @@ github.com/mitchellh/go-homedir # github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 ## explicit; go 1.21 github.com/olekukonko/cat -# github.com/olekukonko/errors v1.1.0 +# github.com/olekukonko/errors v1.2.0 ## explicit; go 1.21 github.com/olekukonko/errors # github.com/olekukonko/ll v0.1.3