Add `prestart`/`poststart`/`prestop`/`poststop` settings

pull/485/head
NextTurn 2020-07-20 00:00:00 +08:00 committed by Next Turn
parent f02e63486b
commit e96fc2b4f3
5 changed files with 181 additions and 36 deletions

View File

@ -0,0 +1,13 @@
namespace WinSW.Configuration
{
internal static class SettingNames
{
internal const string Arguments = "arguments";
internal const string Executable = "executable";
internal const string Poststart = "poststart";
internal const string Poststop = "poststop";
internal const string Prestart = "prestart";
internal const string Prestop = "prestop";
internal const string Service = "service";
}
}

View File

@ -8,6 +8,7 @@ using System.Xml;
using WinSW.Configuration;
using WinSW.Native;
using WinSW.Util;
using Names = WinSW.Configuration.SettingNames;
namespace WinSW
{
@ -250,6 +251,22 @@ namespace WinSW
}
}
public string? PrestartExecutable => this.GetExecutable(Names.Prestart);
public string? PrestartArguments => this.GetArguments(Names.Prestart);
public string? PoststartExecutable => this.GetExecutable(Names.Poststart);
public string? PoststartArguments => this.GetArguments(Names.Poststart);
public string? PrestopExecutable => this.GetExecutable(Names.Prestop);
public string? PrestopArguments => this.GetArguments(Names.Prestop);
public string? PoststopExecutable => this.GetExecutable(Names.Poststop);
public string? PoststopArguments => this.GetArguments(Names.Poststop);
public string WorkingDirectory
{
get
@ -727,5 +744,17 @@ namespace WinSW
return environment;
}
private string? GetExecutable(string name)
{
string? text = this.dom.SelectSingleNode(Names.Service)?.SelectSingleNode(name)?.SelectSingleNode(Names.Executable)?.InnerText;
return text is null ? null : Environment.ExpandEnvironmentVariables(text);
}
private string? GetArguments(string name)
{
string? text = this.dom.SelectSingleNode(Names.Service)?.SelectSingleNode(name)?.SelectSingleNode(Names.Arguments)?.InnerText;
return text is null ? null : Environment.ExpandEnvironmentVariables(text);
}
}
}

View File

@ -425,5 +425,49 @@ $@"<service>
var sd = bldr.ToServiceDescriptor();
Assert.Equal(enabled, sd.DelayedAutoStart);
}
[Fact]
public void Additional_Executable_And_Arguments()
{
const string prestartExecutable = "1";
const string prestartArguments = "2";
const string poststartExecutable = "3";
const string poststartArguments = "4";
const string prestopExecutable = "5";
const string prestopArguments = "6";
const string poststopExecutable = "7";
const string poststopArguments = "8";
string seedXml =
$@"<service>
<prestart>
<executable>{prestartExecutable}</executable>
<arguments>{prestartArguments}</arguments>
</prestart>
<poststart>
<executable>{poststartExecutable}</executable>
<arguments>{poststartArguments}</arguments>
</poststart>
<prestop>
<executable>{prestopExecutable}</executable>
<arguments>{prestopArguments}</arguments>
</prestop>
<poststop>
<executable>{poststopExecutable}</executable>
<arguments>{poststopArguments}</arguments>
</poststop>
</service>";
ServiceDescriptor descriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Equal(prestartExecutable, descriptor.PrestartExecutable);
Assert.Equal(prestartArguments, descriptor.PrestartArguments);
Assert.Equal(poststartExecutable, descriptor.PoststartExecutable);
Assert.Equal(poststartArguments, descriptor.PoststartArguments);
Assert.Equal(prestopExecutable, descriptor.PrestopExecutable);
Assert.Equal(prestopArguments, descriptor.PrestopArguments);
Assert.Equal(poststopExecutable, descriptor.PoststopExecutable);
Assert.Equal(poststopArguments, descriptor.PoststopArguments);
}
}
}

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net461;netcoreapp3.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PublishTrimmed>true</PublishTrimmed>

View File

