6

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:

  1. The app is running normally.
  2. SIGTERM is received. If Execute isn't running, stop the app, if it is - wait for it to end.
  3. 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

2

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.

2
  • 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.
    – nicks
    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)?
    – nicks
    Dec 12, 2016 at 9:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.