TypeSchema: terminate package dependency walk on cyclic deps - #174
Merged
Conversation
mkPackageAwareResolver memoized the package index only after recursing into dependencies, so mutually-dependent packages (e.g. hl7.terminology.r5 <-> hl7.fhir.uv.extensions.r5) recursed forever until OOM. Memoize before recursing so the cycle terminates at the guard.
Cap manager.search() calls and assert each package is scanned exactly once, so reintroducing the bug fails red immediately instead of slowly exhausting memory.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
mkPackageAwareResolver(insrc/typeschema/register.ts) walks each package's dependency tree recursively, memoizing the built index intoacc[pkgId]only after recursing into dependencies. With mutually-dependent FHIR packages — e.g.hl7.terminology.r5↔hl7.fhir.uv.extensions.r5(pulled in transitively by the SQL-on-FHIR R5 closure) — the early-return guard (if (acc[pkgId]) return acc[pkgId];) never fires during the cycle, so resolution recurses forever, growing memory until the process is OOM-killed.Fix
acc[pkgId] = index;before recursing into dependencies, so a cyclic dependency terminates at the guard instead of recursing forever.Memory consumption
Measured with
/usr/bin/time -l bun test test/api/write-generator/multi-package/sql-on-fhir.test.ts(the multi-package test whose R5 closure contains the cyclic packages):The dependency walk now terminates and the resolver is built in bounded memory. (The SQL-on-FHIR test still reports assertion failures, but on a pre-existing, unrelated issue —
Base resource not found 'http://hl7.org/fhir/StructureDefinition/Library'in theorg.sql-on-fhir.ig#2.1.0-preclosure — which reproduces identically on published releases and is out of scope here.)Test
registerFromManagerwith a mock manager whose packages form ana ↔ bcycle. A cap onmanager.search()calls plus an exact "each package scanned once" assertion make a reintroduced bug fail fast and red instead of slowly exhausting memory. Verified: passes with the fix, fails immediately without it.