Repository navigation
Make promise task outcome task-local in processNextTick - #173
Open
yuxi-liu-wired wants to merge 1 commit into
Open
yuxi-liu-wired wants to merge 1 commit into
yuxi-liu-wired wants to merge 1 commit into
Conversation
The drain loop kept `state` across tasks of the same queue, so a task that threw left `state` as REJECTED for every following task, and a reject handler that returned a value never switched it back to RESOLVED. Since (p/resolved nil) returns a shared NULL_PROMISE, independent chains started from nil share one queue, and an exception in one chain corrupted the result of the others. Each task's state/value is now computed only from the source promise and its own handler. A reject handler that returns normally recovers (RESOLVED); one that throws rejects. `handle` goes back to using defaultRejectHandler (as in 11.0.678) so rejections still reach its completion callback as the cause. To avoid paying for a throw on every propagated rejection, a task whose reject handler is defaultRejectHandler gets the same outcome the rethrow would produce, without calling it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iotb3eKMwNTZTnsonJb3F
This branch has not been deployed
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.
processNextTickkeptstateacross tasks of one queue. So a task that threw left every later task REJECTED, and a reject handler that returned a value never set it back to RESOLVED.(p/resolved nil)returns one sharedNULL_PROMISE, so independent chains from nil share a queue and one chain's exception corrupts the others.Reproduction
bAnull"b-ok"c"recovered:C:continued""recovered:C""recovered:C:continued"dCnull"d-ok"Fix
state/rvalueare task-local. Each outcome depends only on the source promise and the task's own handler: return means RESOLVED (a reject handler that returns is a recovery), throw means REJECTED.handleusesdefaultRejectHandleragain (as in 11.0.678). Its identity reject handler only worked because the loop-level state leaked.defaultRejectHandlergets the rethrow outcome directly, with no throw. This avoids a ~2.5× slowdown inhandleon rejected promises. Benchmarks (1M ops, 15 interleaved rounds): resolved and rejected paths match master within noise, and rejection propagation through.then/.catchis ~2× faster (655 → 314 ms).Tests
New
:cljstests incore_test.cljc:independent-chains-from-nil-do-not-share-outcomeandthrow-in-then-recover-in-catch-and-continue. Both fail on master (3 assertions) and pass with the fix.cljs.main, since Clojars was unreachable from the environment the change was made in; the CI job runs the shadow-cljs build).promesa.tests.*-test): 92 tests, 263 assertions, 0 failures.bb test:bb): 68 tests, 160 assertions, 0 failures.🤖 Generated with Claude Code