Skip to content

build: upgrade speakeasy/openapi to v1.24.1 for resolver fixes - #394

Merged
OmarAlJarrah merged 1 commit into
mainfrom
build/speakeasy-openapi-v1.24.1
Aug 11, 2026
Merged

build: upgrade speakeasy/openapi to v1.24.1 for resolver fixes#394
OmarAlJarrah merged 1 commit into
mainfrom
build/speakeasy-openapi-v1.24.1

Conversation

@OmarAlJarrah

Copy link
Copy Markdown
Member

Summary

v1.24.0 carries three defects in $ref resolution. All three were reported and fixed upstream — speakeasy-api/openapi#231, speakeasy-api/openapi#230 — and shipped in v1.24.1:

  • Resolution self-deadlock. Reference.resolve held the reference's own cacheMutex write lock across the call to references.Resolve. That call navigates the document, and navigating into a reference takes a read lock on it. sync.RWMutex is not reentrant, so a $ref whose pointer passes through the reference being resolved blocked forever on a lock its own goroutine held. No concurrency involved.
  • Stack overflow in GetObject. When a resolution chain closed a loop, the references were left holding caches pointing back into it, and GetObject recursed through them until the goroutine stack was gone.
  • Cyclic parent links. resolveObjectWithTracking set the parent links on the hop that closes a cycle, so a caller walking GetParent/GetTopLevelParent looped forever.

This bumps the pin to v1.24.1. The transitive golang.org/x/sync and golang.org/x/text upgrades are required by v1.24.1's own go.mod rather than picked up opportunistically.

What this does and does not change for the compiler

Every refusal in compilers/openapi/internal/scan stays, for two different reasons:

  • Its schema-side refusal remains load-bearing. v1.24.1 does not address the jsonschema/oas3 resolver, which still aborts the process with fatal error: stack overflow on 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.
  • Its outside-schema deadlock refusals become redundant but are not wrong. Those shapes now resolve with an unresolved reference or circular reference detected error instead of hanging, and TestResolverOracle_RefusalMatchesResolverBehavior binds 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 — gofmt clean, go vet clean, golangci-lint run 0 issues, go build ./... clean, ./scripts/check-coverage.sh at 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:

Reproducer v1.24.0 v1.24.1
pointer prefix names its own reference hung resolves, unresolved reference
reached via the cache delegation hung resolves, unresolved reference
components spelling of the same shape hung resolves, unresolved reference
trimmed-pointer self-reference stack overflow circular reference detected: …
two-reference cycle stack overflow circular reference detected: …
schema alias cycle stack overflow stack overflow (unchanged, see above)
bare self-alias schema stack overflow stack overflow (unchanged, see above)
control: healthy path items clean, object non-nil clean, object non-nil
control: legitimate circular reference reported reported
control: ordinary recursive schema clean clean

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_RefusalMatchesResolverBehavior runs all 35 shapes and passes on both versions, but takes 10.58s on v1.24.0 against 0.87s on v1.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 testdata produces 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.

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.
@OmarAlJarrah
OmarAlJarrah merged commit b9c79ce into main Aug 11, 2026
1 check passed
@OmarAlJarrah
OmarAlJarrah deleted the build/speakeasy-openapi-v1.24.1 branch August 11, 2026 10:14
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