Skip to content

Commit 07a2acf

Browse files
committed
feat: exponential backoff delay when restaring modules
1 parent 0947882 commit 07a2acf

File tree

1 file changed

+58
-33
lines changed

1 file changed

+58
-33
lines changed

src-tauri/src/manager.rs

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -387,46 +387,71 @@ fn handle(rx: Receiver<ModuleMessage>, state: Arc<Mutex<ManagerState>>) {
387387
} else {
388388
error!("Module {name} exited with error status");
389389
thread::spawn(move || {
390-
thread::sleep(Duration::from_secs(1));
391-
let state = &mut state_clone
392-
.lock()
393-
.expect("Failed to acquire manager_state lock");
394-
let restart_count =
395-
state.modules_restart_count.get(&name_clone).unwrap_or(&0);
396-
397-
let pending_shutdown = state
398-
.modules_pending_shutdown
399-
.get(&name_clone)
400-
.unwrap_or(&false);
401-
402-
if *pending_shutdown {
403-
return;
404-
}
405-
if *restart_count < 3 {
406-
let new_count = *restart_count + 1;
407-
state
408-
.modules_restart_count
409-
.insert(name_clone.clone(), new_count);
410-
// Get the stored arguments for this module
411-
let stored_args =
412-
state.modules_args.get(&name_clone).cloned().flatten();
413-
state.start_module(&name_clone, stored_args.as_ref());
414-
let app = &*get_app_handle().lock().expect("Failed to get app handle");
390+
let (should_restart, restart_info) = {
391+
let state = &mut state_clone
392+
.lock()
393+
.expect("Failed to acquire manager_state lock");
394+
let restart_count =
395+
state.modules_restart_count.get(&name_clone).unwrap_or(&0);
396+
397+
let pending_shutdown = state
398+
.modules_pending_shutdown
399+
.get(&name_clone)
400+
.unwrap_or(&false);
415401

416-
app.dialog()
417-
.message(format!("{name_clone} crashed. Restarting..."))
418-
.kind(MessageDialogKind::Warning)
419-
.title("Warning")
420-
.show(|_| {});
421-
error!("Module {name_clone} crashed and is being restarted");
402+
// If shutdown is pending, exit early
403+
if *pending_shutdown {
404+
return; // Exit the entire thread
405+
}
406+
407+
if *restart_count < 3 {
408+
// Exponential backoff: 2^(restart_count + 1) seconds
409+
// restart_count 0 -> 2 seconds, 1 -> 4 seconds, 2 -> 8 seconds
410+
let delay_secs = 2u64.pow(*restart_count + 1);
411+
info!("Module {name_clone} will restart in {delay_secs} seconds (attempt {} of 3)", *restart_count + 1);
412+
(true, Some((delay_secs, *restart_count)))
413+
} else {
414+
(false, None)
415+
}
416+
// state is automatically dropped here when the block ends
417+
};
418+
419+
if should_restart {
420+
if let Some((secs, restart_count)) = restart_info {
421+
// Show dialog BEFORE sleeping
422+
let app =
423+
&*get_app_handle().lock().expect("Failed to get app handle");
424+
app.dialog()
425+
.message(format!("{name_clone} crashed. Restarting..."))
426+
.kind(MessageDialogKind::Warning)
427+
.title("Warning")
428+
.show(|_| {});
429+
error!("Module {name_clone} crashed and will be restarted");
430+
431+
thread::sleep(Duration::from_secs(secs));
432+
433+
let state = &mut state_clone
434+
.lock()
435+
.expect("Failed to acquire manager_state lock");
436+
437+
state
438+
.modules_restart_count
439+
.insert(name_clone.clone(), restart_count + 1);
440+
// Get the stored arguments for this module
441+
let stored_args =
442+
state.modules_args.get(&name_clone).cloned().flatten();
443+
state.start_module(&name_clone, stored_args.as_ref());
444+
}
422445
} else {
423-
// Prevent further restarts
446+
// Restart limit reached
447+
let state = &mut state_clone
448+
.lock()
449+
.expect("Failed to acquire manager_state lock");
424450
state
425451
.modules_pending_shutdown
426452
.insert(name_clone.clone(), true);
427453

428454
let app = &*get_app_handle().lock().expect("Failed to get app handle");
429-
430455
app.dialog()
431456
.message(format!(
432457
"{name_clone} keeps on crashing. Restart limit reached."

0 commit comments

Comments
 (0)