feat: add session idle timeout cleanup goroutine
Some checks failed
check / check (push) Has been cancelled
Some checks failed
check / check (push) Has been cancelled
- Periodic cleanup loop deletes stale clients based on SESSION_IDLE_TIMEOUT - Orphaned sessions (no clients) are cleaned up automatically - last_seen already updated on each authenticated request via GetSessionByToken
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/chat/internal/broker"
|
"git.eeqj.de/sneak/chat/internal/broker"
|
||||||
"git.eeqj.de/sneak/chat/internal/config"
|
"git.eeqj.de/sneak/chat/internal/config"
|
||||||
@@ -30,12 +31,15 @@ type Params struct {
|
|||||||
Healthcheck *healthcheck.Healthcheck
|
Healthcheck *healthcheck.Healthcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultIdleTimeout = 24 * time.Hour
|
||||||
|
|
||||||
// Handlers manages HTTP request handling.
|
// Handlers manages HTTP request handling.
|
||||||
type Handlers struct {
|
type Handlers struct {
|
||||||
params *Params
|
params *Params
|
||||||
log *slog.Logger
|
log *slog.Logger
|
||||||
hc *healthcheck.Healthcheck
|
hc *healthcheck.Healthcheck
|
||||||
broker *broker.Broker
|
broker *broker.Broker
|
||||||
|
cancelCleanup context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Handlers instance.
|
// New creates a new Handlers instance.
|
||||||
@@ -43,7 +47,7 @@ func New(
|
|||||||
lifecycle fx.Lifecycle,
|
lifecycle fx.Lifecycle,
|
||||||
params Params,
|
params Params,
|
||||||
) (*Handlers, error) {
|
) (*Handlers, error) {
|
||||||
hdlr := &Handlers{
|
hdlr := &Handlers{ //nolint:exhaustruct // cancelCleanup set in startCleanup
|
||||||
params: ¶ms,
|
params: ¶ms,
|
||||||
log: params.Logger.Get(),
|
log: params.Logger.Get(),
|
||||||
hc: params.Healthcheck,
|
hc: params.Healthcheck,
|
||||||
@@ -51,10 +55,14 @@ func New(
|
|||||||
}
|
}
|
||||||
|
|
||||||
lifecycle.Append(fx.Hook{
|
lifecycle.Append(fx.Hook{
|
||||||
OnStart: func(_ context.Context) error {
|
OnStart: func(ctx context.Context) error {
|
||||||
|
hdlr.startCleanup(ctx)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
OnStop: func(_ context.Context) error {
|
OnStop: func(_ context.Context) error {
|
||||||
|
hdlr.stopCleanup()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -96,3 +104,78 @@ func (hdlr *Handlers) respondError(
|
|||||||
status,
|
status,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (hdlr *Handlers) idleTimeout() time.Duration {
|
||||||
|
raw := hdlr.params.Config.SessionIdleTimeout
|
||||||
|
if raw == "" {
|
||||||
|
return defaultIdleTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
dur, err := time.ParseDuration(raw)
|
||||||
|
if err != nil {
|
||||||
|
hdlr.log.Error(
|
||||||
|
"invalid SESSION_IDLE_TIMEOUT, using default",
|
||||||
|
"value", raw, "error", err,
|
||||||
|
)
|
||||||
|
|
||||||
|
return defaultIdleTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
return dur
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hdlr *Handlers) startCleanup(ctx context.Context) {
|
||||||
|
cleanupCtx, cancel := context.WithCancel(ctx)
|
||||||
|
hdlr.cancelCleanup = cancel
|
||||||
|
|
||||||
|
go hdlr.cleanupLoop(cleanupCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hdlr *Handlers) stopCleanup() {
|
||||||
|
if hdlr.cancelCleanup != nil {
|
||||||
|
hdlr.cancelCleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hdlr *Handlers) cleanupLoop(ctx context.Context) {
|
||||||
|
timeout := hdlr.idleTimeout()
|
||||||
|
|
||||||
|
interval := max(timeout/2, time.Minute) //nolint:mnd // half the timeout
|
||||||
|
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
hdlr.runCleanup(ctx, timeout)
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hdlr *Handlers) runCleanup(
|
||||||
|
ctx context.Context,
|
||||||
|
timeout time.Duration,
|
||||||
|
) {
|
||||||
|
cutoff := time.Now().Add(-timeout)
|
||||||
|
|
||||||
|
deleted, err := hdlr.params.Database.DeleteStaleSessions(
|
||||||
|
ctx, cutoff,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
hdlr.log.Error(
|
||||||
|
"session cleanup failed", "error", err,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if deleted > 0 {
|
||||||
|
hdlr.log.Info(
|
||||||
|
"cleaned up stale clients",
|
||||||
|
"deleted", deleted,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user