Skip to content

fix(js/ts/python): settle cancelled delays and finalizers once - #5038

Open
dbrattli wants to merge 5 commits into
mainfrom
fix/async-delay-settlement
Open

dbrattli wants to merge 5 commits into
mainfrom
fix/async-delay-settlement

Conversation

@dbrattli

@dbrattli dbrattli commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Python Async.Sleep discards its scheduled handle, so cancellation can later be followed by timeout success and a second finalizer. The cancellation guard also falls through and executes already-cancelled work. JS leaves cancelled-sleep listeners installed, and both runtimes mishandle throwing finalizers during cancellation. Executed .NET references confirm that cancellation stays terminal even if its finalizer throws.

The regressions belong in the existing F# async suites so they exercise generated computations and share public behavior checks with .NET. Native fixtures provide deterministic scheduler and thread controls through the suites' established interop pattern.

How

  • Retain the Python timer handle and arbitrate timeout/cancellation once, retiring the timer and listener on success, cancellation, and scheduler installation failure.
  • Marshal foreign-thread cancellation to the owning live event loop and dispose registrations outside the settlement gate.
  • Stop Python work after the initial cancellation continuation and make finalizers run once; ordinary finalizer failures use the error continuation, while cancellation-time failures preserve cancellation as on .NET.
  • Give JS sleep the same single-settlement cleanup and finalizer behavior.
  • Put test cases, assertions, and async computations in tests/Python/TestAsync.fs and tests/Js/Main/AsyncTests.fs. Import scheduler/thread fixtures from tests/Python/py/async_runtime.py and tests/Js/Main/js/async-runtime.js; remove the standalone JS runner and its build hook.
  • Preserve stale-callback execution after timer retirement, installation barriers/failures, cleanup counters, real-thread owner-loop checks, and caller-owned reply/cancel/timeout arbitration for every controlled winner. Run shared finalizer failure checks against .NET as well as generated targets.
  • Document and regress existing idle mailbox limits: token cancellation alone does not wake Receive. Python observes it on a post; JS later delivers the posted message.

@dbrattli
dbrattli marked this pull request as ready for review October 3, 2026 22:45
@dbrattli
dbrattli requested a review from MangelMaxime October 7, 2026 21:43
Base automatically changed from fix/cancellation-registration-lifecycle to main October 10, 2026 15:26
@github-actions

Copy link
Copy Markdown
Contributor

Python Type Checking Results (Pyright)

Metric Value
Total errors 34
Files with errors 4
Excluded files 4
New errors ✅ No
Excluded files with errors (4 files)

These files have known type errors and are excluded from CI. Remove from pyrightconfig.ci.json as errors are fixed.

File Errors Status
temp/tests/Python/test_hash_set.py 18 Excluded
temp/tests/Python/test_applicative.py 12 Excluded
temp/tests/Python/test_nested_and_recursive_pattern.py 2 Excluded
temp/tests/Python/fable_modules/thoth_json_python/encode.py 2 Excluded

This branch has not been deployed

No deployments
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.

1 participant