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. IfExecute
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!
So, I figured something out: what if I add public Task CurrentIteration { get; private set; }
and change Execute();
to:
CurrentIteration = Execute();
await CurrentIteration;
and after starting.Wait();
in stopping sample, add:
job.Stop();
await job.CurrentIteration;
Will that work? Are there any issues with the following approach?
1 Answer 1
Looking at your code, I assume the whole logic of your application can be expressed as single Task Execute()
. And what you need is pretty simple task cancellation scheme.
Your code might look like :
public static void Main(string[] args)
{
Start();
}
public static async void Start()
{
var cts = new CancellationTokenSource();
var starting = new ManualResetEventSlim();
AssemblyLoadContext.Default.Unloading += ctx =>
{
System.Console.WriteLine("Unloding fired");
cts.RequestCancellation();
System.Console.WriteLine("Waiting for completion");
ended.Wait();
};
// run application
try
{
await Execute(cts.Token);
}
catch(TaskCancellationException ex)
{
// cancelled
}
ended.Set();
}
What I don't like is how you handle the stopping "signal" from the docker. I don't like that you basically "deadlock" the Unloading
event till the application quits. But I don't know if there is any other way as I don't have experience with docker and never had to handle "graceful" shutdown when application is terminated using SIGTERM
. Edit : Looking at this, your approach seems to be only real way to handle it. There is even one guy doing it same way as I suggest.
-
As you can see, invocation of Execute method is in a while loop, because it's a job and it's supposed to be run multiple times.nix– nix12/12/2016 09:39:54Commented Dec 12, 2016 at 9:39
-
Also, is it allowed/adviced to await for the same Task from different threads, like I'm doing in my proposed solution (see edit)?nix– nix12/12/2016 09:42:03Commented Dec 12, 2016 at 9:42
Explore related questions
See similar questions with these tags.