I have a console application (dotnet core, ubuntu), that looks like following:
void Main()
{
try
{
var job = new Job();
job.Start();
while(Console.ReadLine() != "quit");
}
catch(Exception e)
{
//some handling
}
}
While Job is implementation of JobAbstract class:
public class JobAbstract
{
private readonly int _periodMs;
protected JobAbstract(int periodMs)
{
_periodMs = periodMs;
}
public bool Working { get; private set; }
public abstract Task Execute();
private void LogFatalError(Exception exception)
{
try
{
//logging
}
catch (Exception)
{
}
}
private async Task ThreadMethod()
{
while (Working)
{
try
{
await Execute();
}
catch (Exception exception)
{
LogFatalError(exception);
}
await Task.Delay(_periodMs);
}
}
public virtual void Start()
{
if (Working)
return;
Working = true;
Task.Run(async () => { await ThreadMethod(); });
}
public void Stop()
{
Working = false;
}
}
Job is defined like:
public class Job : JobAbstract
{
public Job(periodMs) : base(periodMs)
{}
public override async Task Execute()
{
await SomeTask();
OtherKindOfJob();
await MaybeMoreAsyncTasks();
// etc
}
}
It all works fine, as you might expect.
Now, I'm wrapping it all up in docker containers for continuous delivery. docker stop might be run on a container while Execute method of Job is run. In order to wait for the cycle to end and then exit gracefully, I've decided to use this approach:
public static void Main(string[] args)
{
var ended = new ManualResetEventSlim();
var starting = new ManualResetEventSlim();
AssemblyLoadContext.Default.Unloading += ctx =>
{
System.Console.WriteLine("Unloding fired");
starting.Set();
System.Console.WriteLine("Waiting for completion");
ended.Wait();
};
System.Console.WriteLine("Waiting for signals");
starting.Wait();
System.Console.WriteLine("Received signal gracefully shutting down");
Thread.Sleep(5000);
ended.Set();
}
I've tested it and it works, when calling docker stop, docker daemon sends SIGTERM signal to the process #1 of the container (which happens to be my app) and CoreCLR invokes AssemblyLoadContext.Default.Unloading event which is handled appropriately.
So I have a working app and a way (theoretically) how to stop it. I'm just not sure, how should I implement it for a given context. Should I send some kind of token to the Job? I want the run flow to be like following:
- The app is running normally.
SIGTERM is received. If Execute isn't running, stop the app, if it is - wait for it to end.
- When the
Execute ends, end the app.
How this kind of thing should be implemented? Please advice some kind of scheme or pattern to achieve that. Thanks in advance!