# SessionPool _(no submodules)_ ## Purpose `SessionPool` is a session-specific wrapper around `ObjectPool` 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`. - 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` — 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.