fix launcher not shutting down when a startup process doesn't properly/takes too long to shutdown

fixes #110
This commit is contained in:
Chris
2023-08-19 16:18:26 -06:00
parent 479f7b5e77
commit f9abbf5220
7 changed files with 31 additions and 13 deletions

View File

@ -2,6 +2,11 @@
## [Unreleased]
### Bug Fixes
- Fix launcher not shutting down when a startup process doesn't properly shutdown or takes too long to shutdown.
The timeout for taking too long can be configured via the new `shutdownTimeout` property in the [settings file].
It defaults to 10 seconds.
### Misc
- Update Frontier auth API to use v3.0 endpoints to match changes in default launcher

View File

@ -190,6 +190,7 @@ Linux: `$XDG_CONFIG_HOME/min-ed-launcher/settings.json` (`~/.config` if `$XDG_CO
| forceUpdate | By default, Steam and Epic updates are handled by their respective platform. In cases like the Odyssey alpha, FDev doesn't provide updates through Steam or Epic. This allows the launcher to force updates to be done via FDev servers by providing a comma delimited list of SKUs |
| processes | Additional applications to launch before launching the game |
| shutdownProcesses | Additional applications to launch after game has shutdown |
| shutdownTimeout | Time, in seconds, to wait for additional applications to shutdown before forcefully terminating them |
| filterOverrides | Manually override a product's filter for use with launch options filter flag (e.g. /edo, /edh, etc...) |
| additionalProducts | Provide extra products to the authorized product list. Useful for launching Horizons 4.0 when you own the Odyssey DLC |
@ -222,6 +223,7 @@ double backslash (`\\`) instead of a single backslash (`\`).
"arguments": "--arg1 --arg2"
}
],
"shutdownTimeout": 10,
"filterOverrides": [
{ "sku": "FORC-FDEV-DO-1000", "filter": "edo" },
{ "sku": "FORC-FDEV-DO-38-IN-40", "filter": "edh4" }

View File

@ -416,7 +416,7 @@ let run settings launcherVersion cancellationToken = taskResult {
if not cancellationToken.IsCancellationRequested then
let startProcesses = Process.launchProcesses startProcesses
launchProduct settings.DryRun settings.CompatTool processArgs settings.Restart selectedProduct.Name p
Process.stopProcesses startProcesses
Process.stopProcesses settings.ShutdownTimeout startProcesses
settings.ShutdownProcesses |> List.iter (fun p -> Log.info $"Starting process %s{p.FileName}")
Process.launchProcesses shutdownProcesses |> Process.writeOutput
return 0

View File

@ -26,7 +26,7 @@ let private terminate (p: Process) =
if p.CloseMainWindow() then
Ok ()
else
Error ""
Error "Process has no main window or the main window is disabled"
#else
open Microsoft.FSharp.NativeInterop
@ -60,12 +60,18 @@ let private terminate (p: Process) =
let ansiColorSupported() = not (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TERM")))
#endif
let termProcess (p: Process) =
let termProcess (timeout: TimeSpan) (p: Process) =
if p.HasExited then
Ok ()
else
match terminate p with
| Ok _ -> Ok ()
| Error msg ->
terminate p
|> Result.bind(fun _ ->
if p.WaitForExit(timeout) then
Ok ()
else
Error $"Process took longer than %f{timeout.TotalSeconds} seconds to shutdown"
)
|> Result.mapError (fun msg ->
p.Kill(true)
Error $"Unable to gracefully stop %s{p.ProcessName}. Killed process instead. %s{msg}"
$"Unable to gracefully stop %s{p.ProcessName}. Killed process instead. %s{msg}"
)

View File

@ -19,7 +19,7 @@ let launchProcesses (processes:ProcessStartInfo list) =
Log.exn e $"Unable to start process %s{p.FileName}"
None)
let stopProcesses (processes: Process list) =
let stopProcesses timeout (processes: Process list) =
processes
|> List.iter (fun p ->
use p = p
@ -27,7 +27,7 @@ let stopProcesses (processes: Process list) =
Log.debug $"Process %i{p.Id} already exited"
else
Log.debug $"Stopping process %s{p.ProcessName}"
match Interop.termProcess p with
match Interop.termProcess timeout p with
| Ok () ->
p.StandardOutput.ReadToEnd() |> ignore
p.StandardError.ReadToEnd() |> ignore

View File

@ -29,7 +29,8 @@ let defaults =
ShutdownProcesses = List.empty
FilterOverrides = OrdinalIgnoreCaseMap.empty
AdditionalProducts = List.empty
DryRun = false }
DryRun = false
ShutdownTimeout = TimeSpan.FromSeconds(10) }
[<RequireQualifiedAccess>]
type FrontierCredResult = Found of string * string * string option | NotFound of string | UnexpectedFormat of string | Error of string
@ -171,7 +172,9 @@ type Config =
Processes: ProcessConfig list
ShutdownProcesses: ProcessConfig list
FilterOverrides: FilterConfig list
AdditionalProducts: AuthorizedProduct list }
AdditionalProducts: AuthorizedProduct list
[<DefaultValue("10")>]
ShutdownTimeout: int }
let parseConfig fileName =
let configRoot = ConfigurationBuilder()
.AddJsonFile(fileName, false)
@ -254,4 +257,5 @@ let getSettings args appDir fileConfig = task {
ShutdownProcesses = shutdownProcesses
FilterOverrides = filterOverrides
WatchForCrashes = fileConfig.WatchForCrashes
AdditionalProducts = fileConfig.AdditionalProducts }) }
AdditionalProducts = fileConfig.AdditionalProducts
ShutdownTimeout = TimeSpan.FromSeconds(fileConfig.ShutdownTimeout) }) }

View File

@ -125,7 +125,8 @@ type LauncherSettings =
ShutdownProcesses: ProcessStartInfo list
FilterOverrides: OrdinalIgnoreCaseMap<string>
AdditionalProducts: AuthorizedProduct list
DryRun: bool }
DryRun: bool
ShutdownTimeout: TimeSpan }
type ProductMode = Online | Offline
type VersionInfo =
{ Name: string