From ae2ee214101f0d02abd9ac32f36e2e8a3dca2271 Mon Sep 17 00:00:00 2001 From: Harald Rudell Date: Fri, 3 Nov 2023 11:08:27 -0700 Subject: [PATCH] v0.4.119 refactor AtomicMax closers errorglue.ErrorList perrors.ErrorList --- atomic-max.go | 65 +++++++++++++++++---------- close-channel.go | 74 ++++++++++++++++++++++++++++++ close-channel_test.go | 55 +++++++++++++++++++++++ closer.go | 73 +++++------------------------- closer_test.go | 99 ++++++++++++++++------------------------- errorglue/error-list.go | 65 ++++++++++++++++++--------- go.mod | 2 +- go.sum | 4 +- iters/if-iterator.go | 71 +++++++++-------------------- mains/go.mod | 2 +- pterm/go.mod | 2 +- slow-detector-core.go | 32 ++++++------- slow-detector.go | 10 +---- sqliter/go.mod | 4 +- sqliter/go.sum | 4 +- watchfs/go.mod | 2 +- yamler/go.mod | 2 +- 17 files changed, 316 insertions(+), 250 deletions(-) create mode 100644 close-channel.go create mode 100644 close-channel_test.go diff --git a/atomic-max.go b/atomic-max.go index 48e44741..a26052f6 100644 --- a/atomic-max.go +++ b/atomic-max.go @@ -13,22 +13,25 @@ import ( ) // AtomicMax is a thread-safe max container -type AtomicMax[T constraints.Integer] struct{ value, value0 atomic.Uint64 } +type AtomicMax[T constraints.Integer] struct { + // threshold is an optional minimum value for a new max + // - valid if greater than 0 + threshold uint64 + // whether [AtomicMax.Value] has been invoked + didValue atomic.Bool + // value is current max or 0 if no value is present + value atomic.Uint64 +} // NewAtomicMax returns a thread-safe max container // - T underlying type must be int -// - negative values are not allowed -// - to set initial value, use Init -func NewAtomicMax[T constraints.Integer]() (atomicMax *AtomicMax[T]) { return &AtomicMax[T]{} } - -// Init performs actions that cannot happen prior to copying AtomicMax -// - supports functional chaining -// - Thread-safe -func (m *AtomicMax[T]) Init(value T) (atomicMax *AtomicMax[T]) { - atomicMax = m - m.value.Store(uint64(value)) - m.value0.Store(uint64(value)) // set initial threshold - return +// - negative values are not allowed and cause panic +func NewAtomicMax[T constraints.Integer](threshold T) (atomicMax *AtomicMax[T]) { + m := AtomicMax[T]{} + if threshold != 0 { + m.threshold = m.tToUint64(threshold) + } + return &m } // Value updates the container possibly with a new Max value @@ -36,14 +39,21 @@ func (m *AtomicMax[T]) Init(value T) (atomicMax *AtomicMax[T]) { // - Thread-safe func (m *AtomicMax[T]) Value(value T) (isNewMax bool) { - // check if value is a new max - var valueU64, err = ints.Unsigned[uint64](value, "") - if err != nil { - panic(err) // value out of range, ie. negative + // check value against threshold + var valueU64 = m.tToUint64(value) + if valueU64 < m.threshold { + return // below threshold return: isNewMax false + } + + // 0 as max case + if isNewMax = m.didValue.CompareAndSwap(false, true) && value == 0; isNewMax { + return // first invocation with 0: isNewMax true } + + // check against present value var current = m.value.Load() if isNewMax = valueU64 > current; !isNewMax { - return // not a new max return + return // not a new max return: isNewMax false } // store the new max @@ -51,10 +61,10 @@ func (m *AtomicMax[T]) Value(value T) (isNewMax bool) { // try to write value to *max if m.value.CompareAndSwap(current, valueU64) { - return // new max written return + return // new max written return: isNewMax true } if current = m.value.Load(); current >= valueU64 { - return // no longer a need to write return + return // no longer a need to write return: isNewMax true } } } @@ -62,12 +72,21 @@ func (m *AtomicMax[T]) Value(value T) (isNewMax bool) { // Max returns current max and a flag whether a value is present // - Thread-safe func (m *AtomicMax[T]) Max() (value T, hasValue bool) { - var u64 = m.value.Load() - value = T(u64) - hasValue = u64 != m.value0.Load() + value = T(m.value.Load()) + hasValue = m.didValue.Load() return } // Max1 returns current maximum whether default or set by Value // - Thread-safe func (m *AtomicMax[T]) Max1() (value T) { return T(m.value.Load()) } + +// tToUint64 converts T value to uint64 +// - panic if T value is negative +func (m *AtomicMax[T]) tToUint64(value T) (valueU64 uint64) { + var err error + if valueU64, err = ints.Unsigned[uint64](value, ""); err != nil { + panic(err) // value out of range, ie. negative + } + return +} diff --git a/close-channel.go b/close-channel.go new file mode 100644 index 00000000..3deff45c --- /dev/null +++ b/close-channel.go @@ -0,0 +1,74 @@ +/* +© 2022–present Harald Rudell (https://haraldrudell.github.io/haraldrudell/) +ISC License +*/ + +package parl + +import ( + "github.com/haraldrudell/parl/perrors" + "github.com/haraldrudell/parl/pruntime" +) + +const ( + CloseChannelDrain = true +) + +// CloseChannel closes a channel +// - CloseChannel is thread-safe, deferrable and panic-free, +// handles closed-channel panic, nil-channel case and +// has channel drain feature +// - isNilChannel returns true if ch is nil. +// closing a nil channel would cause panic. +// - isCloseOfClosedChannel is true if close paniced due to +// the channel already closed. +// A channel transferring data cannot be inspected for being +// closed +// - if errp is non-nil, panic values updates it using errors.AppendError. +// - if doDrain is [parl.CloseChannelDrain], the channel is drained first. +// Note: closing a channel while a thread is blocked in channel send is +// a data race. +// If a thread is continuously sending items and doDrain is true, +// CloseChannel will block indefinitely. +// - n returns the number of drained items. +func CloseChannel[T any](ch chan T, errp *error, drainChannel ...bool) ( + isNilChannel, isCloseOfClosedChannel bool, n int, err error, +) { + + // nil channel case + if isNilChannel = ch == nil; isNilChannel { + return // closing of nil channel return + } + + // channel drain feature + if len(drainChannel) > 0 && drainChannel[0] { + for { + select { + // read non-blocking from the channel + // - ok true: received item, channel is not closed + // - ok false: channel is closed + case _, ok := <-ch: + if ok { + // the channel is not closed + n++ + continue // read next item + } + default: // channel is open but has no items + } + break // closed or no items + } + } + + // close channel + if Closer(ch, &err); err == nil { + return // close successful return + } + + // handle close error + isCloseOfClosedChannel = pruntime.IsCloseOfClosedChannel(err) + if errp != nil { + *errp = perrors.AppendError(*errp, err) + } + + return +} diff --git a/close-channel_test.go b/close-channel_test.go new file mode 100644 index 00000000..a3c35ef2 --- /dev/null +++ b/close-channel_test.go @@ -0,0 +1,55 @@ +/* +© 2022–present Harald Rudell (https://haraldrudell.github.io/haraldrudell/) +ISC License +*/ + +package parl + +import "testing" + +func TestCloseChannel(t *testing.T) { + var value = 3 + var doDrain = true + + var ch chan int + var err, errp error + var n int + var isNilChannel, isCloseOfClosedChannel bool + + // close of nil channel should return isNilChannel true + ch = nil + isNilChannel, isCloseOfClosedChannel, n, err = CloseChannel(ch, &errp) + if !isNilChannel { + t.Error("isNilChannel false") + } + _ = err + _ = n + _ = isCloseOfClosedChannel + + // n should return number of items when draining + ch = make(chan int, 1) + ch <- value + isNilChannel, isCloseOfClosedChannel, n, err = CloseChannel(ch, &errp, doDrain) + if n != 1 { + t.Errorf("n bad %d exp %d", n, 1) + } + _ = isNilChannel + _ = err + _ = isCloseOfClosedChannel + + // close of closed channel should set isCloseOfClosedChannel, err, errp + ch = make(chan int) + close(ch) + isNilChannel, isCloseOfClosedChannel, n, err = CloseChannel(ch, &errp) + if !isCloseOfClosedChannel { + t.Error("isCloseOfClosedChannel false") + } + if err == nil { + t.Error("isCloseOfClosedChannel err nil") + } + if errp == nil { + t.Error("isCloseOfClosedChannel errp nil") + } + _ = isNilChannel + _ = n +} diff --git a/closer.go b/closer.go index 8f1d7f30..e09594ed 100644 --- a/closer.go +++ b/closer.go @@ -9,16 +9,11 @@ import ( "io" "github.com/haraldrudell/parl/perrors" - "github.com/haraldrudell/parl/pruntime" -) - -const ( - CloseChannelDrain = true ) // Closer is a deferrable function that closes a channel. -// Closer handles panics. -// if errp is non-nil, panic values updates it using errors.AppendError. +// - if errp is non-nil, panic values updates it using errors.AppendError. +// - Closer is thread-safe, panic-free and deferrable func Closer[T any](ch chan T, errp *error) { defer PanicToErr(errp) @@ -26,69 +21,23 @@ func Closer[T any](ch chan T, errp *error) { } // CloserSend is a deferrable function that closes a send-channel. -// CloserSend handles panics. -// if errp is non-nil, panic values updates it using errors.AppendError. +// - if errp is non-nil, panic values updates it using errors.AppendError. +// - CloserSend is thread-safe, panic-free and deferrable func CloserSend[T any](ch chan<- T, errp *error) { defer PanicToErr(errp) close(ch) } -// Close is a deferrable function that closes an io.Closer object. -// Close handles panics. -// if errp is non-nil, panic values updates it using errors.AppendError. +// Close closes an io.Closer object. +// - if errp is non-nil, panic values updates it using errors.AppendError. +// - Close is thread-safe, panic-free and deferrable +// - type Closer interface { Close() error } func Close(closable io.Closer, errp *error) { - defer PanicToErr(errp) - - if e := closable.Close(); e != nil { - *errp = perrors.AppendError(*errp, e) - } -} + var err error + defer RecoverErr(func() DA { return A() }, errp) -// CloseChannel closes a channel recovering panics -// - deferrable -// - if errp is non-nil, panic values updates it using errors.AppendError. -// - if doDrain is CloseChannelDrain or true, the channel is drained first. -// Note: closing a channel while a thread is blocked in channel send is -// a data race. -// If a thread is continuously sending items and doDrain is true, -// CloseChannel will block indefinitely. -// - n returns the number of drained items. -// - isNilChannel returns true if ch is nil. -// No close will be attempted for a nil channel, it would panic. -func CloseChannel[T any](ch chan T, errp *error, drainChannel ...bool) ( - isNilChannel, isCloseOfClosedChannel bool, n int, err error, -) { - if isNilChannel = ch == nil; isNilChannel { - return // closing of nil channel return - } - var doDrain bool - if len(drainChannel) > 0 { - doDrain = drainChannel[0] - } - if doDrain { - var hasItems = true - for hasItems { - select { - // read non-blocking from the channel - case _, ok := <-ch: - if ok { - // the channel is not closed - n++ - continue // read next item - } - default: - } - hasItems = false - } - } - Closer(ch, &err) - if err == nil { - return // close successful - } - isCloseOfClosedChannel = pruntime.IsCloseOfClosedChannel(err) - if errp != nil { + if err = closable.Close(); perrors.IsPF(&err, "%w", err) { *errp = perrors.AppendError(*errp, err) } - return } diff --git a/closer_test.go b/closer_test.go index 946eec13..58194374 100644 --- a/closer_test.go +++ b/closer_test.go @@ -7,6 +7,7 @@ package parl import ( "errors" + "strconv" "strings" "testing" @@ -69,82 +70,60 @@ func TestCloserSend(t *testing.T) { } } -type testClosable struct { - err error -} - -func (tc *testClosable) Close() (err error) { return tc.err } - func TestClose(t *testing.T) { - message := "nil pointer" - err1 := "x" - err2 := "y" + // “nil pointer” + // - part of panic message: + // - “runtime error: invalid memory address or nil pointer dereference” + var message = "nil pointer" + // “x” + var messageX = "x" + // “y” + var messageY = "y" var err error - Close(&testClosable{}, &err) + // a successful Close should not return error + Close(newTestClosable(nil), &err) if err != nil { t.Errorf("Close err: %v", err) } + // Close of nil io.Closable should return error, not panic err = nil Close(nil, &err) + + // err: panic detected in parl.Close: + // “runtime error: invalid memory address or nil pointer dereference” + // at parl.Close()-closer.go:40 + //t.Logf("err: %s", perrors.Short(err)) + //t.Fail() + if err == nil || !strings.Contains(err.Error(), message) { t.Errorf("Close err: %v exp %q", err, message) } - err = errors.New(err1) - Close(&testClosable{err: errors.New(err2)}, &err) - - errs := perrors.ErrorList(err) - if len(errs) != 2 || errs[0].Error() != err1 || errs[1].Error() != err2 { - t.Errorf("erss bad: %v", errs) + // a failed Close should append to err + // - err should become X with Y appended + err = errors.New(messageX) + Close(newTestClosable(errors.New(messageY)), &err) + // errs is a list of associated errors, oldest first + // - first X, then Y + var errs = perrors.ErrorList(err) + if len(errs) != 2 || errs[0].Error() != messageX || !strings.HasSuffix(errs[1].Error(), messageY) { + var quoteList = make([]string, len(errs)) + for i, err := range errs { + quoteList[i] = strconv.Quote(err.Error()) + } + // erss bad: ["x" "parl.Close y"] + t.Errorf("erss bad: %v", quoteList) } } -func TestCloseChannel(t *testing.T) { - var value = 3 - var doDrain = true +// testClosable is an [io.Closable] with configurable fail error +type testClosable struct{ err error } - var ch chan int - var err, errp error - var n int - var isNilChannel, isCloseOfClosedChannel bool +// newTestClosable returns a closable whose Close always fails with err +func newTestClosable(err error) (closable *testClosable) { return &testClosable{err: err} } - // close of nil channel should return isNilChannel true - ch = nil - isNilChannel, isCloseOfClosedChannel, n, err = CloseChannel(ch, &errp) - if !isNilChannel { - t.Error("isNilChannel false") - } - _ = err - _ = n - _ = isCloseOfClosedChannel - - // n should return number of items when draining - ch = make(chan int, 1) - ch <- value - isNilChannel, isCloseOfClosedChannel, n, err = CloseChannel(ch, &errp, doDrain) - if n != 1 { - t.Errorf("n bad %d exp %d", n, 1) - } - _ = isNilChannel - _ = err - _ = isCloseOfClosedChannel - - // close of closed channel should set isCloseOfClosedChannel, err, errp - ch = make(chan int) - close(ch) - isNilChannel, isCloseOfClosedChannel, n, err = CloseChannel(ch, &errp) - if !isCloseOfClosedChannel { - t.Error("isCloseOfClosedChannel false") - } - if err == nil { - t.Error("isCloseOfClosedChannel err nil") - } - if errp == nil { - t.Error("isCloseOfClosedChannel errp nil") - } - _ = isNilChannel - _ = n -} +// Close returns a configured error +func (tc *testClosable) Close() (err error) { return tc.err } diff --git a/errorglue/error-list.go b/errorglue/error-list.go index 8c1fd81d..ffb91a2a 100644 --- a/errorglue/error-list.go +++ b/errorglue/error-list.go @@ -5,31 +5,56 @@ ISC License package errorglue -import "errors" - -// ErrorList returns all error instances from a possible error chain. -// — If err is nil an empty slice is returned. -// — If err does not have associated errors, a slice of err, length 1, is returned. -// — otherwise, the first error of the returned slice is err followed by -// -// other errors oldest first. -// - Cyclic error values are dropped +import ( + "errors" + + "golang.org/x/exp/slices" +) + +// ErrorList returns the list of associated errors enclosed in all error chains of err +// - the returned slice is a list of error chains beginning with err, +// then any associated errors +// - If err is nil, a nil slice is returned +// - order is: +// - — associated errors from err’s error chain, oldest first +// - — then associated errors from other error chain, oldest associated error first, +// and last found error chain first +// - — +// - associated errors are independent error chains that allows a single error +// to enclose addditional errors not part of its error chain +// - cyclic error values are filtered out so that no error instance is returned more than once func ErrorList(err error) (errs []error) { if err == nil { - return + return // nil error return: nil slice } - err0 := err - errMap := map[error]bool{err: true} - for err != nil { - if e, ok := err.(RelatedError); ok { - if e2 := e.AssociatedError(); e2 != nil { - if _, ok := errMap[e2]; !ok { - errs = append([]error{e2}, errs...) - errMap[e2] = true + // errMap ensures that no error instance occurs more than once + var errMap = map[error]bool{err: true} + + // process all error chains in err + for errorChains := []error{err}; len(errorChains) > 0; errorChains = errorChains[1:] { + // traverse error chain + for errorChain := errorChains[0]; errorChain != nil; errorChain = errors.Unwrap(errorChain) { + + // find any associated error not found before + if relatedError, ok := errorChain.(RelatedError); ok { + if associatedError := relatedError.AssociatedError(); associatedError != nil { + if _, ok := errMap[associatedError]; !ok { + + // store associated error, newest first + errs = append(errs, associatedError) + // store the error chain for scanning, first found first + errorChains = append(errorChains, associatedError) + // store associateed error to ensure uniqueness + errMap[associatedError] = true + } } } } - err = errors.Unwrap(err) } - return append([]error{err0}, errs...) + + // errs begins with err, then associated errors, oldest first + slices.Reverse(errs) + errs = append([]error{err}, errs...) + + return } diff --git a/go.mod b/go.mod index 80de61f8..2a32e147 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.21.3 require ( github.com/google/btree v1.1.2 - github.com/haraldrudell/parl/yamler v0.4.117 + github.com/haraldrudell/parl/yamler v0.4.118 golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/sys v0.13.0 golang.org/x/text v0.13.0 diff --git a/go.sum b/go.sum index b6c2844d..913c233d 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/haraldrudell/parl/yamler v0.4.117 h1:GCjl9mEvTYaeymWDU+ZGjP8illDcFQJBEQsa1v19LnA= -github.com/haraldrudell/parl/yamler v0.4.117/go.mod h1:VeafJ+gpRUHRPdE4wa3rODro1XKCjL+6gVMo5xY8FVY= +github.com/haraldrudell/parl/yamler v0.4.118 h1:D6aa/C91iMmS7iEl9T96a7JmUlLCAUqxIblSmG+5e5I= +github.com/haraldrudell/parl/yamler v0.4.118/go.mod h1:HZC/WIxU4F4MrXw7gJ2desIPoiwgXSXgbyMnCog9LyE= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= diff --git a/iters/if-iterator.go b/iters/if-iterator.go index 0a47113b..49f13766 100644 --- a/iters/if-iterator.go +++ b/iters/if-iterator.go @@ -7,80 +7,51 @@ package iters // Iterator allows traversal of values. // -// Iterators in the iters package are thread-safe but generally, -// thread-safety depends on the iterator implementation used. +// The iterator interface is optimized for use in the Go “for” clause. +// Iterators in the iters package are thread-safe but +// thread-safety depends on iterator implementation. // -// the ForIterator interface is optimized for use in the Go “for” clause -// -// The iterators in parly.iterator are thread-safe and re-entrant, but generally, this depends -// on the iterator implementation used. -// -// // triple-expression works for Iterator that do not require Cancel -// for iterator := NewIterator(); iterator.HasNext(); { -// v := iterator.SameValue() -// } -// -// // conditional expression can be used with all iterators -// iterator := NewIterator() -// for iterator.HasNext() { -// v := iterator.SameValue() -// } -// if err = iterator.Cancel(); … -// -// ForIterator is an iterator optimized for the Init and Condition -// statements of a Go “for” clause -// -// Usage: +// Usage in Go “for” clause: // // for i, iterator := NewIterator().Init(); iterator.Cond(&i); { // println(i) // // var err error // for i, iterator := NewIterator().Init(); iterator.Cond(&i, &err); { +// println(i) // } // if err != nil { // // var err error // var iterator = NewIterator() -// for i, iterator := NewIterator().Init(); iterator.Cond(&i); { -// } -// if err := iterator.Cancel() { +// for i := 0; iterator.Cond(&i); { // println(i) +// } +// if err = iterator.Cancel(); err != nil { type Iterator[T any] interface { // Init implements the right-hand side of a short variable declaration in - // the init statement for a Go “for” clause + // the init statement of a Go “for” clause Init() (iterationVariable T, iterator Iterator[T]) // Cond implements the condition statement of a Go “for” clause + // - condition is true if iterationVariable was assigned a value and the iteration should continue // - the iterationVariable is updated by being provided as a pointer. // iterationVariable cannot be nil // - errp is an optional error pointer receiving any errors during iterator execution - // - condition is true if iterationVariable was assigned a value and the iteration should continue Cond(iterationVariablep *T, errp ...*error) (condition bool) // Next advances to next item and returns it - // - if the next item does exist, value is valid and hasValue is true - // - if no next item exists, value is the data type zero-value and hasValue is false + // - if hasValue true, value contains the next value + // - otherwise, no more items exist and value is the data type zero-value Next() (value T, hasValue bool) - // Same returns the same value again. - // - If a value does exist, it is returned in value and hasValue is true - // - If a value does not exist, the data type zero-value is returned and hasValue is false + // Same returns the same value again + // - if hasValue true, value is valid + // - otherwise, no more items exist and value is the data type zero-value // - If Next or Cond has not been invoked, Same first advances to the first item Same() (value T, hasValue bool) - // Cancel release resources for this iterator - // - returns the first error that occurred during iteration if any - // - Not every iterator requires a Cancel invocation + // Cancel stops an iteration + // - after Cancel invocation, Cond, Next and Same indicate no value available + // - Cancel returns the first error that occurred during iteration, if any + // - an iterator implementation may require Cancel invocation + // to release resources + // - Cancel is deferrable Cancel(errp ...*error) (err error) - // // HasNext advances to next item and returns hasValue true if this next item does exists - // HasNext() (hasValue bool) - // // NextValue advances to next item and returns it. - // // - If no next value exists, the data type zero-value is returned - // NextValue() (value T) - // // Has returns true if Same() or SameValue will return items. - // // - If Next, FindNext or HasNext have not been invoked, - // // Has first advances to the first item - // Has() (hasValue bool) - // // SameValue returns the same value again - // // - If a value does not exist, the data type zero-value is returned - // // - If Next, FindNext or HasNext have not been invoked, - // // SameValue first advances to the first item - // SameValue() (value T) } diff --git a/mains/go.mod b/mains/go.mod index 9347f757..638f5289 100644 --- a/mains/go.mod +++ b/mains/go.mod @@ -7,7 +7,7 @@ toolchain go1.21.3 replace github.com/haraldrudell/parl => ../../parl require ( - github.com/haraldrudell/parl v0.4.117 + github.com/haraldrudell/parl v0.4.118 golang.org/x/sys v0.13.0 ) diff --git a/pterm/go.mod b/pterm/go.mod index 55ed1fa5..590dbe4b 100644 --- a/pterm/go.mod +++ b/pterm/go.mod @@ -5,7 +5,7 @@ go 1.21 replace github.com/haraldrudell/parl => ../../parl require ( - github.com/haraldrudell/parl v0.4.117 + github.com/haraldrudell/parl v0.4.118 golang.org/x/term v0.13.0 ) diff --git a/slow-detector-core.go b/slow-detector-core.go index f409de96..c72e32a9 100644 --- a/slow-detector-core.go +++ b/slow-detector-core.go @@ -37,12 +37,19 @@ type SlowDetectorCore struct { average ptime.Averager[time.Duration] } +// NewSlowDetectorCore returns an object tracking nonm-returning or slow function invocations +// - callback receives offending slow-detector invocations, cannot be nil +// - slowTyp configures whether the support-thread is shared +// - goGen is used for a possible deferred thread-launch +// - optional values are: +// - — nonReturnPeriod: how often non-returning invocations are reported, default once per minute +// - — minimum slowness duration that is being reported, default 100 ms func NewSlowDetectorCore(callback CbSlowDetector, slowTyp slowType, goGen GoGen, nonReturnPeriod ...time.Duration) (slowDetector *SlowDetectorCore) { if callback == nil { panic(perrors.NewPF("callback cannot be nil")) } - // threshold 1: time between non-return reports, default 1 minute + // nonReturnPeriod[0]: time between non-return reports, default 1 minute var nonReturnPeriod0 time.Duration if len(nonReturnPeriod) > 0 { nonReturnPeriod0 = nonReturnPeriod[0] @@ -50,28 +57,23 @@ func NewSlowDetectorCore(callback CbSlowDetector, slowTyp slowType, goGen GoGen, nonReturnPeriod0 = defaultNonReturnPeriod } + // nonReturnPeriod[1]: minimum duration for slowness to be reported, default 100 ms + var minReportedDuration time.Duration + if len(nonReturnPeriod) > 1 { + minReportedDuration = nonReturnPeriod[1] + } else { + minReportedDuration = defaultMinReportDuration + } + return &SlowDetectorCore{ ID: slowIDGenerator.ID(), callback: callback, thread: NewSlowDetectorThread(slowTyp, nonReturnPeriod0, goGen), - max: *NewAtomicMax[time.Duration](), + max: *NewAtomicMax[time.Duration](minReportedDuration), average: *ptime.NewAverager[time.Duration](), } } -func (s *SlowDetectorCore) Init(threshold ...time.Duration) (slowDetector *SlowDetectorCore) { - slowDetector = s - // threshold0: minimum slowness to report, default 100 ms - var threshold0 time.Duration - if len(threshold) > 0 { - threshold0 = threshold[0] - } else { - threshold0 = defaultMinReportDuration - } - s.max.Init(threshold0) - return -} - // Start returns the effective start time for a new timing cycle // - value is optional start time, default time.Now() func (sd *SlowDetectorCore) Start(invoLabel string, value ...time.Time) (invocation *SlowDetectorInvocation) { diff --git a/slow-detector.go b/slow-detector.go index 1aa419b4..d043ba4c 100644 --- a/slow-detector.go +++ b/slow-detector.go @@ -49,15 +49,7 @@ func NewSlowDetector(label string, slowTyp slowType, printf PrintfFunc, goGen Go label: label, printf: printf, } - var threshold0, nonReturnPeriod []time.Duration - if len(threshold) > 0 { - threshold0 = threshold[0:1] - if len(threshold) > 1 { - nonReturnPeriod = threshold[1:2] - } - } - sd.sd = *NewSlowDetectorCore(sd.callback, slowTyp, goGen, nonReturnPeriod...) - sd.sd.Init(threshold0...) + sd.sd = *NewSlowDetectorCore(sd.callback, slowTyp, goGen, threshold...) return &sd } diff --git a/sqliter/go.mod b/sqliter/go.mod index 9fb19f59..59280768 100644 --- a/sqliter/go.mod +++ b/sqliter/go.mod @@ -8,8 +8,8 @@ replace github.com/haraldrudell/parl => ../../parl require ( github.com/google/uuid v1.4.0 - github.com/haraldrudell/parl v0.4.117 - modernc.org/sqlite v1.26.0 + github.com/haraldrudell/parl v0.4.118 + modernc.org/sqlite v1.27.0 ) require ( diff --git a/sqliter/go.sum b/sqliter/go.sum index 650beba2..b43e089f 100644 --- a/sqliter/go.sum +++ b/sqliter/go.sum @@ -49,8 +49,8 @@ modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.26.0 h1:SocQdLRSYlA8W99V8YH0NES75thx19d9sB/aFc4R8Lw= -modernc.org/sqlite v1.26.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= +modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8= +modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= diff --git a/watchfs/go.mod b/watchfs/go.mod index ed5a20f6..91eaa7e7 100644 --- a/watchfs/go.mod +++ b/watchfs/go.mod @@ -7,7 +7,7 @@ replace github.com/haraldrudell/parl => ../../parl require ( github.com/fsnotify/fsnotify v1.7.0 github.com/google/uuid v1.4.0 - github.com/haraldrudell/parl v0.4.117 + github.com/haraldrudell/parl v0.4.118 ) require ( diff --git a/yamler/go.mod b/yamler/go.mod index e6743842..e0778004 100644 --- a/yamler/go.mod +++ b/yamler/go.mod @@ -7,7 +7,7 @@ replace github.com/haraldrudell/parl => ../../parl replace github.com/haraldrudell/parl/mains => ../mains require ( - github.com/haraldrudell/parl v0.4.117 + github.com/haraldrudell/parl v0.4.118 golang.org/x/exp v0.0.0-20231006140011-7918f672742d gopkg.in/yaml.v3 v3.0.1 )