Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Base Server: Abstract Class for Timer-based Jobs

DZone's Guide to

Base Server: Abstract Class for Timer-based Jobs

Many systems use background processes that host jobs. Check out this example of using​ Base Server, including what it is, sliding intervals, and running multiple servers.

· Java Zone
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

In one of my systems, I’m using a background process that hosts jobs that import and update some data and communicate with external services. These jobs are running after some interval or on specific moments. I generalized the common base part of these jobs so I can handle them as one in different background processes like Azure worker roles and Windows services. This blog post introduces my work and shows the real-life implementation of jobs base.

Here are some examples of tasks that my background service does:

  • Import telemetric data from external service,
  • Generate invoices,
  • Send out notifications using e-mail and SMS,
  • Update debtors list.

All these tasks happen continuously after some time interval.

There are also some problems to solve:

  • How to handle all these jobs same way in hosting processes?
  • How to avoid overlap of timer callbacks when some task takes more time than expected?

Base Server

My first implementation was a simple base server that handles the triggering of an actual job. As every different job may have different interval I added interval as an abstract property to base server class. The base server uses a timer to trigger Run() method that is implemented in an inherited class. Run() method is the place where the actual job is done.

NB! I added some console output to code to make it easier for you to test my code in Visual Studio. For real-life implementations, you should remove console output or, at least, find some better way to log what base server is doing.

abstract class BaseServer : IDisposable
{
    private object _lock = new object();
    private Timer _timer;

    public abstract int Interval { get; }
    protected abstract void Run();

    public void Start()
    {
        if (_timer == null)
            _timer = new Timer(TimerCallback, null, Interval, Interval);
        else
            _timer.Change(Interval, Interval);
    }    

    private void TimerCallback(object state)
    {
        if (Monitor.TryEnter(_lock))
        {
            Console.WriteLine("Lock entered");

            try
            {
                Run();
            }
            finally
            {
                Monitor.Exit(_lock);
                Console.WriteLine("Lock exited");
            }
        }
        else
        {
            Console.WriteLine("Lock is on");
        }
    }

    public void Stop()
    {
        _timer.Change(Timeout.Infinite, Timeout.Infinite);
    }

    public void Dispose()
    {
        if (_timer == null)
            return;

        _timer.Dispose();
        _timer = null;
    }
}

I’m using Monitor class to find out if some of previous timer callbacks is running. If there is previous callback still alive then the current one just exits because it can’t acquire a lock on locker attribute. If there’s no lock, then the callback calls the Run() method.

Sliding Interval

For some jobs, we need sliding a time interval. A sliding interval means that the timer is not triggering a callback constantly like in the code above, but stops the timer when the Run() method is called and waits for interval amount of time before invoking the callback again.

abstract class BaseServer : IDisposable
{
    private object _lock = new object();
    private Timer _timer;

    public abstract int Interval { get; }
    public abstract bool UseSlidingInterval { get; }
    protected abstract void Run();

    public void Start()
    {
        if (_timer == null)
            _timer = new Timer(TimerCallback, null, Interval, Interval);
        else
            _timer.Change(Interval, Interval);
    }    

    private void TimerCallback(object state)
    {
        if (Monitor.TryEnter(_lock))
        {
            Console.WriteLine("Lock entered");

            try
            {
                if (UseSlidingInterval)
                {
                    _timer.Change(Timeout.Infinite, Timeout.Infinite);
                }

                Run();
            }
            finally
            {
                if(UseSlidingInterval)
                {
                    _timer.Change(Interval, Interval);
                }

                Monitor.Exit(_lock);
                Console.WriteLine("Lock exited");
            }
        }
        else
        {
            Console.WriteLine("Lock is on");
        }
    }

    public void Stop()
    {
        _timer.Change(Timeout.Infinite, Timeout.Infinite);
    }

    public void Dispose()
    {
        if (_timer == null)
            return;

        _timer.Dispose();
        _timer = null;
    }
}

When a sliding interval is used, we stop the timer before calling Run() method. When Run() method gets the job done then the timer is activated again and before calling callback method it waits for the time given by the Interval property.

Running on Given Time

Invoice are sent out once per month and it happens automatically on the fifth day of every month. For this type of job, I made it possible for server implementation to tell next run time to base server.

abstract class BaseServer : IDisposable
{
    private object _lock = new object();
    private Timer _timer;

    public DateTime LastRunTime { get; private set; }

