From 68f0d68980a5ca92f646fb6cb33c128ef06e0f62 Mon Sep 17 00:00:00 2001 From: rouggy Date: Mon, 31 Aug 2026 14:19:30 +0200 Subject: [PATCH] fix(restart): the self-relaunch waits for its parent's mutex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A database switch relaunches OpsLog, and the child regularly won the race against the old instance's teardown — so following the app's own instructions produced 'OpsLog is already running'. The relaunch now passes --relaunch, which gets the same 20-second mutex patience the post-update restart has always had. Opens the 0.27.6 block. --- app.go | 4 +++- changelog.json | 10 ++++++++++ main.go | 16 ++++++++++------ 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/app.go b/app.go index 99ed2ce..d87cca0 100644 --- a/app.go +++ b/app.go @@ -2851,7 +2851,9 @@ func (a *App) RestartApp() error { if err != nil { return fmt.Errorf("locate executable: %w", err) } - cmd := exec.Command(exe) + // --relaunch: the child waits for OUR mutex instead of declaring us a + // duplicate — this instance is quitting, just not always fast enough. + cmd := exec.Command(exe, "--relaunch") cmd.Dir = filepath.Dir(exe) if err := cmd.Start(); err != nil { return fmt.Errorf("relaunch OpsLog: %w", err) diff --git a/changelog.json b/changelog.json index 49869c7..e10768c 100644 --- a/changelog.json +++ b/changelog.json @@ -1,4 +1,14 @@ [ + { + "version": "0.27.6", + "date": "", + "en": [ + "Switching the settings database no longer shows “OpsLog is already running”: the automatic relaunch now waits for the closing instance to release its lock instead of racing it." + ], + "fr": [ + "Changer de base de réglages n’affiche plus « OpsLog is already running » : la relance automatique attend désormais que l’instance qui se ferme libère son verrou au lieu de la prendre de vitesse." + ] + }, { "version": "0.27.5", "date": "", diff --git a/main.go b/main.go index f3081b1..da3aa6b 100644 --- a/main.go +++ b/main.go @@ -48,14 +48,14 @@ func hasFlag(args []string, flag string) bool { } // acquireInstance grabs the single-instance mutex. On a normal launch it's a plain -// try (fail → another OpsLog is running, so exit). On a --post-update relaunch the -// previous instance may still be shutting down and holding the mutex, so retry for -// a few seconds until it frees. -func acquireInstance(postUpdate bool) bool { +// try (fail → another OpsLog is running, so exit). On a --post-update or +// --relaunch start the previous instance may still be shutting down and holding +// the mutex, so retry for a few seconds until it frees. +func acquireInstance(wait bool) bool { if acquireSingleInstance() { return true } - if !postUpdate { + if !wait { return false } deadline := time.Now().Add(20 * time.Second) @@ -90,7 +90,11 @@ func main() { // to free instead of bailing out. Then clear the old exe it left behind. bootLogLaunch() postUpdate := hasFlag(os.Args[1:], "--post-update") - if !acquireInstance(postUpdate) { + // A self-relaunch (database switch) races its own parent: the new process + // regularly wins the start against the old one's teardown, and the operator + // got "OpsLog is already running" for following instructions. Same patience + // as post-update. + if !acquireInstance(postUpdate || hasFlag(os.Args[1:], "--relaunch")) { // SAID, not merely done. This exit is correct — a second instance would // fight the first over the rig — but it happened in total silence: no // window, no data folder, no log, which is indistinguishable from a