- an unexported reserver (Reserve, Release) next to updater: the memory store reserves under its mutex, the Redis store in one Lua script (prune, SCARD, SET PX plus SADD); serveNew reserves instead of Count then Put and releases on a failed connect or initialize (A-WR-03) - serveNew, lookup's rehydration and the stateless getServer build the SDK server with no router-wide lock held and insert or adopt under the lock afterwards, so a slow guard or database check no longer stalls the instance (A-WR-07)
66 lines
2.7 KiB
Go
66 lines
2.7 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)
|
|
}
|
|
|
|
// reserver is implemented by the stores pier ships: Reserve is the atomic
|
|
// form of Count then Put (A-WR-03). It writes rec for id with the TTL and
|
|
// adds id to the binding's sessions only when the binding holds fewer than
|
|
// max live sessions, answering false (nothing written) at the cap, so
|
|
// concurrent initializes of one binding cannot exceed it. Release drops a
|
|
// reservation whose session never opened (a failed connect or initialize).
|
|
type reserver interface {
|
|
Reserve(ctx context.Context, binding, id string, rec Record, max int, ttl time.Duration) (bool, error)
|
|
Release(ctx context.Context, binding, id string) error
|
|
}
|