From 9ee50d312b39901ef0d7819444bbdc2aec1cde22 Mon Sep 17 00:00:00 2001 From: Arpad Ryszka Date: Wed, 4 Mar 2026 15:03:29 +0100 Subject: [PATCH] fix some algo edge cases --- adapative.go | 92 +++++++++++++++++++++++++++++ algo.go | 163 --------------------------------------------------- algo_test.go | 44 -------------- maxto.go | 79 +++++++++++++++++++++++++ pool.go | 1 + 5 files changed, 172 insertions(+), 207 deletions(-) create mode 100644 adapative.go delete mode 100644 algo.go delete mode 100644 algo_test.go create mode 100644 maxto.go diff --git a/adapative.go b/adapative.go new file mode 100644 index 0000000..ffbcc6f --- /dev/null +++ b/adapative.go @@ -0,0 +1,92 @@ +package pool + +import "time" + +const ( + // arbitrary values to be most likely out of sync with anything else: + minNightshiftTime = 729 * time.Millisecond // ~1sec + maxNightshiftTime = 59049 * time.Second // ~2/3day +) + +type adaptive struct { + activeTime time.Time + nsTO time.Duration + idle bool + average int + deviation int +} + +func makeAdaptiveAlgo() *adaptive { + return &adaptive{idle: true} +} + +func abs(v int) int { + if v >= 0 { + return v + } + + return 0 - v +} + +func divE(v int) int { + return (3 * v) >> 3 // 1 / 2.72 => 3 / 8 +} + +func mulE(v int) int { + return (11 * v) >> 2 // 2.72 => 11 / 4 +} + +func movingAverage(prev, currv int) int { + return prev + divE(currv-prev) +} + +func movingAbsoluteDeviation(prev, currv, currav int) int { + return prev + divE(abs(currv-currav)-prev) +} + +func targetCapacity(av, dev int) int { + return av + mulE(dev) +} + +func (a *adaptive) target(s Stats) int { + av := movingAverage(a.average, s.Active) + dev := movingAbsoluteDeviation(a.deviation, s.Active, av) + a.average = av + a.deviation = dev + return targetCapacity(av, dev) +} + +func (a *adaptive) nightshift(s Stats) time.Duration { + if a.idle && s.Active == 0 { + return a.nsTO + } + + if !a.idle && s.Active > 0 { + return 0 + } + + now := time.Now() + a.idle = !a.idle + if !a.idle { + a.activeTime = now + return 0 + } + + a.nsTO = now.Sub(a.activeTime) + a.nsTO = (3 * a.nsTO) >> 3 + if a.nsTO < minNightshiftTime { + a.nsTO = minNightshiftTime + } + + if a.nsTO > maxNightshiftTime { + a.nsTO = maxNightshiftTime + } + + return a.nsTO +} + +func (a *adaptive) Target(s Stats) (int, time.Duration) { + t := a.target(s) + ns := a.nightshift(s) + return t, ns +} diff --git a/algo.go b/algo.go deleted file mode 100644 index d702072..0000000 --- a/algo.go +++ /dev/null @@ -1,163 +0,0 @@ -package pool - -import "time" - -const ( - // arbitrary values to be most likely out of sync with anything else: - minNightshiftTime = 729 * time.Millisecond // ~1sec - maxNightshiftTime = 59049 * time.Second // ~2/3day -) - -type adaptive struct { - lastTurnedActive time.Time - nightshiftTime time.Duration - idle bool - average int - deviation int -} - -type maxTimeout struct { - max int - to time.Duration - items []time.Time -} - -func makeAdaptiveAlgo() *adaptive { - return &adaptive{idle: true} -} - -func abs(v int) int { - if v >= 0 { - return v - } - - return 0 - v -} - -func divE(v int) int { - return (3 * v) >> 3 // 1 / 2.72 => 3 / 8 -} - -func mulE(v int) int { - return (11 * v) >> 2 // 2.72 => 11 / 4 -} - -func movingAverage(prev, currv int) int { - return prev + divE(currv-prev) -} - -func movingAbsoluteDeviation(prev, currv, currav int) int { - return prev + divE(abs(currv-currav)-prev) -} - -func targetCapacity(av, dev int) int { - return av + mulE(dev) -} - -func (a *adaptive) target(s Stats) int { - av := movingAverage(a.average, s.Active) - dev := movingAbsoluteDeviation(a.deviation, s.Active, av) - a.average = av - a.deviation = dev - return targetCapacity(av, dev) -} - -func (a *adaptive) nightshift(s Stats) time.Duration { - if a.idle && s.Active == 0 { - return a.nightshiftTime - } - - if !a.idle && s.Active > 0 { - return 0 - } - - now := time.Now() - a.idle = !a.idle - if !a.idle { - a.lastTurnedActive = now - return 0 - } - - a.nightshiftTime = now.Sub(a.lastTurnedActive) - a.nightshiftTime = (3 * a.nightshiftTime) >> 3 - if a.nightshiftTime < minNightshiftTime { - a.nightshiftTime = minNightshiftTime - } - - if a.nightshiftTime > maxNightshiftTime { - a.nightshiftTime = maxNightshiftTime - } - - return a.nightshiftTime -} - -func (a *adaptive) Target(s Stats) (int, time.Duration) { - t := a.target(s) - ns := a.nightshift(s) - return t, ns -} - -func makeMaxTimeout(max int, to time.Duration) *maxTimeout { - return &maxTimeout{ - max: max, - to: to, - } -} - -func (a *maxTimeout) Target(s Stats) (int, time.Duration) { - if a.max <= 0 && a.to <= 0 { - return s.Idle, 0 - } - - if a.max > 0 && a.to <= 0 { - t := s.Idle - if t > a.max { - t = a.max - } - - return t, 0 - } - - var zero time.Time - if len(a.items) > s.Idle { - for i := s.Idle; i < len(a.items); i++ { - a.items[i] = zero - } - - a.items = a.items[:s.Idle] - if len(a.items) == 0 { - a.items = nil - } - } - - now := time.Now() - for len(a.items) < s.Idle { - a.items = append(a.items, now) - } - - var drop int - for drop < len(a.items) && a.items[drop].Add(a.to).Before(now) { - a.items[drop] = zero - drop++ - } - - if drop > 0 { - for i := 0; i < drop; i++ { - a.items[i] = zero - } - - a.items = a.items[drop:] - } - - if len(a.items) == 0 { - a.items = nil - return 0, 0 - } - - t := len(a.items) - if a.max > 0 && t > a.max { - t = a.max - } - - return t, a.items[0].Add(a.to).Sub(now) -} diff --git a/algo_test.go b/algo_test.go deleted file mode 100644 index 0c4dad6..0000000 --- a/algo_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package pool_test - -import ( - "code.squareroundforest.org/arpio/pool" - "time" -) - -type maxAlgo int - -type timeoutAlgo struct { - to time.Duration - items []time.Time -} - -func (a maxAlgo) Target(s pool.Stats) (int, time.Duration) { - if s.Idle <= int(a) { - return s.Idle, 0 - } - - return int(a), 0 -} - -func (a *timeoutAlgo) Target(s pool.Stats) (int, time.Duration) { - now := time.Now() - for len(a.items) < s.Idle { - a.items = append(a.items, now) - } - - t := s.Idle - for len(a.items) > 0 && a.items[0].Add(a.to).Before(now) { - t-- - a.items = a.items[1:] - } - - if t < 0 { - t = 0 - } - - if len(a.items) == 0 { - return t, 0 - } - - return t, a.items[0].Add(a.to).Sub(now) -} diff --git a/maxto.go b/maxto.go new file mode 100644 index 0000000..a8ec955 --- /dev/null +++ b/maxto.go @@ -0,0 +1,79 @@ +package pool + +import "time" + +type maxTimeout struct { + max int + to time.Duration + items []time.Time + lastPut int +} + +func makeMaxTimeout(max int, to time.Duration) *maxTimeout { + return &maxTimeout{ + max: max, + to: to, + } +} + +func (a *maxTimeout) Target(s Stats) (int, time.Duration) { + if a.max <= 0 && a.to <= 0 { + return s.Idle, 0 + } + + if a.max > 0 && a.to <= 0 { + t := s.Idle + if t > a.max { + t = a.max + } + + return t, 0 + } + + var zero time.Time + if len(a.items) > s.Idle { + for i := s.Idle; i < len(a.items); i++ { + a.items[i] = zero + } + + a.items = a.items[:s.Idle] + if len(a.items) == 0 { + a.items = nil + } + } + + now := time.Now() + for len(a.items) < s.Idle { + a.items = append(a.items, now) + } + + puts := s.Put - a.lastPut + if puts > 0 && len(a.items) >= puts { + a.lastPut = s.Put + for i := len(a.items) - puts; i < len(a.items); i++ { + a.items[i] = now + } + } + + var drop int + for drop < len(a.items) && a.items[drop].Add(a.to).Before(now) { + a.items[drop] = zero + drop++ + } + + if drop > 0 { + a.items = a.items[drop:] + } + + if len(a.items) == 0 { + a.items = nil + return 0, 0 + } + + t := len(a.items) + if a.max > 0 && t > a.max { + t = a.max + } + + return t, a.items[0].Add(a.to).Sub(now) +} diff --git a/pool.go b/pool.go index dbfa433..4fa1548 100644 --- a/pool.go +++ b/pool.go @@ -202,6 +202,7 @@ func (p pool[R]) freeIdle() { t, f := p.options.Algo.Target(s.stats) prev := s.stats.Free s = p.freeItems(s, t) + s.stats.Idle = len(s.items) if s.stats.Free > prev { p.sendEvent(FreeOperation, s.stats) }