Resolve "Deprecate Providers package in favor of Worker"
This commit is contained in:
@@ -1,77 +1,238 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gitlab.michelsen.id/phillmichelsen/tessera/services/data_service/internal/domain"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultClientBuf = 256
|
||||
var (
|
||||
// Lease lifecycle errors.
|
||||
ErrAlreadyReleased = errors.New("lease already released")
|
||||
ErrSenderAlreadyLeased = errors.New("sender already leased")
|
||||
ErrReceiverAlreadyLeased = errors.New("receiver already leased")
|
||||
ErrSenderNotLeased = errors.New("no sender lease active")
|
||||
ErrReceiverNotLeased = errors.New("no receiver lease active")
|
||||
|
||||
// Config errors
|
||||
ErrBadConfig = errors.New("config not valid")
|
||||
ErrConfigActiveLeases = errors.New("cannot configure while a lease is active")
|
||||
)
|
||||
|
||||
// session holds per-session state.
|
||||
// Owned by the manager loop. So we do not need a mutex.
|
||||
type WorkerEntry struct {
|
||||
Type string
|
||||
Spec []byte
|
||||
Unit []byte
|
||||
}
|
||||
|
||||
// SessionConfig carries non-live-tunable knobs for a session.
|
||||
// Manager mutates this directly; session does not expose Configure anymore.
|
||||
type SessionConfig struct {
|
||||
IdleAfter time.Duration // <=0 disables idle timer
|
||||
EgressBuffer int // receiver egress buffer size
|
||||
Patterns []domain.Pattern
|
||||
Workers []WorkerEntry
|
||||
}
|
||||
|
||||
// session is manager-owned state. Single goroutine access.
|
||||
type session struct {
|
||||
id uuid.UUID
|
||||
|
||||
inChannel chan domain.Message // caller writes
|
||||
outChannel chan domain.Message // caller reads
|
||||
// Router pipes
|
||||
ingress chan<- domain.Message // router.Incoming(); router-owned
|
||||
egress chan domain.Message // current receiver lease egress; owned here
|
||||
|
||||
bound map[domain.Identifier]struct{}
|
||||
// Config and timers
|
||||
cfg SessionConfig
|
||||
idleTimer *time.Timer
|
||||
idleCallback func() // stored on creation
|
||||
|
||||
attached bool
|
||||
idleAfter time.Duration
|
||||
idleTimer *time.Timer
|
||||
// Sender lease
|
||||
sendOpen bool
|
||||
sendDone chan struct{}
|
||||
|
||||
// Receiver lease
|
||||
receiveOpen bool
|
||||
receiveDone chan struct{}
|
||||
}
|
||||
|
||||
func newSession(idleAfter time.Duration) *session {
|
||||
return &session{
|
||||
id: uuid.New(),
|
||||
bound: make(map[domain.Identifier]struct{}),
|
||||
attached: false,
|
||||
idleAfter: idleAfter,
|
||||
// newSession arms a 1-minute idle timer immediately. Manager must
|
||||
// configure before it fires. idleCb is invoked by the timer.
|
||||
func newSession(ingress chan<- domain.Message, idleCb func()) *session {
|
||||
s := &session{
|
||||
id: uuid.New(),
|
||||
ingress: ingress,
|
||||
cfg: SessionConfig{
|
||||
IdleAfter: time.Minute, // default 1m on creation
|
||||
EgressBuffer: 256, // default buffer
|
||||
},
|
||||
idleCallback: idleCb,
|
||||
}
|
||||
s.armIdleTimer()
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *session) setConfig(cfg any) error {
|
||||
if s.sendOpen || s.receiveOpen {
|
||||
return ErrConfigActiveLeases
|
||||
}
|
||||
|
||||
cfgParsed, ok := cfg.(SessionConfig)
|
||||
if !ok {
|
||||
return ErrBadConfig
|
||||
}
|
||||
|
||||
s.cfg = cfgParsed
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *session) getEgress() (chan<- domain.Message, bool) {
|
||||
if s.egress == nil {
|
||||
return nil, false
|
||||
}
|
||||
return s.egress, true
|
||||
}
|
||||
|
||||
func (s *session) getPatterns() []domain.Pattern {
|
||||
return nil
|
||||
}
|
||||
|
||||
// leaseSender opens a sender lease and returns send(m) error.
|
||||
func (s *session) leaseSender() (func(domain.Message) error, error) {
|
||||
if s.sendOpen {
|
||||
return nil, ErrSenderAlreadyLeased
|
||||
}
|
||||
s.sendOpen = true
|
||||
s.sendDone = make(chan struct{})
|
||||
s.disarmIdleTimer()
|
||||
|
||||
// Snapshot for lease-scoped handle.
|
||||
done := s.sendDone
|
||||
|
||||
sendFunc := func(m domain.Message) error {
|
||||
select {
|
||||
case <-done:
|
||||
return ErrAlreadyReleased
|
||||
case s.ingress <- m:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return sendFunc, nil
|
||||
}
|
||||
|
||||
// releaseSender releases the current sender lease.
|
||||
func (s *session) releaseSender() error {
|
||||
if !s.sendOpen {
|
||||
return ErrSenderNotLeased
|
||||
}
|
||||
s.sendOpen = false
|
||||
if s.sendDone != nil {
|
||||
close(s.sendDone) // invalidates all prior send funcs
|
||||
s.sendDone = nil
|
||||
}
|
||||
if !s.receiveOpen {
|
||||
s.armIdleTimer()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// leaseReceiver opens a receiver lease and returns receive() (Message, error).
|
||||
func (s *session) leaseReceiver() (func() (domain.Message, error), error) {
|
||||
if s.receiveOpen {
|
||||
return nil, ErrReceiverAlreadyLeased
|
||||
}
|
||||
s.receiveOpen = true
|
||||
s.receiveDone = make(chan struct{})
|
||||
s.egress = make(chan domain.Message, s.cfg.EgressBuffer)
|
||||
s.disarmIdleTimer()
|
||||
|
||||
// Snapshots for lease-scoped handles.
|
||||
done := s.receiveDone
|
||||
eg := s.egress
|
||||
|
||||
receiveFunc := func() (domain.Message, error) {
|
||||
select {
|
||||
case <-done:
|
||||
return domain.Message{}, ErrAlreadyReleased
|
||||
case msg, ok := <-eg:
|
||||
if !ok {
|
||||
return domain.Message{}, ErrAlreadyReleased
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
|
||||
return receiveFunc, nil
|
||||
}
|
||||
|
||||
// releaseReceiver releases the current receiver lease.
|
||||
// Manager must stop any routing into s.egress before calling this.
|
||||
func (s *session) releaseReceiver() error {
|
||||
if !s.receiveOpen {
|
||||
return ErrReceiverNotLeased
|
||||
}
|
||||
s.receiveOpen = false
|
||||
if s.receiveDone != nil {
|
||||
close(s.receiveDone) // invalidates all prior receive funcs
|
||||
s.receiveDone = nil
|
||||
}
|
||||
if s.egress != nil {
|
||||
close(s.egress)
|
||||
s.egress = nil
|
||||
}
|
||||
if !s.sendOpen {
|
||||
s.armIdleTimer()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// closeAll force-releases both sender and receiver leases. Safe to call multiple times.
|
||||
func (s *session) closeAll() {
|
||||
// Sender
|
||||
if s.sendOpen {
|
||||
s.sendOpen = false
|
||||
if s.sendDone != nil {
|
||||
close(s.sendDone)
|
||||
s.sendDone = nil
|
||||
}
|
||||
}
|
||||
// Receiver
|
||||
if s.receiveOpen {
|
||||
s.receiveOpen = false
|
||||
if s.receiveDone != nil {
|
||||
close(s.receiveDone)
|
||||
s.receiveDone = nil
|
||||
}
|
||||
if s.egress != nil {
|
||||
close(s.egress)
|
||||
s.egress = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// armIdleTimer sets the idle timer to call f after idleAfter duration (resets existing timer if any).
|
||||
func (s *session) armIdleTimer(f func()) {
|
||||
// armIdleTimer arms a timer using stored cfg.IdleAfter and idleCb.
|
||||
func (s *session) armIdleTimer() {
|
||||
if s.idleCallback == nil {
|
||||
slog.Warn("nil idle callback function provided to session")
|
||||
}
|
||||
if s.idleTimer != nil {
|
||||
s.idleTimer.Stop()
|
||||
s.idleTimer = nil
|
||||
}
|
||||
if s.cfg.IdleAfter > 0 && s.idleCallback != nil {
|
||||
s.idleTimer = time.AfterFunc(s.cfg.IdleAfter, s.idleCallback)
|
||||
}
|
||||
s.idleTimer = time.AfterFunc(s.idleAfter, f)
|
||||
}
|
||||
|
||||
// disarmIdleTimer stops and nils the idle timer if any. This call is idempotent.
|
||||
// disarmIdleTimer disarms the idle timer if active.
|
||||
func (s *session) disarmIdleTimer() {
|
||||
if s.idleTimer != nil {
|
||||
s.idleTimer.Stop()
|
||||
s.idleTimer = nil
|
||||
}
|
||||
}
|
||||
|
||||
// generateNewChannels creates new in/out channels for the session, will not close existing channels.
|
||||
func (s *session) generateNewChannels(inBuf, outBuf int) (chan domain.Message, chan domain.Message) {
|
||||
if inBuf <= 0 {
|
||||
inBuf = defaultClientBuf
|
||||
}
|
||||
if outBuf <= 0 {
|
||||
outBuf = defaultClientBuf
|
||||
}
|
||||
s.inChannel = make(chan domain.Message, inBuf)
|
||||
s.outChannel = make(chan domain.Message, outBuf)
|
||||
return s.inChannel, s.outChannel
|
||||
}
|
||||
|
||||
// clearChannels closes and nils the in/out channels.
|
||||
func (s *session) clearChannels() {
|
||||
if s.inChannel != nil {
|
||||
close(s.inChannel)
|
||||
s.inChannel = nil
|
||||
}
|
||||
if s.outChannel != nil {
|
||||
close(s.outChannel)
|
||||
s.outChannel = nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user