@@ -1402,3 +1402,210 @@ device-to-device drops from the ~118 ms WAN relay to the ~8 ms direct LAN path
14021402 to the Android socket-protect blocker, which is now precisely isolated with
14031403 a concrete fix design.
14041404- Synthetic RFC-2544 speed server for repeatable in-tunnel measurement.
1405+
1406+ ## 18. Final transport-stall and SCTP release pass (2026-07-28)
1407+
1408+ ### 18.1 A canceled platform transport could retain a blocked write
1409+
1410+ The retained iOS NetworkExtension trace showed a logical client disappearing
1411+ at 11:42:07 while the old connection's TCP batch write did not return until
1412+ 11:42:16. Packet ownership was already removed from the route manager, but H1
1413+ teardown performed this dependency order:
1414+
1415+ ``` text
1416+ remove route → cancel context → join writer → deferred socket close
1417+ ```
1418+
1419+ Context cancellation cannot interrupt a ` net.Conn.Write ` already blocked in
1420+ the kernel. Socket close was the writer's release operation, yet it was
1421+ deferred until after the join waiting for that writer. The old generation and
1422+ its bounded queues could consequently overlap a reconnect for the complete
1423+ write deadline. H3 had the adjacent inversion when a peer stopped reading and
1424+ QUIC stream credit parked ` Stream.Write ` .
1425+
1426+ Both paths now close the connection before joining the writer:
1427+
1428+ ``` text
1429+ remove route → cancel context → close WebSocket/QUIC → join → final drain
1430+ ```
1431+
1432+ ` TestPlatformTransportCloseInterruptsBlockedH1Write ` drives a real WebSocket
1433+ over a wrapper whose ` Write ` can be released only by ` Close ` .
1434+ ` TestPlatformTransportCloseInterruptsBlockedH3Write ` drives a real local QUIC
1435+ connection with fixed 32-KiB stream credit, stops the server reader, fills the
1436+ client's bounded route, and then closes the transport. Both passed ten
1437+ race-enabled repetitions. The asserted upper bound is 500 ms, and H3 normally
1438+ closed in about 45 ms locally. Comparing the physical trace with the test
1439+ bound gives at least an 18× reduction in this teardown tail; it is a lifecycle
1440+ bound, not an end-to-end throughput multiplier.
1441+
1442+ ### 18.2 Callback backpressure no longer owns global sequence maps
1443+
1444+ The send, receive, and forward callbacks remain intentionally synchronous
1445+ backpressure. The defect was the lock domain surrounding that contract:
1446+
1447+ - ` SendBuffer ` held its global sequence-map mutex while closing a sequence.
1448+ Close drains queued ` SendPack ` s and invokes their completion callbacks. A
1449+ parked callback for one sequence therefore blocked unrelated destination
1450+ lookup/creation.
1451+ - ` ReceiveBuffer.Pack ` held its global sequence-map mutex while canceling and
1452+ waiting for an older source generation. That worker may be parked in the
1453+ receive callback, so a same-source ordering wait blocked every other
1454+ concurrent source's map access.
1455+
1456+ Send cleanup now removes exact, wire, and destination indexes atomically,
1457+ unlocks, and closes/drains the sequence afterward. Receive replacement cancels
1458+ under the lock, waits outside it, then conditionally removes only the exact old
1459+ generation and retries. The head is deleted only when it still references
1460+ that generation; a missing map entry heals a stale head rather than panicking.
1461+ Forward cleanup also calls sequence close outside its map lock.
1462+
1463+ The intended pressure remains:
1464+
1465+ - send close does not finish until its completion callback returns;
1466+ - a newer receive generation for the same source cannot pass the old callback;
1467+ - the serial packet receive loop does not dispatch around a blocked data
1468+ callback; and
1469+ - no callback timeout, drop, or asynchronous lifetime escape was introduced.
1470+
1471+ The benefit is isolation of unrelated concurrent sequence bookkeeping, not
1472+ removal of data backpressure. The two top-level regressions park the callback,
1473+ prove the same operation remains blocked, prove an unrelated map operation
1474+ finishes within 250 ms, release the callback, and verify ordered completion.
1475+ They passed 20 normal and 20 race-enabled repetitions alongside the
1476+ wire-indistinguishable send-sequence and receive-rejection tests.
1477+
1478+ ### 18.3 SCTP 1.11.1 closes adjacent idle/resume failure modes
1479+
1480+ The release graph now uses Pion WebRTC 4.2.18, ICE 4.4.0, SCTP 1.11.1,
1481+ interceptor 0.1.47, and RTP 1.10.5. The SCTP patch is relevant to the observed
1482+ failure class:
1483+
1484+ - a write-loop error closes the underlying transport, releasing a read loop
1485+ that could otherwise wait forever after a one-sided failure;
1486+ - cumulative and gap SACK ranges are validated before the transmit
1487+ acknowledgement point or queues are mutated; and
1488+ - pending DATA, partial acknowledgements, late/crossed shutdown, and graceful
1489+ shutdown advance through a consistent drain boundary.
1490+
1491+ The main SDK was initially aligned while its independent ` build ` , ` cgo ` , and
1492+ ` js ` modules still selected the old patches. Those artifact graphs and the
1493+ local Connect/SDK/server/proxy/validator consumers are now aligned. A new SDK
1494+ top-level regression compares all Pion versions in every artifact ` go.mod `
1495+ against the SDK root; it passed 20 normal and 20 race-enabled repetitions.
1496+
1497+ ### 18.4 Congestion sweep confirms the retained knee
1498+
1499+ The opt-in CA-step harness was rerun for 203.7 s after the dependency update.
1500+ Single-run measurements were:
1501+
1502+ | CA setting | 1% loss MiB/s (p50/p95) | 50 Mbps shallow queue | 8 Mbps shallow queue |
1503+ | ---| ---:| ---:| ---:|
1504+ | 4 MTU | 0.54 (75/845 ms) | 3.03 | 0.82 |
1505+ | 6 MTU | 0.58 (75/1100 ms) | 4.10 | 0.75, ~ 1 s tail |
1506+ | ** 8 MTU** | ** 0.60 (52.8/97.6 ms)** | 2.47, one-MTU collapse | ** 0.80** , ~ 110 ms tail |
1507+ | 10 MTU | 0.62 (25.6/99.8 ms) | 3.45 | 0.70, ~ 1 s tail |
1508+ | 12 MTU | 0.66 (75.3/76.5 ms) | 4.00 | 0.71, ~ 1 s tail |
1509+ | 16 MTU | 0.79 (75.7/1070 ms) | 4.45 | 0.73, ~ 1 s tail |
1510+ | 24 MTU | 0.82 (54.1/76.1 ms) | 4.57 | 0.72, 390 ms p50/~ 1 s tail |
1511+ | 8 MTU + 32 KiB floor | 0.79 (25.5/77.5 ms) | 3.26 | 0.82, ~ 110 ms tail |
1512+ | 16 MTU + 32 KiB floor | 0.83 (53.8/76.7 ms) | 3.94 | 0.76, 380 ms p50/~ 1 s tail |
1513+
1514+ This one-shot token/queue phase remains noisy—the 8-MTU 50-Mbps collapse is
1515+ why production selection uses the repeated paired matrices in
1516+ ` OPTIMIZENETWORKPEER1.md §F.1 ` , not the best cell from one run. The new sweep
1517+ confirms the first-principles tradeoff:
1518+
1519+ - larger steps recover faster from exogenous independent loss;
1520+ - the same aggression overshoots real shallow queues and increases RTO pauses;
1521+ - a minimum congestion window improves the loss row by sending despite a real
1522+ congestion signal; and
1523+ - 8 MTU retains the best measured cross-regime knee with stock one-half
1524+ decrease and no forced floor.
1525+
1526+ No SCTP production constant changed. Physical end-to-end steady throughput
1527+ therefore remains 4.8–5.9 MiB/s (about 1.0× the old physical range), while the
1528+ controlled selected-peer receive-window result remains 2.26→28–29 MiB/s
1529+ (about 12.5×). The new wins are bounded teardown, callback-stall locality, and
1530+ upstream association reliability rather than another composable speed
1531+ multiplier.
1532+
1533+ ### 18.5 Validation and remaining physical gate
1534+
1535+ The complete post-change Connect tree passed in 479.471 s, with ` blocker ` ,
1536+ ` connectctl ` , and ` extender ` also green. The SDK passed in 382.483 s; its
1537+ build/C-binding modules, proxy, and local validator suites pass. Static
1538+ analysis passes across Connect, SDK and native artifact modules, server,
1539+ proxy, and the validator. All added regressions are top-level tests and no
1540+ ordinary ` t.Run ` was added.
1541+
1542+ The remaining measurement is the latest-build bidirectional
1543+ iPhone↔Pixel cold multi-origin, DNS/TTFB/load, idle/resume, CPU, and footprint
1544+ matrix. The iPhone is currently unplugged. The last installed iOS build did
1545+ not establish the extension after reinstall/permission state changed, so no
1546+ latest-build physical throughput result is inferred from older successful
1547+ captures.
1548+
1549+ ### 18.6 Network-contract rollover is now relationship-scoped and bounded
1550+
1551+ A long real-page sequence found that the remaining apparent successor pause
1552+ was not SCTP. Contract creation prefetches the next contract, but the global
1553+ janitor expired any queued result after 120 s. A low-rate sequence could keep
1554+ its current contract open longer than that, then block on a new control round
1555+ trip at the eventual boundary.
1556+
1557+ The initial sizing change also exposed an identity-domain mistake. Marking the
1558+ selected top-level peer ID did not mark provider return traffic, whose
1559+ destination is an ephemeral per-window client ID. The provider continued to
1560+ receive only 13,107 usable bytes from a nominal 16 KiB contract.
1561+
1562+ The retained implementation carries an authenticated, local-only
1563+ ` NetworkPeer ` policy bit:
1564+
1565+ ``` text
1566+ selected multi-client default ─┐
1567+ ├→ SendSequence → ContractKey
1568+ ProvideMode_Network return ────┘
1569+ ├→ 1 MiB first contract
1570+ encrypted-control carrier ────────┘ + live-successor retention
1571+ ```
1572+
1573+ The bit is independent of ` ForceStream ` : public direct streams can force a
1574+ stream without receiving no-escrow Network sizing. It is also absent from
1575+ send-sequence/wire identity, preventing a local sizing policy from forking two
1576+ receiver-indistinguishable sequences. Encryption-control first flights mirror
1577+ the policy. Non-Network companion returns clear both Network policy and
1578+ ` ForceStream ` .
1579+
1580+ An open Network contract is now the ownership lease for its prefetch. The
1581+ janitor keeps the newest stale successor while that exact key is open; if a
1582+ fresh result exists it keeps no stale entry. Close/sequence teardown removes
1583+ the lease and flushes the queue. ` ResetLocalStats ` retains the open maps
1584+ because they are operational ownership state. Thus a live rollover is
1585+ immediate without turning delayed create results into unbounded memory or
1586+ unused-contract retention.
1587+
1588+ Physical signed-build evidence (` versionCode=1004819970 ` ):
1589+
1590+ - Samsung→Pixel opened Wikipedia, idled 130 s, then loaded Mozilla, GitHub,
1591+ and Guardian. The first provider-return contract reached
1592+ 837,513/838,860 usable bytes after about 290 s and installed its successor
1593+ about 1.1 ms later. No ≥1 s ` contract wait ` was logged.
1594+ - The four complete loads were 2.490, 12.185, 27.292, and 11.007 s.
1595+ Guardian had timed out beyond 90 s in the preceding build; GitHub was
1596+ 44.06 s.
1597+ - Pixel→Samsung crossed 838,860 usable bytes in both request and return
1598+ directions. Successors appeared about 1.4/1.6 ms later, again without a
1599+ slow wait. Wikipedia and Mozilla loaded in 2.152/7.545 s.
1600+
1601+ GitHub retained a 12.46 s p95 request TTFB under 108-way request parallelism
1602+ after rollover on the large second contract. Guardian then loaded in 11.01 s.
1603+ That remaining tail is parallel-flow/transport scheduling, not contract
1604+ acquisition.
1605+
1606+ Top-level sizing, generated-identity, carrier, ownership, public-expiry, reset,
1607+ and bounded-retention tests passed 100 ordinary and 20 race repetitions.
1608+ Deterministic Connect excluding its two randomized PT stress cases passed in
1609+ 362.173 s; those cases passed separately in 63.46/65.33 s. Subpackages, vet,
1610+ Android release unit tests, lint, SDK bind, assembly, and the physical install
1611+ pass.
0 commit comments