Skip to content

fix(graphile-cache): track generation disposal and release preset services - #1882

Draft
Zetazzz wants to merge 1 commit into
fix/graphile-readiness-disposalfrom
fix/graphile-generation-disposal
Draft

Zetazzz wants to merge 1 commit into
fix/graphile-readiness-disposalfrom
fix/graphile-generation-disposal

Conversation

@Zetazzz

@Zetazzz Zetazzz commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Cache keys can be reused while an older Graphile instance is still releasing. Cleanup now follows the exact GraphileCacheEntry object, so replacing an entry releases both generations and repeated cleanup of one generation returns the same promise, including after failure.

Stacked on #1881 (fix/graphile-readiness-disposal, head 49ae6daf45f8b024d374f8f207ace67c0fe9fa81). This PR contains only the disposal follow-up. The source reference is #1747 at 89f456161e6c5558bd1fd41db315d27f02ab79a1; that PR and its downstream stack are unchanged.

Changes

  • Route LRU disposal and disposeUncachedEntry through identity-based scheduleDisposal. Register the promise before resource callbacks, including synchronous reentrant calls.
  • Snapshot and deduplicate each resolved preset's services in utils/preset-services.ts. Release them once in reverse order, await their public return values, continue after individual errors, and preserve single errors or aggregate multiple errors.
  • Release the entry's own HTTP server, realtime manager, PostGraphile/Grafserv, then preset services. Attempt every resource even when an earlier release throws or rejects.
  • Export clearGraphileCache, waitForActiveDisposals, waitForEntryDisposal, and disposeUncachedEntry. A bulk drain snapshots generations scheduled before the call, retains settled failures until that snapshot is acknowledged, and lets concurrent drains each observe their captured failures. Later generations belong to the next drain. Exact-entry promises retain their outcome after acknowledgement; an unscheduled entry still returns a resolved Promise<void>.
  • closeAllCaches shares concurrent work and attempts pg-cache pool cleanup even if instance cleanup fails, preserving errors from both phases.
  • Attach the service releaser to successful instances and reuse it for failed builds. Put Express/HTTP construction inside the readiness-controlled attachment step so synchronous setup failures are cleaned up. Keep fix(graphile-cache): await schema and server readiness #1881's functional schemaResult, sequential awaits, deferred createServ, void contract, and utils layout.
  • When realtime is requested, missing prerequisites or startup failure now reject creation and dispose the unpublished generation. Assign its manager before start() so partial startup is stopped.
  • Add focused regression suites and move graphile-cache to the PostgreSQL CI batch. Use ts-jest's isolated NodeNext transformation with separate CJS/ESM type checks.

Ownership and limits

Each factory invocation owns its own HTTP server and fresh preset service objects; it never receives or closes the shared main HTTP server. Preset service release honors the upstream ownership contract: externally supplied pools and subscribers remain caller-owned. Only closeAllCaches separately asks pg-cache to close its managed pools.

Disposal completion means the public release calls have settled. It does not guarantee that upstream asynchronous UNLISTEN/client return, listener cleanup, or watch/background work has fully drained. PostGraphile/Grafserv may handle some release errors internally, and pg-cache currently settles its own pool-disposal errors internally; this PR preserves errors those public APIs expose. It does not repair the watch boundaries documented in #1881, patch dependencies, change realtime subscription internals, or import the old performance harness.

Validation

CNC, Node 22.23.3 / pnpm 10.34.5 / PostgreSQL 18.6:

  • pnpm --filter graphile-cache test --runInBand: 8 suites / 78 tests passed. 74 are database-free, including the inherited nine strict Node subprocess cases using real locked packages. Four use real PostgreSQL through pgsql-test.
  • The PostgreSQL tests cover subscribed release and eventual LISTEN/client return, caller-owned pools/subscribers, same-key subscribed generations with listening per-entry servers while a separate main server stays open, and failed schema cleanup with a still-usable external pool. These are functional checks, not load/stress measurements.
  • Server middleware regression: graphile-single-flight.test.ts and graphile-introspection.test.ts: 2 suites / 11 tests passed.
  • graphile-cache CJS/ESM builds, both tsc --noEmit checks, package ESLint (only the existing unused unregister warning), git diff --check, frozen offline dependency installation, supply-chain policy, and CI test-registration check passed.
  • The initial database setup exceeded Jest's default hook timeout. The final suite uses a 30s PostgreSQL timeout and isolated transformation; the abandoned temporary database was cleaned through pgsql-test. No task test databases remain.

GitHub checks at the published head

At 1fc1d5442d41289ffbf6b45f1388143315994628, no GitHub Actions build/test workflow was triggered: the repository's PR workflow targets only main and v1, while this stacked PR targets fix/graphile-readiness-disposal. Socket Security Project Report completed successfully. Socket PR Alerts also reports a successful check conclusion, but its report is explicitly skipped because it found no net dependency changes. These checks do not constitute a build/test CI pass. This PR remains a Draft.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant