Repository navigation
global: write cache files atomically to avoid races between concurrent shells (#109) - #727
Conversation
3702195 to
e99d9c1
Compare
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R2ZY4aNfZ2gnhKw9UBJovV Co-Authored-By: Koichi Murase <myoga.murase@gmail.com>
e99d9c1 to
0dd5f7d
Compare
|
Thanks for the contribution. I've added adjustments and force-pushed them. Could you test in your environment the PR head with the mentioned 32 interactive Bash sessions? It should be noted that the leftover |
|
@akinomyoga thanks for the adjustments. Tested the PR head (0dd5f7d) with the same harness: 32
After the PR-head runs the cache directory holds the 12 expected files and no Understood on keeping From my side this is good to merge. |
|
Thanks for the confirmation. |
Fixes #109
Problem
The cache files under
$_ble_base_cacheare written in place (>| "$file",3>| "$file"). When many interactive shells start at the same moment with a stale cache, one shell truncates and rewrites a cache file while the others already pass the-s/-ntvalidity checks andsourcea half-written file.I hit this with kitty's session restore (32 tabs) right after
ble-updatefollowed by a reboot, so the restored shells were the first to run on the new install. Symptoms in the affected tabs:ble.sh: The keymap 'emacs' is empty.andble-attachreturns 1 (the emacs counterpart of [tmux-resurrect] Cache broken on restoring (Errorble.sh: The keymap 'vi_imap' is empty.) #109).bash: syntax error near unexpected tokenwhen a half-writtendecode.bind.*.bindis sourced.1;4000;48c,RRRR...), and hooks registered withblehook PRECMD(e.g. starship) never run again, so the prompt freezes.The workaround from #D1562 checks
-s, which covers the zero-byte case, but a file that is being written is non-empty and syntactically broken for most of its lifetime.Fix
Write each attach-time cache to
<file>.$$.partand rename it into place withble/bin/mv -f, removing the temporary on failure. The temporary lives in the same directory, so the rename is atomic and readers only ever see complete files. Covered writers:lib/keymap.emacs.sh,lib/keymap.vi.sh,lib/keymap.vi_digraph.sh(keymap.*)lib/init-cmap.sh(decode.cmap.*.dump)lib/init-bind.sh(decode.bind.*.bind/.unbind)lib/init-term.sh(term.$TERM)src/decode.sh:decode.readline.*.txtalready used a.parttemporary, but with a name shared by all shells, so two writers could still interleave into it; it now uses the per-process name.decode.inputrc.*(.cache-save) is now written via temporaries too.Not touched, although they follow the same pattern, because they are not on the attach path: the mandb completion cache in
lib/core-complete.shand the compiled msleep helper inlib/init-msleep.sh. Happy to include them if you prefer.Testing
Reproduction: spawn 32 interactive
bash -iin ptys at the same time withXDG_CACHE_HOMEpointing at an empty directory (the harness answers DA1/DA2/CPR queries so attach does not sit on timeouts). Each shell sources ble.sh with--noattachfrom.bashrcand callsble-attachat the end.After the patched runs all cache files are present and no
*.partfiles are left behind.make check: every section passes except twoble/util/sprintfcases ("%#.2f"gives27,00instead of27.00), which fail identically on untouched master in my environment, so they are unrelated to this change.Environment: bash 5.2.37, kitty 0.48 (
TERM=xterm-kitty), Debian trixie.Refs #109, which I believe has the same root cause.
🤖 Generated with Claude Code
https://claude.ai/code/session_01R2ZY4aNfZ2gnhKw9UBJovV