From f24e637dc90e623b2e937475d1ac6300fc6aa956 Mon Sep 17 00:00:00 2001 From: Stephen Boddy Date: Sun, 12 Jul 2026 21:51:56 +0100 Subject: [PATCH] Fix autosave timer self-unregister crash risk The periodic autosave timer called reset_timer() at the end of its own callback, which unregistered the timer that was still executing (itself). Blender frees the timer's internal registry entry on that manual unregister, then frees it again when the callback returns None - a double free that corrupts the heap and can crash Blender later, once the corrupted memory is reused. Reschedule by returning the next interval from the callback instead, which is the safe, documented way to repeat a bpy.app.timers callback. External reset_timer() calls (from SaveProject, LoadProject, AutosavePrompt) are unaffected since they run from a separate call stack (UI events), not from inside the timer. Found while investigating a segfault reported when cancelling the autosave recovery popup; not itself the cause of that crash (see the following commit), but the same reentrant-unregister pattern and a real, independent latent bug in the periodic reminder path. Generated with the assistance of an AI coding tool. (cherry picked from commit 6306ce0f80dc5097312437053325e5544910d94a) --- src/bonsai/bonsai/tool/autosave.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/bonsai/bonsai/tool/autosave.py b/src/bonsai/bonsai/tool/autosave.py index bf4a34690f..db55b12e05 100644 --- a/src/bonsai/bonsai/tool/autosave.py +++ b/src/bonsai/bonsai/tool/autosave.py @@ -96,9 +96,16 @@ class Autosave: if not cls.is_eligible(): return - def on_timer() -> None: + def on_timer() -> Union[float, None]: cls._on_timer_expired() - return None + # Reschedule by returning the next interval rather than calling + # reset_timer(), which would unregister this timer from within + # its own callback. Blender frees the timer's internal registry + # entry on that manual unregister, then frees it again when the + # callback returns - a double free that corrupts the heap and + # crashes Blender shortly after (e.g. when the prompt dialog + # spawned below is next interacted with). + return cls.get_interval_seconds() if cls.is_eligible() else None global _timer_callback _timer_callback = on_timer @@ -120,7 +127,6 @@ class Autosave: cls.perform_backup(bpy.context) except Exception as error: print(f"Bonsai: autosave backup failed: {error}") - cls.reset_timer() @classmethod def perform_backup(cls, context: bpy.types.Context) -> None: