Files
elmprodvpn/selective-vpn-api/app/events_bus.go
beckline 10a10f44a8 baseline: api+gui traffic mode + candidates picker
Snapshot before app-launcher (cgroup/mark) work; ignore binaries/backups.
2026-02-14 15:52:20 +03:00

110 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package app
import (
"os"
"strconv"
"strings"
"sync"
"time"
)
// ---------------------------------------------------------------------
// события / event bus
// ---------------------------------------------------------------------
// EN: In-memory bounded event bus used for SSE replay and polling watchers.
// EN: It keeps only the latest N events and assigns monotonically increasing IDs.
// RU: Ограниченная in-memory шина событий для SSE-реплея и фоновых вотчеров.
// RU: Хранит только последние N событий и присваивает монотонно растущие ID.
type eventBus struct {
mu sync.Mutex
cond *sync.Cond
buf []Event
cap int
next int64
}
// ---------------------------------------------------------------------
// EN: `newEventBus` creates a new instance for event bus.
// RU: `newEventBus` - создает новый экземпляр для event bus.
// ---------------------------------------------------------------------
func newEventBus(capacity int) *eventBus {
if capacity < 16 {
capacity = 16
}
b := &eventBus{
cap: capacity,
buf: make([]Event, 0, capacity),
}
b.cond = sync.NewCond(&b.mu)
return b
}
// ---------------------------------------------------------------------
// EN: `push` contains core logic for push.
// RU: `push` - содержит основную логику для push.
// ---------------------------------------------------------------------
func (b *eventBus) push(kind string, data interface{}) Event {
b.mu.Lock()
defer b.mu.Unlock()
b.next++
evt := Event{
ID: b.next,
Kind: kind,
Ts: time.Now().UTC().Format(time.RFC3339Nano),
Data: data,
}
if len(b.buf) >= b.cap {
b.buf = b.buf[1:]
}
b.buf = append(b.buf, evt)
b.cond.Broadcast()
return evt
}
// ---------------------------------------------------------------------
// EN: `since` contains core logic for since.
// RU: `since` - содержит основную логику для since.
// ---------------------------------------------------------------------
func (b *eventBus) since(id int64) []Event {
b.mu.Lock()
defer b.mu.Unlock()
return b.sinceLocked(id)
}
// ---------------------------------------------------------------------
// EN: `sinceLocked` contains core logic for since locked.
// RU: `sinceLocked` - содержит основную логику для since locked.
// ---------------------------------------------------------------------
func (b *eventBus) sinceLocked(id int64) []Event {
if len(b.buf) == 0 {
return nil
}
var out []Event
for _, ev := range b.buf {
if ev.ID > id {
out = append(out, ev)
}
}
return out
}
// ---------------------------------------------------------------------
// env helpers
// ---------------------------------------------------------------------
// EN: Positive integer env reader with safe default fallback.
// RU: Чтение положительного целого из env с безопасным fallback на дефолт.
func envInt(key string, def int) int {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
if n, err := strconv.Atoi(v); err == nil && n > 0 {
return n
}
}
return def
}
var events = newEventBus(envInt("SVPN_EVENTS_CAP", defaultEventsCapacity))