Reddog
Reddog

Reputation: 15579

Service Fabric self-deleting service

I'd like to add a service that executes some initialization operations for the system when it's first created.

I'd imagine it would be a stateless service (with cluster admin rights) that should self-destruct when it's done it's thing. I am under the impression that exiting the RunAsync function allows me to indicate that I'm finished (or in an error state). However, then it still hangs around on the application's context and annoyingly looking like it's "active" when it's not really doing anything at all.

Is it possible for a service to remove itself?

I think maybe we could try using the FabricClient.ServiceManager's DeleteServiceAsync (using parameters based on the service context) inside an OnCloseAsync override but I've not been able to prove that might work and it feels a little funky:

var client = new FabricClient();
await client.ServiceManager.DeleteServiceAsync(new DeleteServiceDescription(Context.ServiceName));

Is there a better way?

Upvotes: 0

Views: 507

Answers (2)

Adriaan de Beer
Adriaan de Beer

Reputation: 1286

As mentioned already, returning from RunAsync will end this method only, but the service will continue to run and hence not be deleted.

DeleteServiceAsync certainly is the way to go - however it's not quite as simple as just calling it because if you're not careful it will deadlock on the current thread (especially in local developer cluster). You would also likely get a few short-lived health warnings about RunAsync taking a long time to terminate and/or target replica size not being met.

In any case - solution is quite simple - just do this:

private async Task DeleteSelf(CancellationToken cancellationToken)
{
       using (var client = new FabricClient())
       {
            await client.ServiceManager.DeleteServiceAsync(new DeleteServiceDescription(this.Context.ServiceName), TimeSpan.FromMinutes(1), cancellationToken);
       }
}

Then, in last line of my RunAsync method I call:

await DeleteSelf(cancellationToken).ConfigureAwait(false);

The ConfigureAwait(false) will help with deadlock issue as it will essentially return to a new thread synchronization context - i.e. not try to return to "caller context".

Upvotes: 1

masnider
masnider

Reputation: 2599

Returning from RunAsync will end the code in RunAsync (indicate completion), so SF won't start RunAsync again (It would if it returned an exception, for example). RunAsync completion doesn't cause the service to be deleted. As mentioned, for example, the service might be done with background work but still listening for incoming messages.

The best way to shut down a service is to call DeleteServiceAsync. This can be done by the service itself or another service, or from outside the cluster. Services can self-delete, so for services whose work is done we typically see await DeleteServiceAsync as the last line of RunAsync, after which the method just exits. Something like:

RunAsync(CancellationToken ct)
{
    while(!workCompleted && !ct.IsCancellationRequested)
    {
        if(!DoneWithWork())
        {
            DoWork()
        }

        if(DoneWithWork())
        {
            workCompleted == true;
            await DeleteServiceAsync(...) 
        }
    }
}

The goal is to ensure that if your service is actually done doing the work it cleans itself up, but doesn't trigger its own deletion for the other reasons that a CancellationToken can get signaled, such as shutting down due to some upgrade or cluster resource balancing.

Upvotes: 4

Related Questions