feat: adaptive bisection with attempts cap (F6b) - #21
Merged
Merged
Conversation
Bisect a rejecting physical batch (length > 1) into two halves and retry both through the pool's own central queue instead of failing the whole batch immediately. Bounded per original batch by maxBatchAttempts (default 2*ceil(log2(batchSize))+1); on exhaustion the unresolved calls become terminal with the last transport error. Splitting never awaits its own children — the permit is released before they're enqueued — so a pool of size 1 can drain an arbitrarily deep bisection tree without deadlocking. Off by default (adaptiveBatching: false) so existing run()/runSettled() behavior is unchanged byte-for-byte; enabling it lets run() rethrow the isolated raw transport error and runSettled() record per-call kind: 'batch' failures instead of coarsening the whole batch, while reverts carried in a resolved RawResult[] are never retried.
A length>1 rejection that consumed an original batch's LAST allowed attempt was still unconditionally pushing both children onto the queue. Those children only terminalize once claimed, at the claim-time cap check — but if a sibling original's cancellation wins the race first, queued items are never claimed at all, so this item's own terminal error silently never reached terminalErrors. Re-check attempts remaining before splitting; when none remain, terminalize this item synchronously in the same tick as its rejection instead. Regression test: two original batches, conc=2, maxBatchAttempts=2, where the lower-index original's final attempt is still in flight when the higher-index original exhausts and cancels first — the lower index's last transport error must still win the (batchIndex, callIndex) selection.
halaprix
added a commit
that referenced
this pull request
Jul 24, 2026
* feat: adaptive bisection with attempts cap (F6b) Bisect a rejecting physical batch (length > 1) into two halves and retry both through the pool's own central queue instead of failing the whole batch immediately. Bounded per original batch by maxBatchAttempts (default 2*ceil(log2(batchSize))+1); on exhaustion the unresolved calls become terminal with the last transport error. Splitting never awaits its own children — the permit is released before they're enqueued — so a pool of size 1 can drain an arbitrarily deep bisection tree without deadlocking. Off by default (adaptiveBatching: false) so existing run()/runSettled() behavior is unchanged byte-for-byte; enabling it lets run() rethrow the isolated raw transport error and runSettled() record per-call kind: 'batch' failures instead of coarsening the whole batch, while reverts carried in a resolved RawResult[] are never retried. * fix: terminalize an exhausted-on-rejection bisection item immediately A length>1 rejection that consumed an original batch's LAST allowed attempt was still unconditionally pushing both children onto the queue. Those children only terminalize once claimed, at the claim-time cap check — but if a sibling original's cancellation wins the race first, queued items are never claimed at all, so this item's own terminal error silently never reached terminalErrors. Re-check attempts remaining before splitting; when none remain, terminalize this item synchronously in the same tick as its rejection instead. Regression test: two original batches, conc=2, maxBatchAttempts=2, where the lower-index original's final attempt is still in flight when the higher-index original exhausts and cancels first — the lower index's last transport error must still win the (batchIndex, callIndex) selection. --------- Co-authored-by: halaprix <halaprix@users.noreply.github.com>
halaprix
added a commit
that referenced
this pull request
Jul 24, 2026
* feat: adaptive bisection with attempts cap (F6b) Bisect a rejecting physical batch (length > 1) into two halves and retry both through the pool's own central queue instead of failing the whole batch immediately. Bounded per original batch by maxBatchAttempts (default 2*ceil(log2(batchSize))+1); on exhaustion the unresolved calls become terminal with the last transport error. Splitting never awaits its own children — the permit is released before they're enqueued — so a pool of size 1 can drain an arbitrarily deep bisection tree without deadlocking. Off by default (adaptiveBatching: false) so existing run()/runSettled() behavior is unchanged byte-for-byte; enabling it lets run() rethrow the isolated raw transport error and runSettled() record per-call kind: 'batch' failures instead of coarsening the whole batch, while reverts carried in a resolved RawResult[] are never retried. * fix: terminalize an exhausted-on-rejection bisection item immediately A length>1 rejection that consumed an original batch's LAST allowed attempt was still unconditionally pushing both children onto the queue. Those children only terminalize once claimed, at the claim-time cap check — but if a sibling original's cancellation wins the race first, queued items are never claimed at all, so this item's own terminal error silently never reached terminalErrors. Re-check attempts remaining before splitting; when none remain, terminalize this item synchronously in the same tick as its rejection instead. Regression test: two original batches, conc=2, maxBatchAttempts=2, where the lower-index original's final attempt is still in flight when the higher-index original exhausts and cancels first — the lower index's last transport error must still win the (batchIndex, callIndex) selection. --------- Co-authored-by: halaprix <halaprix@users.noreply.github.com>
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
Completes spec F6 (1.2.0) — adaptive bisection on the concurrency pool:
adaptiveBatching(opt-in, default false — JSDoc carries the spec's rate-limit-amplification rationale): a batch-level transport rejection splits the batch and retries both halves through the pool's central queue; at length 1 the failure is terminal (rethrown byrun, recorded as a per-callkind: 'batch'DominoCallErrorbyrunSettled). Reverts inside resolved results are never retried.maxBatchAttempts, default2·⌈log₂(batchSize)⌉+1): enforced at claim time — every execution counts; exhaustion terminalizes unresolved calls as a coarse group withcause= the last transport error (never wrong data). Review hardening: a rejection that consumes the final attempt terminalizes immediately instead of enqueueing unclaimable children (regression-verified fix for a concurrent-cancellation error-loss race).Test evidence
240/240 (13 new: isolation with 99 surviving siblings, attempts-cap coarse groups with exact surviving values, deadlock pool=1, reverts-not-retried, multi-poisoned no-identity, exhausted-on-rejection regression), compat-vs-dist 38/38, full gate build-first. Gzip 12.3KB (<15KB).
Spec:
spec.md§ 1.2.0 F6 (bisection).