From 3cd9e47fd0331bb3a5b645708a6cee65911381c7 Mon Sep 17 00:00:00 2001 From: yusing Date: Thu, 10 Apr 2025 06:08:37 +0800 Subject: [PATCH] refactor: make lock in error Builder optional --- internal/gperr/builder.go | 80 +++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/internal/gperr/builder.go b/internal/gperr/builder.go index 4eeee60..ac85b56 100644 --- a/internal/gperr/builder.go +++ b/internal/gperr/builder.go @@ -5,44 +5,65 @@ import ( "sync" ) +type noLock struct{} + +func (noLock) Lock() {} +func (noLock) Unlock() {} +func (noLock) RLock() {} +func (noLock) RUnlock() {} + +type rwLock interface { + sync.Locker + RLock() + RUnlock() +} + type Builder struct { about string errs []error - sync.Mutex + rwLock } -func NewBuilder(about string) *Builder { - return &Builder{about: about} +// NewBuilder creates a new Builder. +// +// If about is not provided, the Builder will not have a subject +// and will expand when adding to another builder. +func NewBuilder(about ...string) *Builder { + if len(about) == 0 { + return &Builder{rwLock: noLock{}} + } + return &Builder{about: about[0], rwLock: noLock{}} +} + +func NewBuilderWithConcurrency(about ...string) *Builder { + if len(about) == 0 { + return &Builder{rwLock: new(sync.RWMutex)} + } + return &Builder{about: about[0], rwLock: new(sync.RWMutex)} +} + +func (b *Builder) EnableConcurrency() { + b.rwLock = new(sync.RWMutex) } func (b *Builder) About() string { - if !b.HasError() { - return "" - } return b.about } -//go:inline func (b *Builder) HasError() bool { + // no need to lock, when this is called, the Builder is not used anymore return len(b.errs) > 0 } -func (b *Builder) error() Error { - if !b.HasError() { +func (b *Builder) Error() Error { + if len(b.errs) == 0 { return nil } return &nestedError{Err: New(b.about), Extras: b.errs} } -func (b *Builder) Error() Error { - if len(b.errs) == 1 { - return wrap(b.errs[0]) - } - return b.error() -} - func (b *Builder) String() string { - err := b.error() + err := b.Error() if err == nil { return "" } @@ -57,10 +78,12 @@ func (b *Builder) Add(err error) *Builder { return b } + wrapped := wrap(err) + b.Lock() defer b.Unlock() - switch err := wrap(err).(type) { + switch err := wrapped.(type) { case *baseError: b.errs = append(b.errs, err.Err) case *nestedError: @@ -105,26 +128,33 @@ func (b *Builder) AddFrom(other *Builder, flatten bool) *Builder { if flatten { b.errs = append(b.errs, other.errs...) } else { - b.errs = append(b.errs, other.error()) + b.errs = append(b.errs, other.Error()) } return b } func (b *Builder) AddRange(errs ...error) *Builder { + nonNilErrs := make([]error, 0, len(errs)) + for _, err := range errs { + if err != nil { + nonNilErrs = append(nonNilErrs, err) + } + } + b.Lock() defer b.Unlock() - for _, err := range errs { - if err != nil { - b.errs = append(b.errs, err) - } - } + b.errs = append(b.errs, nonNilErrs...) return b } func (b *Builder) ForEach(fn func(error)) { - for _, err := range b.errs { + b.RLock() + errs := b.errs + b.RUnlock() + + for _, err := range errs { fn(err) } }