FILE / ScuroNeko/Laniakea
observer_async.go
Исходный файл и его история в репозитории.
101 lines
1.9 KiB
Go
101 lines
1.9 KiB
Go
package laniakea
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"git.scuroneko.dev/scuroneko/sneklog/v2"
|
|
)
|
|
|
|
const observerQueueSize = 1024
|
|
|
|
type queuedObserverEvent struct {
|
|
ctx context.Context
|
|
event Event
|
|
}
|
|
|
|
type observerDispatcher struct {
|
|
observer Observer
|
|
logger *sneklog.Logger
|
|
queue chan queuedObserverEvent
|
|
stop chan struct{}
|
|
mu sync.RWMutex
|
|
closed bool
|
|
wg sync.WaitGroup
|
|
stopOnce sync.Once
|
|
dropped atomic.Uint64
|
|
}
|
|
|
|
func newObserverDispatcher(observer Observer, logger *sneklog.Logger) *observerDispatcher {
|
|
dispatcher := &observerDispatcher{
|
|
observer: observer,
|
|
logger: logger,
|
|
queue: make(chan queuedObserverEvent, observerQueueSize),
|
|
stop: make(chan struct{}),
|
|
}
|
|
dispatcher.wg.Add(1)
|
|
go dispatcher.run()
|
|
return dispatcher
|
|
}
|
|
|
|
func (d *observerDispatcher) enqueue(ctx context.Context, event Event) {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
} else {
|
|
ctx = context.WithoutCancel(ctx)
|
|
}
|
|
d.mu.RLock()
|
|
defer d.mu.RUnlock()
|
|
if d.closed {
|
|
return
|
|
}
|
|
select {
|
|
case d.queue <- queuedObserverEvent{ctx: ctx, event: event}:
|
|
default:
|
|
dropped := d.dropped.Add(1)
|
|
if d.logger != nil && (dropped == 1 || dropped&(dropped-1) == 0) {
|
|
d.logger.Warnf("observer queue full; dropped %d events", dropped)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d *observerDispatcher) run() {
|
|
defer d.wg.Done()
|
|
for {
|
|
select {
|
|
case queued := <-d.queue:
|
|
d.dispatch(queued)
|
|
case <-d.stop:
|
|
for {
|
|
select {
|
|
case queued := <-d.queue:
|
|
d.dispatch(queued)
|
|
default:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d *observerDispatcher) dispatch(queued queuedObserverEvent) {
|
|
defer func() {
|
|
if recovered := recover(); recovered != nil && d.logger != nil {
|
|
d.logger.Errorln(fmt.Sprintf("panic in observer: %v", recovered))
|
|
}
|
|
}()
|
|
emitObserverEvent(d.observer, queued.ctx, queued.event)
|
|
}
|
|
|
|
func (d *observerDispatcher) close() {
|
|
d.stopOnce.Do(func() {
|
|
d.mu.Lock()
|
|
d.closed = true
|
|
close(d.stop)
|
|
d.mu.Unlock()
|
|
})
|
|
d.wg.Wait()
|
|
}
|