Wails version family
v3 (alpha/beta)
Exact Wails version
v3.0.0-beta.15 (sites verified identical at master 9d49e8e and at beta.9/beta.12)
Operating System
All (the os.Exit call is in shared code; darwin/linux have one extra exit site each)
Description
When SingleInstance detects an already-running instance, application.New calls os.Exit directly:
v3/pkg/application/application.go:206-218 — on alreadyRunningError: notifyFirstInstance(), then os.Exit(appOptions.SingleInstance.ExitCode)
- plus
single_instance_darwin.go:82 and single_instance_linux.go:99
os.Exit terminates without running deferred functions (Go spec), so any cleanup the caller scheduled before calling New() — flushing a write-ahead file, releasing a lock file, closing a log — is silently skipped in the second process. New() looks like an ordinary constructor, so having opened resources before it is natural; the constraint is invisible in the API.
In our production app (gadak) the second-instance exit was skipping a persistence flush scheduled with defer. We had to reorder initialization so nothing requiring cleanup exists before New(), and pin that ordering with a test — that works, but every SingleInstance adopter has to discover it the same way.
What this issue is not asking: SingleInstance.ExitCode already covers the exit value (#3723), and the second instance correctly must not continue into Run() and show a window — no change to that. The ask is a seam for cleanup, in either shape:
New() returns a distinguished error (e.g. errors.Is(err, application.ErrAlreadyRunning)) after notifyFirstInstance(), and the caller exits — possibly opt-in to avoid breaking apps that don't check the error; or
- a hook, e.g.
SingleInstance.OnSecondInstanceExit func(), invoked after notifyFirstInstance() and before os.Exit.
Related: #3301 ("in idiomatic Go we return an error"); #4066 already converted service-startup fatals into returned errors; #5170 recently touched this path. Happy to send a PR for whichever shape you prefer.
To Reproduce
SingleInstance enabled; in main(), open any resource needing cleanup and schedule it with defer (e.g. defer f.Sync()), then call application.New(...).
- Start the app once, then start a second instance.
- The second process exits with
ExitCode — the deferred cleanup never runs (observable with a defer that writes a sentinel file: the file is not written).
Expected behaviour
A way for the second instance to run its cleanup before terminating — via a returned error or a pre-exit hook — while wails keeps owning the "second instance does not open a window" behavior.
Attempted Fixes
Reordered our initialization so that nothing requiring cleanup exists before New(), pinned with a test. Works, but is a per-app rediscovery of an API-invisible constraint.
Wails version family
v3 (alpha/beta)
Exact Wails version
v3.0.0-beta.15 (sites verified identical at master
9d49e8eand at beta.9/beta.12)Operating System
All (the
os.Exitcall is in shared code; darwin/linux have one extra exit site each)Description
When
SingleInstancedetects an already-running instance,application.Newcallsos.Exitdirectly:v3/pkg/application/application.go:206-218— onalreadyRunningError:notifyFirstInstance(), thenos.Exit(appOptions.SingleInstance.ExitCode)single_instance_darwin.go:82andsingle_instance_linux.go:99os.Exitterminates without running deferred functions (Go spec), so any cleanup the caller scheduled before callingNew()— flushing a write-ahead file, releasing a lock file, closing a log — is silently skipped in the second process.New()looks like an ordinary constructor, so having opened resources before it is natural; the constraint is invisible in the API.In our production app (gadak) the second-instance exit was skipping a persistence flush scheduled with
defer. We had to reorder initialization so nothing requiring cleanup exists beforeNew(), and pin that ordering with a test — that works, but everySingleInstanceadopter has to discover it the same way.What this issue is not asking:
SingleInstance.ExitCodealready covers the exit value (#3723), and the second instance correctly must not continue intoRun()and show a window — no change to that. The ask is a seam for cleanup, in either shape:New()returns a distinguished error (e.g.errors.Is(err, application.ErrAlreadyRunning)) afternotifyFirstInstance(), and the caller exits — possibly opt-in to avoid breaking apps that don't check the error; orSingleInstance.OnSecondInstanceExit func(), invoked afternotifyFirstInstance()and beforeos.Exit.Related: #3301 ("in idiomatic Go we return an error"); #4066 already converted service-startup fatals into returned errors; #5170 recently touched this path. Happy to send a PR for whichever shape you prefer.
To Reproduce
SingleInstanceenabled; inmain(), open any resource needing cleanup and schedule it withdefer(e.g.defer f.Sync()), then callapplication.New(...).ExitCode— the deferred cleanup never runs (observable with a defer that writes a sentinel file: the file is not written).Expected behaviour
A way for the second instance to run its cleanup before terminating — via a returned error or a pre-exit hook — while wails keeps owning the "second instance does not open a window" behavior.
Attempted Fixes
Reordered our initialization so that nothing requiring cleanup exists before
New(), pinned with a test. Works, but is a per-app rediscovery of an API-invisible constraint.