Files
summercms/modules/pier/store.go
Jakub Zych 1111828e8a fix(pier): an ended session is never written back by a request still in flight
- persisting a session's protocol state after a request now rewrites an
  existing record only (Redis SET XX, the memory store likewise) and closes
  the local copy when the record is gone
- before, a request that finished after DELETE, a revoked or deleted subject
  event or the idle expiry put the record back, and the next request
  rehydrated the ended session
- found by plan 19.7-05's TestFanOut (revocation and deletion cases, about
  one run in a hundred)
2026-10-23 13:55:04 +02:00

55 lines
2.1 KiB
Go

package pier
import (
"context"
"encoding/json"
"time"
)
// Record is one stored session: the binding key of the identity that opened
// it, the JSON of the SDK's ServerSessionState once initialised (nil until
// then) and the creation time.
type Record struct {
Binding string `json:"binding"`
State json.RawMessage `json:"state,omitempty"`
Created time.Time `json:"created"`
}
// Event is what instances publish to each other: a session ended by DELETE
// (EventEnded, with SessionID) or a subject change (the pact
// MCPSubjectChanged kinds, with Guard and UserID). Instance names the
// publisher so it can skip its own echo when it has already acted.
type Event struct {
Kind string `json:"kind"`
Guard string `json:"guard,omitempty"`
UserID uint `json:"user_id,omitempty"`
SessionID string `json:"session_id,omitempty"`
Instance string `json:"instance,omitempty"`
}
// EventEnded is the kind of a session-ended event.
const EventEnded = "ended"
// Store is the session registry and the event channel shared by every
// instance of the application: memory for one instance (and tests), Redis
// for several. Put writes a record with a TTL, Touch slides it, Count
// answers the live sessions of one binding, Publish and Subscribe carry
// Events.
type Store interface {
Put(ctx context.Context, id string, rec Record, ttl time.Duration) error
Get(ctx context.Context, id string) (Record, bool, error)
Touch(ctx context.Context, id string, ttl time.Duration) error
Delete(ctx context.Context, id string) error
Count(ctx context.Context, binding string) (int, error)
Publish(ctx context.Context, ev Event) error
Subscribe(ctx context.Context, fn func(Event)) error
}
// updater is implemented by the stores pier ships: Update rewrites an
// existing record and never creates one (Redis SET XX), answering false when
// the record is gone, so a request that finishes after its session ended
// (DELETE, revocation, deletion, expiry) cannot write the record back.
type updater interface {
Update(ctx context.Context, id string, rec Record, ttl time.Duration) (bool, error)
}