Skip to content

Make async acquire and release cancellation-safe #640

Closed

Description

@gaborbernat

Problem

Cancelling an executor-backed acquisition can leave the backend locked with no acquisition counter or deadlock-registry
entry. The worker can finish after the awaiting task has rolled back its state:

after_cancel=(is_locked=False, counter=0)
after_executor_finished=(is_locked=True, counter=0)

The acquisition and reconciliation paths are in
BaseAsyncFileLock.acquire(),
_async_poll_until_acquired(),
and
_run_internal_method().
CPython propagates cancellation from asyncio.wrap_future() to the wrapped future:
asyncio/futures.py.
A running executor future rejects cancellation:
concurrent/futures/_base.py.

Release has an error-channel defect. It shields and awaits the release task, and its state reconciliation keeps
is_locked and the counter accurate, but
BaseAsyncFileLock.release()
suppresses a release exception after caller cancellation. The caller receives cancellation with no report that the lock
may remain held.

AsyncReadWriteLock._run()
awaits run_in_executor() and needs the same transaction boundary.

Required behavior

Run each backend acquisition in a shielded task or future. If caller cancellation arrives during an in-flight attempt,
wait for that attempt to finish. Release the backend under a shield if acquisition succeeded, then propagate
cancellation. Retain the counter and registry entry when rollback fails and the backend remains held.

Do not suppress a release error after cancellation. Report both failures through the dual-error policy from
#631, preserving the backend error as an independent leaf. Coordinate
descriptor close handling with #633 and registry-key lifetime with
#632.

Verification

Use deterministic synchronization points instead of timing sleeps. Cancel before executor start, while the worker is
blocked, after backend acquisition but before state commit, and during rollback. Cover rollback and release failures.
After each case, assert the counter and public state, then use a separate process to test the operating-system lock.
Apply the same cases to AsyncReadWriteLock where its API permits them.

Activity

  1. added 2 commits that reference this issue on Jul 14, 2026
    e9782a8
    63a038a
  2. added a commit that references this issue on Jul 14, 2026
    3b15112
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions