Skip to content

cMeta v0.35.0: the soft lock across systems, a registered repository that stays registered, the engine's modules imported once - #55

Merged
gfursin merged 6 commits into
mainfrom
dev
Oct 11, 2026
Merged

gfursin merged 6 commits into
mainfrom
dev

Conversation

@gfursin

@gfursin gfursin commented Oct 11, 2026

Copy link
Copy Markdown
Member

cMeta v0.35.0: the soft lock across systems, a registered repository that stays registered, the engine's modules imported once

See CHANGELOG.md for the full text. In short:

The soft lock (the fallback of PathLock where a file system refuses OS locks; nothing changes where OS locks work):

  • the age of a lock is counted from the time its holder wrote, never from the drive's clock;
  • the holder keeps its file open and beats (one thread per process rewrites the time in its line every 2 s); a waiter that has seen the same line for 20 s on its own clock knows the holder is gone - no comparison of two machines' clocks, no limit on how long a lock is held;
  • the line names the system (pid host time system), so a pid is checked only on its own system (Windows and WSL2 share the host name; a container shares the kernel); lock files of earlier versions are judged as before;
  • a stale lock file is replaced in one rename by one waiter (never renamed away and put back - under load that gave two holders, leftover .stale-* files, and a lock stuck with the line of a live process); the file is closed before the rename everywhere (Windows refuses otherwise; a Windows drive seen through 9p shows nothing at the new path while it is open);
  • a single try takes the lock of a dead process in that try; a forked child never releases its parent's lock and starts with thread locks of its own;
  • asking for it on a shared folder: CMETA_SOFT_LOCK_PATHS=<folders> (or *) for a process, lock: soft in a repository's _cmr.yaml for every system that plugs it - a home or repository written by several systems at once through a shared drive, whose OS locks do not see each other;
  • on exFAT under macOS a lock with a note no longer leaves its file behind.

A registered repository that cannot be reached stays registered (unreachable: true in repos.json, left out of the index with a notice, back by itself with the next index run; cx repo unplug <path>, cx repo reindex <path>).

The engine's own category API is imported once: loaded by its path under a name made from its folders, its relative imports made a second copy of every engine module in the process, each with a state of its own.

Python 3.15 in the CI matrix.

Tested on Windows, Ubuntu (Python 3.9, 3.14, the free-threaded 3.14t), macOS, WSL2 (ext4 and /mnt/d), a container and eight Docker images; the soft lock between Windows, WSL2 and a container on one folder (exclusion, killed holders replaced, a three-system race, the engine updating one artifact from twelve processes on three systems without a lost key); FAT32 and exFAT images on Linux and a real exFAT image on macOS.

🤖 Generated with Claude Code

gfursin and others added 6 commits October 11, 2026 14:09
…n.ask, utils.files.acquire_with_notice, the resume record suffix of utils.net)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Grigori Fursin <gfursin@gmail.com>
Python 3.15.0 is out: the core tests run on Linux, Windows and macOS with 3.9, 3.14 and 3.15.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Grigori Fursin <gfursin@gmail.com>
Both CI matrices passed on Python 3.15.0 (Linux, Windows, macOS). AGENTS.md, CLAUDE.md, CONTRIBUTING.md and
the pull request template say 3.9-3.15, docs/releasing.md names the three versions of the CI, pyproject.toml
gets the classifier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Grigori Fursin <gfursin@gmail.com>
When the index was built - cx --reindex, or the incremental index of pull, plug,
unplug and cx repo reindex - and the folder of a registered repository was not
there (a detached drive, a share that was down) or its _cmr.yaml could not be
read, the repository was dropped from repos.json without a word and had to be
plugged again; one transient miss was enough.

Now its line stays in repos.json, marked "unreachable: true"; the repository is
left out of the index (an incremental run removes its records, so that a find
does not hand out artifacts whose folder is away); a notice names it and the two
commands. When the folder is there again, the next index run of any kind indexes
it as if it had just been plugged and clears the mark - also cx repo reindex
<path>, which accepts the folder of a registered repository that is not in the
index. cx repo unplug <path> forgets a repository by the path of its folder.

Fixed on the way: the stored descriptor of a repository plugged with --keep was
left out of repos.json whenever the file was rewritten while the repository was
reachable; a _cmr.yaml without an artifact name ended the index with a KeyError;
the pause of an ambiguous category alias ended the index with EOFError when
nobody could answer.

Tests: tests/internal_repo_tests/test_integration_repo_unreachable.py (8).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Grigori Fursin <gfursin@gmail.com>
…e step, and a way to ask for it on a shared folder

The soft lock is the fallback of PathLock where a file system refuses OS locks;
nothing changes where OS locks work.

- How long ago a soft lock was taken is counted from the time its holder wrote
  into the file, never from the time the file system gave the file (a drive can
  keep another clock: a Windows drive inside WSL2, a network share). An empty
  lock file is a leftover once a waiter has seen it empty for 2 seconds.
- The holder keeps its lock file open and one thread per process rewrites the
  time in the line of every held soft lock every 2 seconds; a waiter that has
  seen the same line for 20 seconds on its own clock knows the holder is gone -
  no comparison of two machines' clocks, no limit on how long a lock is held,
  no pid it cannot check.
- The line names the system (pid host time system: the kernel's boot id and
  pid namespace on Linux, the machine id on Windows and macOS), so the pid is
  checked only on the holder's own system: Windows and WSL2 share the host name
  and have different pids; a container shares the kernel and has its own pids.
  A lock file of an earlier cMeta (three words) is judged as before.
- A stale lock file is replaced in one rename by one waiter (<lock>.take-<crc>
  made exclusively, checked, renamed over the path), never renamed away and put
  back: under load the old way gave two holders, leftover .stale-* files and a
  lock file carrying the line of a live process that did not hold it any more.
- A single try takes the lock of a dead process in that try; a holder confirms
  the file at the path is its own and removes it at release only when it still
  is; the empty file a refused OS-lock attempt leaves is taken over the same
  way; a forked child never releases its parent's lock and starts with thread
  locks of its own.
- A home or repository that several systems write at once through a shared
  drive asks for the soft lock: CMETA_SOFT_LOCK_PATHS (folders, or *) for a
  process, lock: soft in a repository's _cmr.yaml for every system that plugs
  it (registered when it is plugged and at the first access of every process);
  utils.files.use_soft_lock_under is the API. Each system keeps its own index
  of such a repository: another system's change is seen after cx repo reindex.
- A lock with a note leaves no lock file behind on exFAT under macOS, where an
  empty file reports a placeholder inode that changes at the first write: the
  identity is asked from the open file at release.
- The engine's own category API (category_api_v1.py) is imported once by its
  real name: loaded by its path under a name made from its folders, its
  relative imports made a second copy of the engine's modules in the process,
  each with a state of its own.

Tests: tests/core_tests/test_utils_files_path_lock.py (21 -> 31),
tests/internal_repo_tests/test_integration_repo_soft_lock.py (3); a test of
the writes skips where a file system has no symbolic links.
Docs: docs/async-and-concurrency.md, docs/using-cmeta.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Grigori Fursin <gfursin@gmail.com>
The version in every file that carries it (docs/releasing.md, step 2); release
date 2026-10-11.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Grigori Fursin <gfursin@gmail.com>
@gfursin
gfursin merged commit b5fdada into main Oct 11, 2026
10 checks passed
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