130 lines
3.1 KiB
Go
130 lines
3.1 KiB
Go
package manager
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"gitlab.michelsen.id/phillmichelsen/tessera/services/data_service/internal/domain"
|
|
)
|
|
|
|
// Session holds per-session state. Owned by the manager loop.
|
|
type session struct {
|
|
id uuid.UUID
|
|
|
|
clientIn chan domain.Message // caller writes
|
|
clientOut chan domain.Message // caller reads
|
|
|
|
bound map[domain.Identifier]struct{}
|
|
|
|
closed bool
|
|
attached bool
|
|
idleAfter time.Duration
|
|
idleTimer *time.Timer
|
|
}
|
|
|
|
// attachSession wires channels, stops idle timer, and registers ready routes.
|
|
// Precondition: session exists and is not attached/closed. Runs in loop.
|
|
func (m *Manager) attachSession(s *session, inBuf, outBuf int) (chan<- domain.Message, <-chan domain.Message, error) {
|
|
if inBuf <= 0 {
|
|
inBuf = defaultClientBuf
|
|
}
|
|
if outBuf <= 0 {
|
|
outBuf = defaultClientBuf
|
|
}
|
|
|
|
cin := make(chan domain.Message, inBuf)
|
|
cout := make(chan domain.Message, outBuf)
|
|
s.clientIn, s.clientOut = cin, cout
|
|
|
|
if s.idleTimer != nil {
|
|
s.idleTimer.Stop()
|
|
s.idleTimer = nil
|
|
}
|
|
|
|
// Forward clientIn to router.Incoming with drop on backpressure.
|
|
go func(src <-chan domain.Message, dst chan<- domain.Message) {
|
|
for msg := range src {
|
|
select {
|
|
case dst <- msg:
|
|
default:
|
|
lg().Warn("drop message on clientIn backpressure", "identifier", msg.Identifier.Key())
|
|
}
|
|
}
|
|
}(cin, m.router.IncomingChannel())
|
|
|
|
// Register all currently bound that are ready.
|
|
for ident := range s.bound {
|
|
if !ident.IsRaw() {
|
|
m.router.RegisterRoute(ident, cout)
|
|
continue
|
|
}
|
|
// Raw: register only if provider stream is active.
|
|
if p, subj, err := m.resolveProvider(ident); err == nil && p.IsStreamActive(subj) {
|
|
m.router.RegisterRoute(ident, cout)
|
|
}
|
|
}
|
|
|
|
s.attached = true
|
|
return cin, cout, nil
|
|
}
|
|
|
|
// detachSession deregisters all routes, closes channels, and arms idle timer.
|
|
// Precondition: session exists and is attached. Runs in loop.
|
|
func (m *Manager) detachSession(sid uuid.UUID, s *session) error {
|
|
if s.clientOut != nil {
|
|
for ident := range s.bound {
|
|
m.router.DeregisterRoute(ident, s.clientOut)
|
|
}
|
|
close(s.clientOut)
|
|
}
|
|
if s.clientIn != nil {
|
|
close(s.clientIn)
|
|
}
|
|
s.clientIn, s.clientOut = nil, nil
|
|
s.attached = false
|
|
|
|
// Arm idle timer to auto-close the session.
|
|
s.idleTimer = time.AfterFunc(s.idleAfter, func() {
|
|
m.cmdCh <- closeSessionCmd{sid: sid, resp: make(chan error, 1)}
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// closeSession performs full teardown and refcount drops. Runs in loop.
|
|
func (m *Manager) closeSession(sid uuid.UUID, s *session) {
|
|
if s.closed {
|
|
return
|
|
}
|
|
s.closed = true
|
|
|
|
// Detach if attached.
|
|
if s.attached {
|
|
if s.clientOut != nil {
|
|
for ident := range s.bound {
|
|
m.router.DeregisterRoute(ident, s.clientOut)
|
|
}
|
|
close(s.clientOut)
|
|
}
|
|
if s.clientIn != nil {
|
|
close(s.clientIn)
|
|
}
|
|
} else if s.idleTimer != nil {
|
|
s.idleTimer.Stop()
|
|
s.idleTimer = nil
|
|
}
|
|
|
|
// Drop refs for raw identifiers and stop streams if last ref. Fire-and-forget.
|
|
for ident := range s.bound {
|
|
if !ident.IsRaw() {
|
|
continue
|
|
}
|
|
if last := m.decrementStreamRefCount(ident); last {
|
|
if p, subj, err := m.resolveProvider(ident); err == nil {
|
|
_ = p.StopStreams([]string{subj}) // do not wait
|
|
}
|
|
}
|
|
}
|
|
|
|
delete(m.sessions, sid)
|
|
}
|