mirror of https://github.com/winsw/winsw
Add `prestart`/`poststart`/`prestop`/`poststop` settings
parent
f02e63486b
commit
e96fc2b4f3
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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})";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue