Handoff: relay announcement loss remains 100% reproducible after isolating ACK-admission starvation
Continue the second session of the UrlResolver relay announcement-loss investigation to one of two honest outcomes:
Continue the second session of the UrlResolver relay announcement-loss investigation to one of two honest outcomes:
Moves it out of every priority list and into ArchiveArea.
Written 2026-07-17 UTC.
RE-VERIFY: Everything below is a write-time snapshot. Before acting, re-run
gh pr view 369 --repo CodexCoder21Organization/UrlProtocol --json state,mergeStateStatus,statusCheckRollup,mergedAt
and gh pr view 788 --repo CodexCoder21Organization/UrlResolver --json state,mergeStateStatus,statusCheckRollup,mergedAt.
For investigation branches, fetch the named remote branch and verify its head SHA. Fresh GitHub/build
state always wins over this document.
Continue the second session of the UrlResolver relay announcement-loss investigation to one of two honest outcomes:
The fixed reproducer is
UrlResolver/tests/stressTestBootstrapServiceResyncDoesNotGiveUpUnderSaturation.kts: 52 providers,
8 CPU spinners, 128 MiB heap per child, four child JVMs concurrently pinned to CPUs 0-1. It must not
be weakened. Baseline is 4/4 failures (100%) on both protocol 0.0.360/snapshot-14-effective and
protocol 0.0.367/snapshot-18.
This session reached outcome 2. It isolated and deterministically reproduced a real relay registration ACK-starvation sub-mechanism, but the bounded upstream correction still failed the unchanged consumer amplifier 4/4. It was therefore not published or offered as a fix.
Re-established the prior evidence. The full ongoing record is committed at
UrlResolver investigation-log.md.
Snapshot-18 eliminates the Negotiator.ResponderHandler close exception but not loss. Prior
executor isolation, bounded PEX, mux preservation, targeted-gossip removal, and acknowledged
relay-registration refresh experiments all remained below verification.
Traced the relay registration transaction end to end. Temporary generation-tagged diagnostics recorded registration-map apply, ACK admission, controller activation, peer-registry apply, stream cleanup, keepalive overwrite, expiry, and the UrlResolver callback. Two unchanged traced batches failed 4/4 each:
No run contained a stale stream cleanup removing a newer registration or a stale keepalive
overwrite. Missing providers either never reached relay apply, or reached registeredPeers.put
but never reached ACK/peer-record apply.
Proved one exact rollback mechanism. A repeat with explicit ack-failed tracing failed with
51/52 learned. The only missing service, creg.provider.service.50, was applied twice; both attempts
failed before ACK and peer-record apply. All 16 ACK failures in that run were:
foundation.url.protocol.RelayException: Relay message was not admitted within the 200ms budget:
stream/connection was closed or remained unwritable, or the per-connection parked-write budget was exhausted
Verified mechanism sentence: ordinary writes on a provider's multiplexed connection can occupy all 16 shared parked-write permits, so the relay applies a registration but cannot admit its small ACK commit record and correctly rolls the unacknowledged registration back; repeated attempts can leave that provider absent from the relay peer record.
Built a deterministic upstream RED guard. The new real-libp2p test
testRelayControlFrameIsNotStarvedByOrdinaryParkedWrites parks exactly 16 ordinary writes on one
physical connection, submits a registration-ACK-shaped control frame, holds the stall beyond the
normal 200 ms admission window, then restores writability. Before the behavioral change it failed
exactly at the intended assertion:
The control frame must remain admitted in its reserved bounded lane...
Expected <1>, actual <0>.
Implemented and locally verified the bounded sub-fix. The experiment added
RelayMessageSender.sendControlMessage, one separate control permit per root connection, and routed
registration ACKs through that lane using the existing bounded delivery wait. Worst-case parked
writes rose from 16 to 17 per physical connection; it did not introduce an unbounded queue. The
deterministic upstream test passed after the change.
Falsified it as the complete announcement-loss fix. Using the locally built, unpublished
foundation.url:protocol:0.0.370, the exact unchanged consumer amplifier again failed 4/4:
| Run | Learned | Missing | |---|---:|---:| | 1 | 42/52 | 10 | | 2 | 41/52 | 11 | | 3 | 38/52 | 14 | | 4 | 40/52 | 12 |
Local logs are /tmp/creg-control-fix-batch-1/run-{1..4}.log on the investigation host. The result
means ACK starvation is real but not sufficient: many requests still never reach relay apply, or
the confirmed PEX recovery path still cannot complete. Do not publish 0.0.370 or present this
experiment as the announcement-loss fix without new evidence and a passing seven-run batch.
Observed a separate withdrawal-integrity defect, not yet causally generalized. One prior traced
run fully ACKed and added creg.provider.service.46, then replaced its services with [] after a
withdrawal before the assertion. Sender-tagged follow-up traces proved the relay accepts a
ServiceWithdrawal.providerPeerId different from the registered stream sender. However, that
follow-up had zero pre-assertion withdrawals, so third-party/synthetic withdrawal remains a real
protocol-integrity hypothesis rather than the general mechanism behind the controlled 4/4 loss.
Recorded a tooling challenge. Kompile selected-test builds reused a stale same-coordinate Maven
artifact despite dirty source/build script, hiding sendControlMessage until the repository cache
was moved aside. Exact evidence and workaround:
stale same-version artifact challenge.
| Item | Write-time state (2026-07-17 UTC) |
|---|---|
| UrlProtocol refresh PR | OPEN, DIRTY; all three checks succeeded, but main advanced with protocol 0.0.368 and the branch now conflicts in build.kts. Head still 7e316748997718e13486406f7e4721c071cd624c. This API was published as 0.0.368/0.0.369 but did not fix the stress reproducer. |
| Blocked UrlResolver PR | OPEN, BEHIND; GitHub build and kotlin.build remote checks both failed. Head 0f633dde336d8dd2193f901841e68c5c9bfc1771. It is not the D2 experiment but must remain visible to the next investigator. |
| UrlResolver D2 evidence branch | Pushed, no PR. Head e1e34f45; contains the complete investigation-log.md plus temporary resolver callback traces. Checkout: /code/workspace/urlresolver-announce-loss. |
| UrlProtocol D2 experiment branch | Pushed, no PR. Head edda455; contains server diagnostics, the fail-first control-lane test, and the bounded but consumer-falsified experiment. Checkout: /code/workspace/urlresolver-announce-loss-urlprotocol. |
| foundation.url:protocol:0.0.369 | Published refresh API artifact from the prior session. |
| foundation.url:protocol:0.0.370 | Not published. Coordinate exists only on the experiment branch/local cache. |
| Protocol main at rebase time | 2acce11 (Release protocol 0.0.368 (transport admission-failure surfacing)). |
There were no uncommitted source changes at handoff creation: both investigation trees were committed and pushed to their dedicated branches. No code PR was opened for the falsified experiment.
Re-verify current state first with the PR commands in the banner, then fetch both investigation branches and confirm the heads above.
Instrument the client-side relay registration request, not only server ACK:
registerWithRelay start;The important unresolved population is the missing providers whose service-bearing registration never reached server apply. Distinguish request admission rejection, stream negotiation starvation, cancellation, and connection replacement. Do not add retries/timeouts as a fix.
Instrument the confirmed PEX request and response independently at both ends:
PEX is the long-lived convergence mechanism, and the ACK-control experiment left the 4/4 consumer failure rate unchanged.
Reproduce the pre-assertion third-party withdrawal before acting on it. Enable both protocol and
resolver debug, tag the registered stream sender, and stop analysis at [CREG] (exclude teardown).
If senderPeerId != providerPeerId correlates with a missing service before the assertion, write a
real upstream authorization test and fix RelayService.processIncomingMessage at the owning layer.
Keep this as a separate defect unless it explains the main missing set.
Any proposed fix must:
Do not merge any code PR without explicit user approval. The handoff-document PR is the only standing exception and was merged as part of this handoff workflow.
taskset -c 0,1, -Xmx128m, with a 190-second outer guard matching the existing 180-second
test timeout. Do not run four sequentially; shared two-CPU contention is the calibrated reproducer.scripts/test.bash --test <name> still compiles every test script before selecting one; expect several
minutes and very large warning output./tmp/urlprotocol-cache-backup.IAlfa2/repository-cache and
/tmp/urlprotocol-cache-pre-green.Fz4h00/repository-cache. Moving the exact repository-specific cache
aside (rather than deleting it) forced a source-current build./tmp/creg-trace-batch-2/run-{1..4}.log/tmp/creg-trace-batch-3/run-{1..4}.log/tmp/creg-withdraw-sender.log/tmp/creg-ack-failure.log/tmp/creg-control-fix-batch-1/run-{1..4}.logorigin/main. Before updating an existing PR branch, first
verify that its PR is still open. The refresh branch currently needs a conflict resolution because
main now owns the 0.0.368 release entry.