You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
mkdir: exits 0 despite EEXIST from the kernel under concurrent invocation (breaks mkdir-as-mutex) #13970
Under concurrent invocation, mkdir (uutils coreutils 0.8.0, as shipped by Ubuntu 26.04 LTS) can exit 0 even though the kernel returned EEXIST for the underlying mkdir(2) syscall. Two processes racing to create the same directory then both believe they succeeded — which silently breaks the classic mkdir-as-mutex shell pattern (until mkdir lock; do sleep 1; done).
# one round: 20 concurrent attempts to create the same directory
rm -rf /tmp/lockdir
foriin$(seq 20);do
( mkdir /tmp/lockdir 2>/dev/null &&echo"WINNER $i" ) &donewait
Expected: exactly one WINNER per round.
Observed: occasionally two winners — measured 3 of 100 rounds with 20 contenders. The rate grows with contention: an independent rerun with 100 contenders per round hit a double-winner in 17 of 20 rounds.
Evidence that the syscall failed but the exit code lied
strace on a losing process shows the kernel refusing the create, yet the process exits 0:
Sequential (non-concurrent) invocation reports EEXIST correctly with exit 1, so this looks like an internal race window in the binary rather than a general semantics change.
Controls (0 failures in 100 rounds each, same scenario)
busybox mkdir
python3 -c 'import os; os.mkdir(...)'
bash noclobber ( set -C; : > lock ) (open(O_CREAT|O_EXCL) without an external binary)
GNU coreutils 8.30 on another machine in the same fleet also showed 0 failures.
Impact
Any script using mkdir as an atomic mutex (a pattern old enough to be in textbooks) gets a silently leaky lock on affected systems: two writers enter the critical section and, in our case, interleaved bytes in an append-only file. We have since migrated our protocol to shell noclobber, but the binary's exit code disagreeing with the syscall result seems worth fixing upstream.
Happy to provide the full test harness (100-round concurrency bench) if useful.
Activity
sylvestre commentedon Aug 16, 2026
Could you please try with main or 0.10.0?
mkdir: do not ignore EEXIST on non-recursive creation
mkdir: fix EEXIST race condition in non-recursive mode
mkdir: fix EEXIST race condition in non-recursive mode
mkdir: fix EEXIST race condition in non-recursive mode
mkdir: do not ignore EEXIST on non-recursive creation
mkdir: do not ignore EEXIST on non-recursive creation
mkdir: fix EEXIST race condition in non-recursive mode
mkdir: do not ignore EEXIST on non-recursive creation
Claim pool builds with noclobber instead of mkdir
fix(ssh): concurrent archive installs no longer race on uutils mkdir
fix(ci): ci-rerun.sh claims a run's reservation with mkdir(2), not th…
feat(ci): one run per PR head, the private bootstrap off pull request…
🤖 fix: claim non-host background spawn names with an exclusive marker…
test(ctags): lock the failed fake ctags start with set -C, not mkdir
fix(shared-run): decide the lock owner with noclobber, not with mkdir…
fix(shared-run): decide the lock owner with noclobber, not with mkdir…
fix: prevent concurrent task metadata corruption (#64)
swarm: the run registry's lock holds where mkdir lets two callers thr…
fix: prevent duplicate run IDs on false-success mkdir (#81)
fix(PG-956): the run-slot tests take a lock as the Mac does on CI's u…
Lock the test sbatch shim with noclobber, not mkdir (#100)