✨ feat(platform): support Termux/Android#678
Merged
Merged
Conversation
Importing filelock crashed on Termux with `AttributeError: module 'os' has no attribute 'link'`, because the strict backend read `os.link` at import time and Termux's bionic CPython ships without it (discussion tox-dev#677). That killed `from filelock import FileLock` before any lock was even chosen, even though flock and the soft backend both work there. Gate every `os.link` reference on a single capability probe so the import succeeds and only a real StrictSoftFileLock acquire reports the missing hard links, reusing the existing unsupported-filesystem error path. Termux also reports `sys.platform == "android"` rather than "linux", so the /proc-based process-start token silently degraded to PID-only liveness; treat android as Linux, since it runs the same kernel with a working /proc. The strict lock genuinely cannot run without hard links, so its tests carry a requires_hard_links marker that the conftest skips when os.link is absent, and a new CI job runs the whole suite inside a real termux-docker userland to keep the platform honest. Verified there: 1042 passed, 159 skipped, zero failures. Bundled with the pre-commit autoupdate that bumps ruff to 0.15.22 and migrates the repo's `# noqa` suppressions to the `# ruff:ignore` syntax.
The requires_hard_links pytestmark added to test_strict_soft_recovery.py rebound the module's existing cpython-only pytestmark instead of joining it, so the crash-injection tests ran on PyPy, where the os.link/os.remove audit events never fire and the child finishes cleanly rather than crashing. Combine both markers in a list. The eight-process contention worker proved exclusivity by creating a shared file with O_EXCL and unlinking it inside the lock, which tripped over Windows delete-pending lag: the previous holder's unlink had not finished before the next holder's create ran, raising a spurious FileExistsError. Count increments under the lock instead; a lost update still exposes a real overlap without depending on unlink timing.
Replace the hand-rolled pip install with `docker compose run --rm termux`, which installs tox and the patched filelock, then runs `tox run -e py` inside a real Termux userland. tox imports filelock for its own provisioning lock, so the patched tree goes in first, otherwise tox crashes on startup with the very AttributeError this branch fixes. The same one command reproduces the job locally. Raise the per-environment coverage floor from 76 to 79, the lowest non-Termux figure the matrix reports (Windows); Termux clears it at 86. Give the sole try-except-in-loop suppression its missing rationale.
Under heavy contention StrictSoftFileLock could let two processes hold the lock at once. A winner unlinked its intent claim, leaving only the freshly linked held claim as its marker. os.scandir gives no guarantee about entries created during a scan (POSIX leaves it unspecified), so a contender's final rescan could miss that held claim, see none of the holder's claims, and read itself as the lowest-token owner. PyPy's timing exposed this reliably (13/25 runs overlapped at 16x300); CPython hides it (0/8). Keep the intent claim for the whole hold. It has existed unchanged since the owner published it, so any complete scan is guaranteed to return it; the min-token decision is then always computed over the true claim set. Release removes both claims. Observable: a held lock now exposes both an intent and a held claim, so force_break must remove both to break it. Recovery and failure tests updated to the two-claim state; the stress test now detects overlap with an occupancy marker and no longer asserts empty worker stderr, which flaked on interpreter warnings.
Split the Termux run into a cached base image (apt + tox) and a per-call compose command, so iterating on the source no longer re-runs the apt/tox bootstrap. tasks/termux/run.sh folds into tasks/termux/Dockerfile plus the compose command. Keep copying the source out of the mount before tox runs: in Termux, tox registers its package env twice and dies with "duplicate configuration definition for .pkg" whenever its work_dir is on a different filesystem than the project (tox-dev/tox#3987). Copying keeps the project and its .tox on one layer and leaves the mounted tree untouched.
_check_give_up drops its lock_id/lock_filename parameters and reads them from self as an instance method; _resolve_lifetime takes the lock class instead of five unpacked attributes. The remaining ignores sit on public constructors and their metaclass __call__ forwarders, where the parameter count is the documented API, so each now carries a reason for why it stays.
Every inline ruff:ignore now either states why it stands or is gone. Two are fixed: _resolve_preserve_lock_file takes its bool keyword-only, and the soft marker's line count reads through a named constant. The rest sit on public signatures, deliberate internal cross-instance access, interface-parity arguments, and intentional test patterns, each now carrying its reason.
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.
import filelockcrashed on Termux/Android withAttributeError: module 'os' has no attribute 'link', reported in the Termux support discussion (#677). The strict backend reados.linkat import time, and Termux's bionic CPython ships without it, sofrom filelock import FileLockdied before filelock could pick a backend, even thoughflockand the soft backend both work on the app-private filesystem. 🤖Every
os.linkreference now sits behind one capability probe, so the module imports and aStrictSoftFileLockreports the missing hard links through the existing unsupported-filesystem error only when a caller acquires it. Termux also reportssys.platform == "android"rather than"linux", which had left the/proc-based process-start token degrading to PID-only liveness. Android runs the Linux kernel with a working/proc, so it now takes the Linux path.A
StrictSoftFileLockcannot run without hard links, so the suite skips those cases whereos.linkis absent, and a newtermuxCI job runs the library inside a realtermux-dockeruserland to keep the platform green. The branch also folds in the pre-commit autoupdate that bumps ruff to 0.15.22 and rewrites the repo's# noqasuppressions as# ruff:ignore, which is why the diff reaches files beyond the platform change.