Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
# SessionPool
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`SessionPool` is a session-specific wrapper around `ObjectPool<T>` that
recycles `PooledSession` records — plain-data session containers
(`id`, `type`, `metadata`, `children[]`) — between game-loop frames.
The wrapper presents a "fresh session per call" API
(`acquireSession(data) → PooledSession`) while internally amortising
the underlying allocation across borrows, which keeps GC pressure low
in workloads that churn through thousands of short-lived game sessions
per minute (matchmaking server, tournament orchestrator, tutorial sim).
The pool is universal. It owns no logic beyond what `ObjectPool`
already provides; the wrapper exists so the factory + reset semantics
stay close to the session shape and so callers can inject a `Clock` for
deterministic placeholder timestamps without poking at the underlying
pool's `factory`.
## Features
- Typed wrapper over `ObjectPool<PooledSession>`.
- Configurable `initialSize` (default 20), `maxSize` (default 100), and
`preAllocate` toggle.
- Injectable `Clock` for deterministic placeholder timestamps.
- `acquireSession(data)`, `releaseSession(session)`, and auto-release
`withSession(data, callback)`.
- `getStats()` forwards `ObjectPool`'s `PoolStats` for dashboard reuse.
- Forwarded lifecycle: `clear()`, `dispose()`, `disposed` getter.
- Default session type configurable via `defaultSessionType`.
## Usage: Basic
```typescript
import { SessionPool } from "@suaveplan/game-server/performance/session-pool";
const pool = new SessionPool();
const session = pool.acquireSession({
id: "match-42",
type: "game",
metadata: { createdAt: Date.now(), updatedAt: Date.now() },
});
session.children.push("player-alice", "player-bob");
pool.releaseSession(session);
```
## Usage: Advanced
```typescript
import { SessionPool } from "@suaveplan/game-server/performance/session-pool";
import { metrics } from "@opentelemetry/api";
const pool = new SessionPool(
{
initialSize: 100,
maxSize: 2_000,
defaultSessionType: "queue",
},
{
clock: () => Date.now(),
meter: metrics.getMeter("game-server.sessions"),
},
);
// Hot loop: every match-found event borrows a session, populates it,
// hands it to a downstream coordinator, and releases it on cleanup.
await pool.withSession(
{
id: `match-${crypto.randomUUID()}`,
type: "game",
metadata: {
createdAt: Date.now(),
updatedAt: Date.now(),
queueKey: "ranked",
},
},
async (session) => {
for (const playerId of matchedPlayers) {
session.children.push(playerId);
}
await coordinator.start(session);
},
);
```
## API Reference
- `new SessionPool(config?: SessionPoolConfig, deps?: SessionPoolDeps)`.
- `acquireSession(data: PooledSessionData): PooledSession` — stamps the
borrowed record with caller fields, clears `children`, returns.
- `releaseSession(session: PooledSession): void` — forwards to
`ObjectPool.release`; throws `RELEASE_FOREIGN_OBJECT` on a foreign
object, `POOL_DISPOSED` after dispose.
- `withSession(data, callback): Promise<R>` — borrows + finally-releases.
- `getStats(): PoolStats`.
- `clear()` / `dispose()` / `disposed: boolean`.
## Implementation Notes
`acquireSession` always overwrites the record's identity and metadata
fields before returning it, so callers never observe stale placeholder
data left over from a previous borrow. The `children` array is
emptied via `length = 0` rather than reassigned to a new array, which
keeps the underlying buffer (and its hidden-class shape) alive — a
small but measurable win in V8 hot loops. The factory's placeholder id
counter increments monotonically across both factory + reset paths so
production logs that capture a borrowed session before the caller
overwrote its id can be debugged unambiguously. The wrapper does not
re-export `ObjectPool`'s underlying counters — instead the OTel meter
is forwarded into the underlying pool so the same `pool.acquire_total`
/ `pool.release_total` instruments carry the session pool's
contribution. Dashboards can disambiguate by attaching pool-name
attributes on a thin wrapper if needed.