]> git.hungrycats.org Git - bees/commitdiff
checkpoint: re-lock mutex on write failure in snapshot_point
authorZygo Blaxell <bees@furryterror.org>
Wed, 3 Jun 2026 05:48:03 +0000 (01:48 -0400)
committerZygo Blaxell <bees@furryterror.org>
Sat, 5 Sep 2026 04:03:56 +0000 (00:03 -0400)
snapshot_point() runs with m_mutex held and, in the non-deferred path,
temporarily released it across m_point_file.write() to keep the slow I/O
off the lock.  The release used a raw mutex::unlock() / mutex::lock()
pair, so a throw from write() unwound out of snapshot_point() with the
mutex left unlocked.  save_point() then called m_mutex.unlock() on the
unlocked mutex, which is undefined behaviour.

Wrap the write in a try/catch that re-locks before rethrowing, so the
postcondition "lock is held on return" holds in both the success and
exception paths.  This is the minimum fix; it does not change the
clean/dirty bookkeeping or the recovery semantics, just the mutex
state on the exception edge.

Assisted-by: Claude-Code:claude-opus-4-7
Signed-off-by: Zygo Blaxell <bees@furryterror.org>
src/bees-roots.cc

index 746e5ab2b6564ac4a3504c649a22123991e875d2..3325af47fd3aa6d89d8fb3320e8e8e8df04a4bd1 100644 (file)
@@ -1837,10 +1837,18 @@ BeesRoots::snapshot_point()
 
        if (!m_checkpoint_defer) {
                // Immediate write (current behaviour): write while lock is temporarily released.
-               // We must re-acquire the lock before updating m_crawl_clean.
-               // Use a raw unlock/lock here since snapshot_point() is called with lock held.
+               // We must re-acquire the lock before updating m_crawl_clean, and before
+               // returning to the caller (who holds the lock as a postcondition).  Use a
+               // try/catch so a throw from m_point_file.write() still re-locks the mutex
+               // before the exception propagates -- otherwise save_point() would call
+               // m_mutex.unlock() on an unlocked mutex.
                m_mutex.unlock();
-               m_point_file.write(ini_string);
+               try {
+                       m_point_file.write(ini_string);
+               } catch (...) {
+                       m_mutex.lock();
+                       throw;
+               }
                m_mutex.lock();
                m_crawl_clean = crawl_saved;
        } else {