Demonisation of a .NET Mono application on Linux

How to make a Linux-demon from a .NET Mono application.

tuxdemon

Sample solution is available for download here https://github.com/mchudinov/ServiceDemon.


Main program starts a service process that will stay active in memory and will periodically activate a job – do something.

servicedemon

Creating a service (demon) application on Linux using Mono is the same simple as creating it on Windows with pure .NET. Here is a basic MSDN article Walkthrough: Creating a Windows Service Application in the Component Designer about it. The same code will run on both Windows and Linux and the only difference is how to run the service itself.
To run a mono application as a service it must be started with a help of a special tool called mono-service.

Workflow:

In my sample application I use Quartz.NET scheduler to run tasks periodically.

Program class

class Program
{
    public static void Main(string[] args)
    {
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[] { new ServiceDemon.Service() };
        ServiceBase.Run(ServicesToRun);
    }
}

Service extends ServiceBase class with scheduler

public class Service : ServiceBase
{
    static IScheduler Scheduler { get; set; }

    protected override void OnStart(string[] args)
    {
        StartScheduler();
        StartMyJob();
    }

    protected override void OnStop()
    {
        Scheduler.Shutdown();
    }

    void StartScheduler()
    {
        ISchedulerFactory schedFact = new StdSchedulerFactory();
        Scheduler = schedFact.GetScheduler();
        Scheduler.Start();
    }

    void StartMyJob()
    {
        var seconds = Int16.Parse(ConfigurationManager.AppSettings["MyJobSeconds"]);

        IJobDetail job = JobBuilder.Create<Jobs.MyJob>()
            .WithIdentity("MyJob", "group1")
            .UsingJobData("Param1", "Hello MyJob!")
            .Build();

        ITrigger trigger = TriggerBuilder.Create()
            .WithIdentity("MyJobTrigger", "group1")
            .StartNow()
            .WithSimpleSchedule(x => x.WithIntervalInSeconds(seconds).RepeatForever())
            .Build();

        Scheduler.ScheduleJob(job, trigger);
    }
}

Debugging

Since I develop a service application debugging of it directly is complicated. I need a trick to simulate a service start. I can run OnStart() from a fake Main() method. This is described in an MSDN article How to: Debug the OnStart Method.

I do two modifications in the program:

  1. Surround main Main() method in the Program class with non-debug directives
  2. Add a second Main() method to the Service class and surround it with debug-only directives

Modified Program class

class Program
{
    #if (DEBUG != true)
    public static void Main(string[] args)
    {
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[] { new ServiceDemon.Service() };
        ServiceBase.Run(ServicesToRun);
    }
    #endif
}

Second Main() method in the Service class

public class Service : ServiceBase
{
    .....

    #if DEBUG
    // This method is for debugging of OnStart() method only.
    // Switch to Debug config, set a breakpoint here and a breakpoint in OnStart()
    public static void Main(String[] args)
    {      
        (new ServiceDemon.Service()).OnStart(new string[1]);
        ServiceBase.Run( new ServiceDemon.Service() );
    }
    #endif
}

Now to debug the service switch to Debug configuration, set a breakpoint in the fake Main() and in the OnStart() methods.

Run the service

$ sudo mono-service -l:/tmp/myservice.lock ./ServiceDemon.exe

-l:LOCKFILE
Specifies the file to use for locking, the default is a filename constructed in /tmp based on the name of the program that hosts the service.

Stop the service

Just use kill UNIX command to terminate the process. Process id is stored in the lock file.

$ sudo kill 8939

Start-stop script

Here is my simple start-stop script for a Mono service. Script must be included into C# solution and copied to the binary folder after build.
Script contains 3 procedures: usage, stop and start

#!/bin/bash

PROGRAM_NAME="ServiceDemon"
LOCK_FILE="/tmp/"${PROGRAM_NAME}".lock"

usage()
{
    echo "$0 (start|stop)"
}

stop()
{
    if [ -e ${LOCK_FILE} ]
    then
        _pid=$(cat ${LOCK_FILE})
        kill $_pid
        rt=$?
        if [ "$rt" == "0" ]
	        then
	                echo "Demon stop"
	        else
	                echo "Error stop demon"
        fi
    else
        echo "Demon is not running"
    fi
}

start()
{
    mono-service -l:${LOCK_FILE} ./${PROGRAM_NAME}.exe
}

case $1 in
    "start")
            start
            ;;
    "stop")
            stop
            ;;
    *)
            usage
            ;;
esac
exit

Running script

$ sudo ./demon.sh start
$ sudo ./demon.sh stop

serviceoutput