v0.0.575 DelayedCombiningInvoker
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 3m31s

This commit is contained in:
Mike Schwörer 2025-05-11 19:17:05 +02:00
parent 959020e3c0
commit 49bc52d63e
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
3 changed files with 164 additions and 2 deletions

View File

@ -0,0 +1,132 @@
package dataext
import (
"context"
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
"git.blackforestbytes.com/BlackForestBytes/goext/syncext"
"sync"
"time"
)
type DelayedCombiningInvoker struct {
syncLock sync.Mutex
triggerChan chan bool
cancelChan chan bool
execNowChan chan bool
action func()
delay time.Duration
maxDelay time.Duration
executorRunning *syncext.AtomicBool
lastRequestTime time.Time
initialRequestTime time.Time
}
func NewDelayedCombiningInvoker(action func(), delay time.Duration, maxDelay time.Duration) *DelayedCombiningInvoker {
return &DelayedCombiningInvoker{
action: action,
delay: delay,
maxDelay: maxDelay,
executorRunning: syncext.NewAtomicBool(false),
triggerChan: make(chan bool),
cancelChan: make(chan bool, 1),
execNowChan: make(chan bool, 1),
lastRequestTime: time.Now(),
initialRequestTime: time.Now(),
}
}
func (d *DelayedCombiningInvoker) Request() {
now := time.Now()
d.syncLock.Lock()
defer d.syncLock.Unlock()
if d.executorRunning.Get() {
d.lastRequestTime = now
d.triggerChan <- true
} else {
d.initialRequestTime = now
d.lastRequestTime = now
d.executorRunning.Set(true)
syncext.ReadNonBlocking(d.triggerChan) // clear the channel
syncext.ReadNonBlocking(d.cancelChan) // clear the channel
syncext.ReadNonBlocking(d.execNowChan) // clear the channel
go d.run()
}
}
func (d *DelayedCombiningInvoker) run() {
defer func() {
d.syncLock.Lock()
d.executorRunning.Set(false)
d.syncLock.Unlock()
}()
for {
d.syncLock.Lock()
timeOut := min(d.maxDelay-time.Since(d.initialRequestTime), d.delay-time.Since(d.lastRequestTime))
if timeOut < 0 {
timeOut = 0
}
d.syncLock.Unlock()
immediately := false
select {
case <-d.execNowChan:
// run immediately
immediately = true
break
case <-d.triggerChan:
// external trigger - needs to re-evaluate
break
case <-d.cancelChan:
// cancel
return
case <-time.After(timeOut):
// time elapsed - check for execution
break
}
d.syncLock.Lock()
execute := immediately || time.Since(d.lastRequestTime) >= d.delay || time.Since(d.initialRequestTime) >= d.maxDelay
if !execute {
d.syncLock.Unlock()
continue
}
_ = langext.RunPanicSafe(d.action)
d.syncLock.Unlock()
return
}
}
func (d *DelayedCombiningInvoker) CancelPendingRequests() {
d.syncLock.Lock()
defer d.syncLock.Unlock()
syncext.WriteNonBlocking(d.cancelChan, true)
}
func (d *DelayedCombiningInvoker) HasPendingRequests() bool {
return d.executorRunning.Get()
}
func (d *DelayedCombiningInvoker) ExecuteNow() bool {
d.syncLock.Lock()
defer d.syncLock.Unlock()
if d.executorRunning.Get() {
syncext.WriteNonBlocking(d.execNowChan, true)
return true
} else {
return false
}
}
func (d *DelayedCombiningInvoker) WaitForCompletion(ctx context.Context) error {
return d.executorRunning.WaitWithContext(ctx, false)
}

View File

@ -119,6 +119,25 @@ func (s *SyncMap[TKey, TData]) Delete(key TKey) bool {
return ok
}
func (s *SyncMap[TKey, TData]) DeleteIf(fn func(key TKey, data TData) bool) int {
s.lock.Lock()
defer s.lock.Unlock()
if s.data == nil {
s.data = make(map[TKey]TData)
}
rm := 0
for k, v := range s.data {
if fn(k, v) {
delete(s.data, k)
rm++
}
}
return rm
}
func (s *SyncMap[TKey, TData]) Clear() {
s.lock.Lock()
defer s.lock.Unlock()
@ -172,3 +191,14 @@ func (s *SyncMap[TKey, TData]) GetAllValues() []TData {
return r
}
func (s *SyncMap[TKey, TData]) Count() int {
s.lock.Lock()
defer s.lock.Unlock()
if s.data == nil {
s.data = make(map[TKey]TData)
}
return len(s.data)
}

View File

@ -1,5 +1,5 @@
package goext
const GoextVersion = "0.0.574"
const GoextVersion = "0.0.575"
const GoextVersionTimestamp = "2025-05-07T15:28:15+0200"
const GoextVersionTimestamp = "2025-05-11T19:17:05+0200"