Skip to content

fix(locking): take the driver lock with an exclusive create - #802

Merged
HaoZeke merged 4 commits into
developfrom
fix/atomic-driver-lock
Oct 23, 2026
Merged

HaoZeke merged 4 commits into
developfrom
fix/atomic-driver-lock

Conversation

@HaoZeke

@HaoZeke HaoZeke commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

Two server drivers started in one directory could both pass the lock check and write their pid. They then raced over the shared job directories, and one run aborted with FileExistsError.

The lock is now created with an exclusive open. The pid is written and flushed, and a dead, empty or truncated lock is removed and retried once. Where fcntl is available the owner holds an flock, so a recycled pid cannot look live. removelock deletes the file only when it still holds the pid this process wrote.

acquirelock is the name to call, and aquirelock remains as a deprecated alias. The akmc, basin hopping, parallel replica and escape rate drivers now call acquirelock.

Basin hopping also raised FileExistsError when the state directory or the incoming job directory already existed. Those creates, and the same incoming job directory in parallel replica, escape rate and the explorer, now accept an existing directory.

Tests in tests/test_locking.py start two processes and two basin hopping drivers together and check that exactly one takes the lock. They also cover stale and truncated lock files, and existing state and job directories. Each test fails on develop d243519 and passes here. The full pytest run gives 670 passed and 43 skipped.

LockFile.aquirelock checked islocked and then wrote the pid. Two
drivers started together both passed the check and both wrote the
file. Create the file with O_CREAT|O_EXCL|O_WRONLY, write the pid,
and fsync. On EEXIST, a dead pid or a truncated file is removed and
the create is tried once more. Where fcntl is available, hold an
flock on the descriptor so a reused pid is not treated as the owner.
removelock unlinks the file only when it still contains the pid this
object wrote.
The exclusive lock lives on acquirelock. aquirelock is a deprecated
alias. The akmc, basin hopping, parallel replica, and escape rate
drivers, and the library body test, call the spelled method.
register_results removed the incoming job directory and created it
again without exist_ok. Two drivers that both passed the lock both
created it, and the second raised FileExistsError. add_state had the
same race on the new state directory. Create both with exist_ok. The
replica drivers and the process-search explorer share the incoming
job directory pattern and get the same call.
@codecov

codecov Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.74510% with 76 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
eon/locking.py 62.37% 56 Missing and 17 partials ⚠️
eon/basinhopping.py 66.66% 0 Missing and 1 partial ⚠️
eon/escaperate.py 50.00% 0 Missing and 1 partial ⚠️
eon/parallelreplica.py 50.00% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Develop took the fork exit guard and the results directory
create after this branch. Keep the exclusive lock, disarm
the exit hook in a forked child, and create the results
directory before the log and the lock.
@HaoZeke
HaoZeke merged commit 407cc34 into develop Oct 23, 2026
52 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