@ -225,6 +225,21 @@ namespace WinSW
throw new AggregateException(exceptions);
}
try
{
string? prestartExecutable = this.descriptor.PrestartExecutable;
if (prestartExecutable != null)
{
using Process process = this.StartProcess(prestartExecutable, this.descriptor.PrestartArguments);
this.WaitForProcessToExit(process);
Log.Info($"Pre-start process '{GetDisplayName(process)}' exited with code {process.ExitCode}.");
}
}
catch (Exception e)
{
Log.Error(e);
}
string? startArguments = this.descriptor.StartArguments;
if (startArguments is null)
@ -253,6 +268,26 @@ namespace WinSW
this.ExtensionManager.FireOnProcessStarted(this.process);
this.process.StandardInput.Close(); // nothing for you to read!
try
{
string? poststartExecutable = this.descriptor.PoststartExecutable;
if (poststartExecutable != null)
{
using Process process = this.StartProcess(poststartExecutable, this.descriptor.PoststartArguments);
process.Exited += (sender, _) =>
{
Process process = (Process)sender!;
Log.Info($"Post-start process '{GetDisplayName(process)}' exited with code {process.ExitCode}.");
};
process.EnableRaisingEvents = true;
}
}
catch (Exception e)
{
Log.Error(e);
}
}
protected override void OnShutdown()
@ -293,6 +328,21 @@ namespace WinSW
/// </summary>
private void StopIt()
{
try
{
string? prestopExecutable = this.descriptor.PrestopExecutable;
if (prestopExecutable != null)
{
using Process process = this.StartProcess(prestopExecutable, this.descriptor.PrestopArguments);
this.WaitForProcessToExit(process);
Log.Info($"Pre-stop process '{GetDisplayName(process)}' exited with code {process.ExitCode}.");
}
}
catch (Exception e)
{
Log.Error(e);
}
string? stopArguments = this.descriptor.StopArguments;
this.LogEvent("Stopping " + this.descriptor.Id);
Log.Info("Stopping " + this.descriptor.Id);
@ -307,7 +357,7 @@ namespace WinSW
}
else
{
this.SignalShutdownPending();
this.SignalPending();
stopArguments += " " + this.descriptor.Arguments;
@ -324,6 +374,21 @@ namespace WinSW
this.WaitForProcessToExit(stopProcess);
}
try
{
string? poststopExecutable = this.descriptor.PoststopExecutable;
if (poststopExecutable != null)
{
using Process process = this.StartProcess(poststopExecutable, this.descriptor.PoststopArguments);
this.WaitForProcessToExit(process);
Log.Info($"Post-stop process '{GetDisplayName(process)}' exited with code {process.ExitCode}.");
}
}
catch (Exception e)
{
Log.Error(e);
}
// Stop extensions
this.ExtensionManager.FireBeforeWrapperStopped();
@ -335,48 +400,23 @@ namespace WinSW
Log.Info("Finished " + this.descriptor.Id);
}
private void WaitForProcessToExit(Process processoWait)
private void WaitForProcessToExit(Process process)
{
this.SignalShutdownPending();
this.SignalPending();
int effectiveProcessWaitSleepTime;
if (this.descriptor.SleepTime.TotalMilliseconds > int.MaxValue)
int processWaitHint = (int)Math.Min(this.descriptor.SleepTime.TotalMilliseconds, int.MaxValue);
while (!process.WaitForExit(processWaitHint))
{
Log.Warn("The requested sleep time " + this.descriptor.SleepTime.TotalMilliseconds + "is greater that the max value " +
int.MaxValue + ". The value will be truncated");
effectiveProcessWaitSleepTime = int.MaxValue;
this.SignalPending();
}
else
{
effectiveProcessWaitSleepTime = (int)this.descriptor.SleepTime.TotalMilliseconds;
}
// WriteEvent("WaitForProcessToExit [start]");
while (!processoWait.WaitForExit(effectiveProcessWaitSleepTime))
{
this.SignalShutdownPending();
// WriteEvent("WaitForProcessToExit [repeat]");
}
// WriteEvent("WaitForProcessToExit [finished]");
}
private void SignalShutdownPending()
private void SignalPending()
{
int effectiveWaitHint;
if (this.descriptor.WaitHint.TotalMilliseconds > int.MaxValue)
{
Log.Warn("The requested WaitHint value (" + this.descriptor.WaitHint.TotalMilliseconds + " ms) is greater that the max value " +
int.MaxValue + ". The value will be truncated");
effectiveWaitHint = int.MaxValue;
}
else
{
effectiveWaitHint = (int)this.descriptor.WaitHint.TotalMilliseconds;
}
int serviceWaitHint = (int)Math.Min(this.descriptor.WaitHint.TotalMilliseconds, int.MaxValue);
this.RequestAdditionalTime(effectiveWaitHint);
this.RequestAdditionalTime(serviceWaitHint);
}
private void SignalShutdownComplete()
@ -428,5 +468,24 @@ namespace WinSW
redirectStdin: redirectStdin,
hideWindow: this.descriptor.HideWindow);
}
private Process StartProcess(string executable, string? arguments)
{
var info = new ProcessStartInfo(executable, arguments)
{
UseShellExecute = false,
RedirectStandardInput = true,
WorkingDirectory = this.descriptor.WorkingDirectory,
};
Process process = Process.Start(info);
process.StandardInput.Close();
return process;
}
private static string GetDisplayName(Process process)
{
return $"{process.ProcessName} ({process.Id})";
}
}
}