    public abstract bool SupportsInterval { get; }
    public abstract int Interval { get; }
    public abstract bool UseSlidingInterval { get; }
    public abstract DateTime NextRunTime { get; }
    protected abstract void Run();

    public void Start()
    {
        if (SupportsInterval)
        {
            if (_timer == null)
                _timer = new Timer(TimerCallback, null, Interval, Interval);
            else
                _timer.Change(Interval, Interval);
        }
        else
        {
            Console.WriteLine("Running at " + NextRunTime);

            var interval = GetNextRunTimeInterval();
            _timer = new Timer(TimerCallback, null, interval, Timeout.Infinite);
        }
    }    

    private int GetNextRunTimeInterval()
    {
        var span = NextRunTime - DateTime.Now;
        var interval = int.MaxValue;

        if (span.TotalMilliseconds < int.MaxValue)
            interval = (int)span.TotalMilliseconds;

        if (interval < 0)
            throw new Exception("Timer interval cannot be less than zero");

        return interval;
    }

    private void TimerCallback(object state)
    {
        if (Monitor.TryEnter(_lock))
        {
            Console.WriteLine("Lock entered");

            try
            {
                if (SupportsInterval && UseSlidingInterval)
                {
                    _timer.Change(Timeout.Infinite, Timeout.Infinite);
                }

                LastRunTime = DateTime.Now;
                Run();
            }
            finally
            {
                if (SupportsInterval && UseSlidingInterval)
                {
                    _timer.Change(Interval, Interval);
                }
                else
                {
                    _timer.Change(GetNextRunTimeInterval(), Timeout.Infinite);
                }

                Monitor.Exit(_lock);
                Console.WriteLine("Lock exited");
            }
        }
        else
        {
            Console.WriteLine("Lock is on");
        }
    }

    public void Stop()
    {
        _timer.Change(Timeout.Infinite, Timeout.Infinite);
    }

    public void Dispose()
    {
        if (_timer == null)
            return;

        _timer.Dispose();
        _timer = null;
    }
}

Here I used a simple trick. I find the interval in milliseconds between the current time and the next time when a job must run. Then I initialize the timer as a one-shot timer. It waits with the first run the amount of milliseconds until the next run and then runs the callback. In the callback, I initialize the timer again using the next running time. The interval in all cases is infinity, which means that the timer runs just once per initialization.

Testing Base Server

I’m sure you want to test the base server and see how it works. I wrote a simple test server so you can try it out, modify it, and do whatever experiments you like to do.

class TestServer : BaseServer 
{
    public override int Interval
    {
        get { return 5000; }
    }

    public override bool UseSlidingInterval
    {
        get { return true; }
    }

    protected override void Run()
    {
        Console.WriteLine(DateTime.Now + " Run");
    }

    public override bool SupportsInterval
    {
        get { return false; }
    }

    private DateTime _nextRunTime = DateTime.MinValue;

    public override DateTime NextRunTime
    {
        get
        {
            if (_nextRunTime < DateTime.Now)
                _nextRunTime = DateTime.Now.AddMinutes(1);

            return _nextRunTime;
        }
    }
}

And here is program file of my console application.

class Program
{
    static void Main(string[] args)
    {
        var server = new TestServer();
        server.Start();

        Console.WriteLine("Press any key to exit ...");
        Console.ReadLine();
    }
}

When you run the application, you should see an output similar to the screenshot below.

Test server output

Running Multiple Servers

Suppose we have three different test servers. This is close to my real code where I have around six of them. The following code shows how to use an arbitrary number of server instances.

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(" ");

        var servers = new List<BaseServer>();
        servers.Add(new TestServer1());
        servers.Add(new TestServer2());
        servers.Add(new TestServer3());

        foreach (var server in servers)
            server.Start();

        Console.WriteLine("Press any key to exit ...");
        Console.ReadLine();

        foreach (var server in servers)
        {
            server.Stop();
            server.Dispose();
        }
    }
}

Wrapping Up

Instead of having multiple background jobs each with its own implementation, we created a base server class that provides a solid base for our jobs. The base server takes care of running jobs based on an interval, sliding interval, or fixed moment of time. The base server also takes care of overlaping callback issues. Having same base class means that we can handle multiple jobs easily. We can use lists or other collections to store them, and this way we don’t need one variable per server.

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:
.net ,.net api ,base

Published at DZone with permission of Gunnar Peipman, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}