How to make a Linux-demon from a .NET Mono application.
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.
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:
- Create a class that extends System.ServiceProcess.ServiceBase class
- Implement at least methods OnStart and OnStop
- Build an application and run it with mono-service tool
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:
- Surround main Main() method in the Program class with non-debug directives
- 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