build: upgrade speakeasy/openapi to v1.24.1 for resolver fixes - #394
Merged
Conversation
v1.24.0 carries three defects in $ref resolution, all reported and fixed upstream (speakeasy-api/openapi#231, speakeasy-api/openapi#230) and released in v1.24.1: - Reference.resolve held the reference's own cacheMutex write lock across the pointer walk, so a $ref whose pointer passes through the reference being resolved self-deadlocked on a non-reentrant RWMutex. - GetObject recursed through a cyclic resolution cache, exhausting the goroutine stack. - Parent links were set on the hop that closes a cycle, so walking GetParent looped forever. The transitive golang.org/x/sync and golang.org/x/text bumps are required by v1.24.1's own go.mod, not opportunistic. The scan package's refusals all stay. Its schema-side refusal is still load-bearing: the jsonschema/oas3 resolver still aborts the process on an alias cycle, which v1.24.1 does not address. The outside-schema deadlock refusals become redundant but not wrong -- those shapes now resolve with an error rather than hanging, which the resolver oracle deliberately treats as binding neither of its two directions, so nothing in the model needs to change.
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.
Summary
v1.24.0carries three defects in$refresolution. All three were reported and fixed upstream — speakeasy-api/openapi#231, speakeasy-api/openapi#230 — and shipped in v1.24.1:Reference.resolveheld the reference's owncacheMutexwrite lock across the call toreferences.Resolve. That call navigates the document, and navigating into a reference takes a read lock on it.sync.RWMutexis not reentrant, so a$refwhose pointer passes through the reference being resolved blocked forever on a lock its own goroutine held. No concurrency involved.GetObject. When a resolution chain closed a loop, the references were left holding caches pointing back into it, andGetObjectrecursed through them until the goroutine stack was gone.resolveObjectWithTrackingset the parent links on the hop that closes a cycle, so a caller walkingGetParent/GetTopLevelParentlooped forever.This bumps the pin to
v1.24.1. The transitivegolang.org/x/syncandgolang.org/x/textupgrades are required byv1.24.1's owngo.modrather than picked up opportunistically.What this does and does not change for the compiler
Every refusal in
compilers/openapi/internal/scanstays, for two different reasons:v1.24.1does not address thejsonschema/oas3resolver, which still aborts the process withfatal error: stack overflowon an alias cycle (A: {$ref: B}, B: {$ref: A}, or a bare self-alias). This is the follow-up called out in the upstream PR and it is still open.unresolved referenceorcircular reference detectederror instead of hanging, andTestResolverOracle_RefusalMatchesResolverBehaviorbinds only two ways — a shape the resolver cannot survive must be refused, and one it resolves cleanly must not be. A shape that survives but reports an error binds neither, which is where these now land. So the oracle stays green and the refusal model needs no change.Trimming the now-redundant refusals is deliberately left out of scope: it would be a behavior change to the compiler's diagnostics, not a dependency bump, and it should be judged on its own.
Test plan
Full gate green on the final tree —
gofmtclean,go vetclean,golangci-lint run0 issues,go build ./...clean,./scripts/check-coverage.shat 5823/5823 statements.Because a green suite cannot by itself show that the upgrade reached anything, the behavior change was measured directly. Each reproducer ran as its own subprocess, since a deadlock and a stack overflow cannot be observed from inside the process they happen to:
unresolved referenceunresolved referenceunresolved referencestack overflowcircular reference detected: …stack overflowcircular reference detected: …stack overflowstack overflow(unchanged, see above)stack overflowstack overflow(unchanged, see above)The controls matter as much as the failures: they show a healthy document still resolves to a real object and a legitimate cycle is still reported rather than crashed on.
Two further checks that the upgrade is reached and inert where it should be:
TestResolverOracle_RefusalMatchesResolverBehaviorruns all 35 shapes and passes on both versions, but takes 10.58s onv1.24.0against 0.87s onv1.24.1— the difference is the hanging shapes no longer burning the 10s probe timeout, which is direct evidence the oracle reaches the changed behavior rather than passing by not touching it.go run ./cmd/morphic-harness testdataproduces byte-identical results across all 121 corpus specs on both versions. That comparison was itself validated by planting a change in one line and confirming the diff catches it.