- 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)
55 lines
2.1 KiB
Go
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)
|
|
}
|