Source code for cadrumo.core.lockfile_unlink

"""Share-violation-tolerant lockfile removal shared by the crash-recoverable locks.

Every PID-stamped lockfile in this project is inspected by its waiters: a
process that finds the lock taken opens the file to read the holder's PID and
decide whether the holder is still alive. On Windows the CRT opens that read
without ``FILE_SHARE_DELETE``, so for as long as the reader's handle is open the
holder's ``unlink`` is refused with ``ERROR_SHARING_VIOLATION`` -- surfaced by
CPython as :class:`PermissionError` with ``winerror == 32``.

Left uncaught on a release path that is a **wedge**, not a delay: the lockfile
survives stamped with a PID that is still a live process, so no peer will ever
reclaim it as stale, and every later acquirer blocks to its own timeout. The
subsystem the lock guards is unusable until the holder exits.

The window is small but not theoretical. Under cross-process contention on this
project's own bucket lock, three of four contending processes stranded their
lockfile this way within a few seconds.

Two dispositions cover every call site, and which one applies is a property of
the caller, not of the error:

- **Releasing a lock this process holds** must retry to a deadline. Nobody else
  can clear it, so giving up strands the lock.
- **Reclaiming a lock judged stale**, or a best-effort cleanup, must not retry
  and must not raise. Losing that race costs one more poll, and the next
  attempt reclaims it.

Both are :func:`unlink_lockfile` with different retry budgets, so the two lock
subsystems and the locale catalogue guard cannot drift apart on the handling.

A blocked removal is never reported as a success. Windows cannot distinguish
contention from a denying ACL at the unlink boundary, so the retry budget is
what separates them: a transient block clears inside the window, and anything
that outlasts it comes back as ``False`` for the caller to refuse on.
"""

from __future__ import annotations

import time
from typing import TYPE_CHECKING

from .logging import get_logger
from .windows_contention import is_windows_contention

if TYPE_CHECKING:
    from pathlib import Path

_log = get_logger(__name__)

LOCKFILE_UNLINK_RETRY_SECONDS = 10.0
"""Default budget for retrying a blocked release; a reader's handle lives microseconds."""

_UNLINK_POLL_SECONDS = 0.02






__all__ = ["LOCKFILE_UNLINK_RETRY_SECONDS", "unlink_lockfile"]