AtomicFile::finalize_all() renames the database files one after another — month data, then undo.data, then tags.data. A process killed between two renames (SIGKILL, OOM killer, hard reboot) leaves files that are each intact but inconsistent with each other. The signal masking from #316 cannot block SIGKILL, and this is a different mechanism from #772: no fsync is involved, and an fsync fix would not close this window.
Confirmed on 1.4.3 (Debian bookworm) and on develop @ db7751c, built from source. I found the two states below with a crash-consistency test harness I wrote (it SIGKILLs the process immediately before each state-directory syscall in turn) and verified them by hand; the reverted file is byte-for-byte what a kill between the renames leaves behind.
Case 1 — the journal misses the last transaction (killed after the data rename, before the undo rename). timew undo then reports success and deletes an interval committed before the crash:
export TIMEWARRIORDB=$(mktemp -d)
timew track 2020-01-01T10:00 - 2020-01-01T11:00 alpha :yes
cp "$TIMEWARRIORDB/data/undo.data" /tmp/undo.before
timew track 2020-01-02T10:00 - 2020-01-02T11:00 beta :yes
cp /tmp/undo.before "$TIMEWARRIORDB/data/undo.data" # the crash window
timew undo # prints "Undo", exits 0
timew export # alpha is gone; beta survives
Case 2 — tags.data misses the last update (killed after the undo rename, before the tags rename). timew undo then aborts and stays unusable until the file is repaired by hand:
export TIMEWARRIORDB=$(mktemp -d)
timew track 2020-01-01T10:00 - 2020-01-01T11:00 alpha :yes
cp "$TIMEWARRIORDB/data/tags.data" /tmp/tags.before
timew track 2020-01-02T10:00 - 2020-01-02T11:00 beta :yes
cp /tmp/tags.before "$TIMEWARRIORDB/data/tags.data" # the crash window
timew undo # "Trying to decrement non-existent tag 'beta'", exits 255
The window is only a few syscalls wide, but the inconsistent state is durable, and nothing reads it until the next timew undo — possibly days later. Case 1 is silent loss of committed data; case 2 leaves undo broken.
Could you confirm this analysis? I can provide more detail if useful.
AtomicFile::finalize_all()renames the database files one after another — month data, thenundo.data, thentags.data. A process killed between two renames (SIGKILL, OOM killer, hard reboot) leaves files that are each intact but inconsistent with each other. The signal masking from #316 cannot block SIGKILL, and this is a different mechanism from #772: no fsync is involved, and an fsync fix would not close this window.Confirmed on 1.4.3 (Debian bookworm) and on
develop@ db7751c, built from source. I found the two states below with a crash-consistency test harness I wrote (it SIGKILLs the process immediately before each state-directory syscall in turn) and verified them by hand; the reverted file is byte-for-byte what a kill between the renames leaves behind.Case 1 — the journal misses the last transaction (killed after the data rename, before the undo rename).
timew undothen reports success and deletes an interval committed before the crash:Case 2 — tags.data misses the last update (killed after the undo rename, before the tags rename).
timew undothen aborts and stays unusable until the file is repaired by hand:The window is only a few syscalls wide, but the inconsistent state is durable, and nothing reads it until the next
timew undo— possibly days later. Case 1 is silent loss of committed data; case 2 leaves undo broken.Could you confirm this analysis? I can provide more detail if useful.