1
0
Fork 0
mirror of https://github.com/anyproto/any-sync.git synced 2025-06-08 05:57:03 +09:00
any-sync/commonspace/sync/tryaddqueue.go
2024-06-24 21:29:16 +02:00

138 lines
2.4 KiB
Go

package sync
import (
"context"
"sync"
"sync/atomic"
"github.com/cheggaaa/mb/v3"
"go.uber.org/zap"
)
type entry struct {
call func()
onRemove func()
cnt uint64
}
func newTryAddQueue(workers, maxSize int) *tryAddQueue {
ctx, cancel := context.WithCancel(context.Background())
ss := &tryAddQueue{
ctx: ctx,
cancel: cancel,
workers: workers,
batch: mb.New[string](maxSize),
entries: map[string]entry{},
}
return ss
}
type tryAddQueue struct {
ctx context.Context
cancel context.CancelFunc
workers int
cnt atomic.Uint64
entries map[string]entry
batch *mb.MB[string]
mx sync.Mutex
}
func (rp *tryAddQueue) Replace(id string, call, remove func()) {
curCnt := rp.cnt.Load()
rp.cnt.Add(1)
rp.mx.Lock()
if prevEntry, ok := rp.entries[id]; ok {
rp.entries[id] = entry{
call: call,
onRemove: remove,
cnt: curCnt,
}
rp.mx.Unlock()
prevEntry.onRemove()
return
}
ent := entry{
call: call,
onRemove: remove,
cnt: curCnt,
}
rp.entries[id] = ent
rp.mx.Unlock()
err := rp.batch.TryAdd(id)
if err != nil {
rp.mx.Lock()
curEntry := rp.entries[id]
if curEntry.cnt == curCnt {
delete(rp.entries, id)
}
rp.mx.Unlock()
if ent.onRemove != nil {
ent.onRemove()
}
}
}
func (rp *tryAddQueue) TryAdd(id string, call, remove func()) bool {
curCnt := rp.cnt.Load()
rp.cnt.Add(1)
rp.mx.Lock()
if _, ok := rp.entries[id]; ok {
rp.mx.Unlock()
if remove != nil {
remove()
}
return false
}
ent := entry{
call: call,
onRemove: remove,
cnt: curCnt,
}
rp.entries[id] = ent
rp.mx.Unlock()
err := rp.batch.TryAdd(id)
if err != nil {
rp.mx.Lock()
curEntry := rp.entries[id]
if curEntry.cnt == curCnt {
delete(rp.entries, id)
}
rp.mx.Unlock()
if ent.onRemove != nil {
ent.onRemove()
}
return false
}
return true
}
func (rp *tryAddQueue) Run() {
for i := 0; i < rp.workers; i++ {
go rp.sendLoop()
}
}
func (rp *tryAddQueue) sendLoop() {
for {
id, err := rp.batch.WaitOne(rp.ctx)
if err != nil {
log.Debug("close send loop", zap.Error(err))
return
}
rp.mx.Lock()
curEntry := rp.entries[id]
delete(rp.entries, id)
rp.mx.Unlock()
if curEntry.call != nil {
curEntry.call()
if curEntry.onRemove != nil {
curEntry.onRemove()
}
}
}
}
func (rp *tryAddQueue) Close() (err error) {
rp.cancel()
return rp.batch.Close()
}