Skip to content

Fix the Oracle queue Uri identity round-trip, and finish GH-3808 (GH-3820) - #3823

Merged
jeremydmiller merged 1 commit into
mainfrom
gh-3820/oracle-queue-uri-casing
Aug 4, 2026
Merged

Fix the Oracle queue Uri identity round-trip, and finish GH-3808 (GH-3820)#3823
jeremydmiller merged 1 commit into
mainfrom
gh-3820/oracle-queue-uri-casing

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #3820.

The handoff called the casing mismatch under #3808 "the real bug to chase." It is — and it is a
product bug, not a test quirk. It reproduces in 43ms with no Oracle container.

The defect

OracleTransport.findEndpointByUri looked its queue up with a bare Queues[uri.Host]:

protected override OracleQueue findEndpointByUri(Uri uri)
{
    var queueName = uri.Host;
    return Queues[queueName];
}

That is correct for the two transports it was copied from — and wrong for this one:

Transport SanitizeIdentifier System.Uri authority Round-trips?
Postgres ToLowerInvariant lower-cased
SQL Server ToLowerInvariant lower-cased
Oracle ToUpperInvariant lower-cased

System.Uri normalises the authority to lower case, so an upper-cased Oracle queue name never came
back out of its own Uri. Queues is a LightweightCache, so the miss didn't throw — it silently
created a second OracleQueue over the same physical queue tables.

This is reachable from ordinary user code: ToOracleQueue("name") ends with
publishing.To(queue.Uri), which round-trips the endpoint through its own Uri. Oracle is the only
transport in the repo whose SanitizeIdentifier upper-cases, so it is the only one affected —
checked across every SanitizeIdentifier override.

The fix

Queues[SanitizeIdentifier(uri.Host)]. SanitizeIdentifier rather than MaybeCorrectName
deliberately: the host segment already carries any IdentifierPrefix, which MaybeCorrectName would
prepend a second time. The correction is idempotent, so a Uri built from an already-corrected name is
unaffected.

Two new tests, no database required, both red on main:

transport.GetOrCreateEndpoint(queue.Uri) should be same as
  OracleQueue (22550079) but was OracleQueue (55400036)

The other two uri.Host reads in the Oracle transport are Guid.Parse on control-endpoint node ids,
which is case-insensitive — not affected.

What that unlocks

clear_all_wolverine_storage was the sole provider in ClearAllWolverineStorageCompliance attaching
a listener, against the base class's explicit instruction, purely to work around the duplicate
endpoint. It can now go subscriber-only like Postgres, SQL Server, MySql and Sqlite.

That also removes the cause of the ORA-00054 the handoff flagged. The listener was the only thing
holding a TM/DML lock on the queue table; #3808 made that lock reachable by pointing the DROP at a
schema whose tables actually exist. So the contention is gone rather than retried around.

The unreachable handler (my defect from #3808)

catch (OracleException e) when (e.Number == 54 && attempt < maxAttempts)

On the final attempt the guard is false, the raw OracleException escapes the loop, and the
descriptive InvalidOperationException below it is dead code — it never ran once, which is exactly
what the CI error demonstrated. ORA-54 is now caught unconditionally, with the last one carried out as
the inner exception so the descriptive throw finally names the underlying Oracle error too.

The retry itself stays as a belt-and-braces guard — a host from a prior class in the collection can
still be tearing connections down — and the doc comment no longer claims a listener this class no
longer has.

Verification

  • Full OracleTests suite: 115 passed, 0 failed (against a live gvenzl/oracle-free:23-slim)
  • clear_all_wolverine_storage + the new tests: 7 passed in 6s, no retry (was 1 retry/run)
  • New tests confirmed red before the fix, green after
  • Full wolverine.slnx Release build clean, 0 warnings

🤖 Generated with Claude Code

https://claude.ai/code/session_01WHAuhdWS3XeAk16swV9G8m

…3820)

OracleTransport.findEndpointByUri looked its queue up with a bare
Queues[uri.Host]. That is correct for the Postgres and SQL Server transports
it was copied from, whose SanitizeIdentifier lower-cases -- matching how
System.Uri normalises the authority. Oracle's SanitizeIdentifier
upper-cases, so uri.Host never matched the key the queue was registered
under and LightweightCache quietly minted a SECOND OracleQueue over the same
physical queue tables. Oracle is the only transport in the repo that
upper-cases identifiers, so it is the only one affected.

This is a product bug, not a test quirk: any ToOracleQueue("name")
subscription resolves through this path, because the publishing.To(queue.Uri)
inside it round-trips the endpoint through its own Uri.

Fixed by correcting the name on the way back in. SanitizeIdentifier rather
than MaybeCorrectName deliberately -- the host segment already carries any
IdentifierPrefix, which MaybeCorrectName would prepend a second time. The
correction is idempotent, so a Uri built from an already-corrected name is
unaffected. Covered by two tests that need no database and fail on main.

With that fixed, the clear_all_wolverine_storage compliance class no longer
needs the listener it only ever attached to work around the duplicate
endpoint, so it goes subscriber-only like Postgres, SQL Server, MySql and
Sqlite. The listener was also the only thing holding a TM/DML lock on the
queue table, which is what made the ORA-00054 in beforeHostAsync reachable
once GH-3808 pointed the DROP at a schema whose tables actually exist.

Also fixes the unreachable failure handler introduced in GH-3808: the
`when (e.Number == 54 && attempt < maxAttempts)` guard was false on the final
attempt, so the raw OracleException escaped the loop and the descriptive
InvalidOperationException below it was dead code that never ran once. ORA-54
is now caught unconditionally and the last one is carried out as the inner
exception.

Full OracleTests suite: 115 passed, 0 failed. The clear_all class now runs 7
tests in 6s with no retry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHAuhdWS3XeAk16swV9G8m
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.

clear_all_wolverine_storage on Oracle: unreachable failure handler, and the listener that makes ORA-00054 reachable at all

1 participant