Skip to content

Extension methods to add leader election startup configuration to Topshelf.

License

Notifications You must be signed in to change notification settings

stevewgh/Topshelf.Leader

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

73 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Topshelf.Leader Build Status

An extension method to the Topshelf ServiceConfigurator<T> class that adds Leader checking to the service startup.

When would I use this?

Use it when your services require active / passive or any form of high availablility where the services aren't able to naturally compete.

  • Catch up suscriptions that don't allow competing consumers
  • Services that can operate in an Active / Passive configuration

When should I not use this?

Don't use this extension if you want a non-leader service to perform tasks whilst the leader service is also performing tasks. The design of the extension is that only one service is actively doing anything at any one time.

Getting started

Install-Package Topshelf.Leader

Once the package is installed, create a Console application and wireup the Topshelf service as you normally would except for the WhenStarted() method - this should no longer be used.

You should use the WhenStartedAsLeader() method that Topshelf.Leader provides instead which constains its own version of the WhenStarted() method, one with cancellation token support.

Example

using Topshelf.Leader;

public class Program
{
    static void Main(string[] args)
    {
      var rc = HostFactory.Run(x =>
      {
          x.Service<TheService>(s =>
          {
              s.WhenStartedAsLeader(builder =>
              {
                  builder.WhenStarted(async (service, token) =>
                  {
                      await service.Start(token);
                  });

                  builder.WithHeartBeat(
                      TimeSpan.FromSeconds(30),
                      (isLeader, token) =>
                      {
                          // Track metrics here
                          return Task.CompletedTask;
                      });
              });
              s.ConstructUsing(name => new TheService());
              s.WhenStopped(service => service.Stop());
          });
      });
  }
}

How does it work?

The WhenStarted() method will be executed when the service discovers that it is the current leader. If that situation changes the cancellation token will be set to cancelled. You decide how to handle this situation, throw an exception, exit gracefully or even carry on whatever you were doing - that's entirely up to you.

Example of a service which supports leadership change

    public class TestService
    {
        public async Task Start(CancellationToken stopToken)
        {
            while (!stopToken.IsCancellationRequested)
            {
                // do your work here, if it's async pass the stopToken to it
            }
        }

	public void Stop()
        {
            // Tidy up unmanaged resources
        }
    }

Lease Manager

The responsibility for deciding if your service is the leader is delegated to any class which implements the ILeaseManager interface. The process is as follows:

  1. The process will call ILeaseManager.AcquireLease() until we have obtained a lease (which means that we are the leader)
  2. If we are the leader, the delegate passed to the WhenStarted() builder method is run.
  3. Whilst we are running the ILeaseManager.RenewLease() method is called.
  4. When asked to stop the service we call ILeaseManager.ReleaseLease()

You configure which manager to use during the configuration stage. If one isn't supplied then an in memory manager is used. The in memory manager is not muti-process aware so it is not suitable for production use.

Consider using the Topshelf.Leader.AzureBlob lease manager for production environments. It is backed by Azure Blob Storage and is perfectly suited to production loads. If that doesn't meet your requirements it's easy to write your own.

Configuring the lease manager

var rc = HostFactory.Run(x =>
{
    x.Service<TheService>(s =>
    {
        s.WhenStartedAsLeader(builder =>
        {
            builder.WhenStarted(async (service, token) =>
            {
                await service.Start(token);
            });

            builder.Lease(lcb =>
            {
                lcb.RenewLeaseEvery(TimeSpan.FromSeconds(2))
					.AquireLeaseEvery(TimeSpan.FromSeconds(5))
					.LeaseLength(TimeSpan.FromDays(1))
					.WithLeaseManager(new YourManagerHere());
            });
        });
        s.ConstructUsing(name => new TheService());
        s.WhenStopped(service => service.Stop());
    });
}

Example of a simple lease manager

public class InMemoryLeaseManager : ILeaseManager
{
    private string owningNodeId;

    public InMemoryLeaseManager(string owningNodeId)
    {
        this.owningNodeId = owningNodeId;
    }

    public void AssignLeader(string newLeaderId)
    {
        this.owningNodeId = newLeaderId;
    }

    public Task<bool> AcquireLease(LeaseOptions options, CancellationToken token)
    {
        return Task.FromResult(options.NodeId == owningNodeId);
    }

    public Task<bool> RenewLease(LeaseOptions options, CancellationToken token)
    {
        return Task.FromResult(options.NodeId == owningNodeId);
    }

    public Task ReleaseLease(LeaseReleaseOptions options)
    {
	owningNodeId = string.Empty;
        return Task.FromResult(true);
    }
}

About

Extension methods to add leader election startup configuration to Topshelf.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages