Daemons and Services
│
English (en) │
español (es) │
français (fr) │
polski (pl) │
português (pt) │
русский (ru) │
What are daemons, services and agents?
Unix daemons and Windows services are system-wide programs running without user interaction; macOS agents are per user programs (cf system-wide daemons) that may or may not run without user interaction. Although the nomenclature differs, their function is similar: for example www or ftp servers are called daemons under Linux and services under Windows. Because they do not interact with the user directly, they close their stdin, stdout, stderr descriptors at start.
With Free Pascal, Lazarus it is possible to write these daemons/services platform-independent via the Lazarus lazdaemon package. To avoid name conflicts with the Delphi components these classes are called 'daemons'.
Install the LazDaemon package
Before you can start, install the LazDaemon package. Either via Components / Configure installed packages or by opening/installing the lpk file directly: lazarus/components/daemon/lazdaemon.lpk. This package installs some new components and menu items in the IDE:
Under File - New: 3 items appear in the dialog, under the heading: "Daemon (service) applications":
Creating your first Service Daemon
After having installed the LazDaemon package, from the File-New Menu, pick "Daemon (service) application)". This will automatically create two units: one for a TDaemon descendant, and one for a TDaemonMapper descendant. This is what the scaffolded units look like:
unit DaemonUnit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, DaemonApp;
type
TDaemon1 = class(TDaemon)
private
public
end;
var
Daemon1: TDaemon1;
implementation
procedure RegisterDaemon;
begin
RegisterDaemonClass(TDaemon1)
end;
{$R *.lfm}
initialization
RegisterDaemon;
end.
unit DaemonMapperUnit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, DaemonApp;
type
TDaemonMapper1 = class(TDaemonMapper)
private
public
end;
var
DaemonMapper1: TDaemonMapper1;
implementation
procedure RegisterMapper;
begin
RegisterDaemonMapper(TDaemonMapper1)
end;
{$R *.lfm}
initialization
RegisterMapper;
end.
The TDaemon class, which is a TDataModule descendant, does mainly respond to the various service control messages sent by the OS. For the "worker" part you are supposed to create a thread of its own, see sample code below, don't try to use the execute method provided by the TDaemon class. The TDaemonMapper contains data structures describing the service. Both classes need to be registered, which, in this code sample, is done in the initialization section of each unit. Note that both units have a resource file, which gives you convenient access to all the event handlers supported by those classes from within the Lazarus GUI using the Forms Editor [F12]. The main application component for a service-style application is introduced by putting a reference to DaemonApp into the uses secion of each unit. It provides a complete service framework including advanced features like install and uninstall support, and logging.
Populating the DaemonMapper Class
For the moment let's just fill some basic properties to get things going.
For later use, note the WinBindings property, which lets you configure various service properties for use by the Windows Service Manager.
Now ... how about testing what we have achieved so far? Sure, our daemon doesn't have any execute routine so far, but configuring the DaemonMapper provides all required infos for installing and uninstalling the service on the operating system. To test, compile your application, and then you need open a command line window with elevated privileges, because normal users are by default not allowed to install Windows services. Navigate to the directory where you compiled your test application into, and try out the following commands (sc ist the built-in command line tool to control Windows services from the command line):
TestDaemon -install
|
install the daemon |
sc query TestDaemon
|
check the service status, of course, our service doesn't handle any events yet, so any attempt to start it it runs into an error, but note: the service got known by sc, hence it was installed successfully. |
TestDaemon -uninstall
|
remove the daemon, so we can enhance its code for the next try |
sc query TestDaemon
|
check whether the service was indeed uninstalled |
Screenshots taken 2/2022 on a Windows 11 machine.
Writing the Daemon Methods
TDaemons support the following methods:
- Start
- Called when daemon should start. This method must return immediately with True.
- Stop
- Called when daemon should stop. This method must return immediately with True.
- Shutdown
- Called when daemon should be killed. This method must stop the daemon immediately and return with True. This is not triggered under Linux. Linux simply kills the daemon.
- Pause
- Called when daemon should pause. This method must return immediately with True. Under Linux this is not triggered because the kernel stops the whole daemon on STOP and continues it on CONT.
- Continue
- Called when daemon should continue after a pause. This method must return immediately with True. Under Linux this is not triggered.
- Install
- Called when daemon is registered as a Windows service. This method should return True on success.
- Uninstall
- Called when daemon is unregistered as a Windows service. This method should return True on success.
- AfterUnInstall
- Called after daemon was unregistered as a Windows service. This method should return True on success.
- HandleCustomCode
- Called when a special signal was sent to the daemon. This method should return True on success.
The following sample code has been tested on Windows (and soon on Linux). Before you try to key in all that stuff to try it, please unpack the accompanying zip to get all the files, including the required resource files. The DaemonMapperUnit1 does not require any modification, the sample given above works just fine without any modification.
unit DaemonUnit1;
// -------------------------------------------------------------------------------
// Demo application on how to use the TDaemon application type
// 2/2022 by arminlinder@armnninlinder.de
// Based on document
// "Taming the daemon: Writing cross-platform service applications in FPC/Lazarus"
// by Michaël Van Canneyt, February 4, 2007
// -------------------------------------------------------------------------------
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, DaemonApp;
const
DAEMON_VERSION = '1.0'; // Just for logging
type
{ TDaemonThread }
// This is the "workhorse" of the daemon
// Execute holds the main work code of the service
// Do not try the execute method of TDaemon, since it does not multitask,
// the service thread will stop responding to control messages if looping in the
// TDaemon execute method. Thus we need a worker thread.
TDaemonThread = class(TThread)
private
public
procedure Execute; override; // the worker thread
constructor Create;
destructor Destroy; override;
end;
{ TDaemon1 }
// The main control structure of the thread
// Note: there is an execute procedure defined in TDaemon, but you will
// normally never use it, but create a worker thread
TDaemon1 = class(TDaemon)
// Events called through -install and -uninstall
procedure DataModuleAfterInstall(Sender: TCustomDaemon);
procedure DataModuleAfterUnInstall(Sender: TCustomDaemon);
// Service start and stop
procedure DataModuleStart(Sender: TCustomDaemon; var OK: boolean);
procedure DataModuleStop(Sender: TCustomDaemon; var OK: boolean);
// Service pause and resume
procedure DataModulePause(Sender: TCustomDaemon; var OK: boolean);
procedure DataModuleContinue(Sender: TCustomDaemon; var OK: boolean);
// Construction and destruction
procedure DataModuleCreate(Sender: TObject);
procedure DataModuleDestroy(Sender: TObject);
private
// The worker task, required to keep the service responding to control signals while executing
FDaemonThread: TDaemonThread;
public
end;
var
Daemon1: TDaemon1;
implementation
// ---------------------------------------------------------------------
// Quick and dirty write log message to file
// Note: TDaemonApplication has extensive logging capabilities built-in!
// ---------------------------------------------------------------------
procedure LogToFile(aMessage: string);
var
f: Text;
LogFilePath: string;
function TimeStamped(S: string): string;
// Return a timestamped copy of a string
begin
Result := FormatDateTime('hh:mm:ss', now) + ' ' + S;
end;
begin
// create a new log file in the .exe directory every hour
LogFilePath := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName)) +
FormatDateTime('YYYYMMDDhh', now) + '.log';
AssignFile(f, LogFilePath);
try
if FileExists(LogFilePath) then
Append(f)
else
begin
Rewrite(f);
writeln(f, TimeStamped('Log created'));
end;
Writeln(f, TimeStamped(aMessage));
finally
CloseFile(f);
end;
end;
{$R *.lfm}
{ TDaemonThread }
// ----------------------------------------------------------------------
// The thread providing the execute method of the worker thread.
// ----------------------------------------------------------------------
procedure TDaemonThread.Execute;
// This is the main workhorse, the routine which does
// actually hold the service working code
var
i: integer;
begin
LogToFile('Daemon worker thread executing');
while not Terminated do
begin
// placeholder, put your actual service code here
// ...
LogToFile('Daemon worker thread running');
// Thread- and CPUload friendly 5s delay loop
for i := 1 to 50 do
begin
if Terminated then break;
Sleep(100);
end;
// ...
// ----------------------------------------
end;
LogToFile('Daemon worker thread terminated');
end;
// Construction and destruction of the worker thread
constructor TDaemonThread.Create;
begin
// Create a suspended worker thread
// The actual thread execution will happen if the OS sends a "Start" Signal
// See OnStart/DataModelStart event handler
inherited Create(True);
LogToFile('Daemon worker thread created');
end;
destructor TDaemonThread.Destroy;
begin
// Nothing to do here, just for logging
LogToFile('Daemon worker thread destroyed');
inherited Destroy;
end;
{ TDaemon1 }
// ---------------------------------------------------------
// This is the main scaffolding of the thread, containing
// the event handlers responding to control signals from the
// operating system service control facility
// ---------------------------------------------------------
// installation and uninstallation events
procedure TDaemon1.DataModuleAfterInstall(Sender: TCustomDaemon);
begin
// Nothing to do here, just for logging
LogToFile(Format('Daemon %s installed', [DAEMON_VERSION]));
end;
procedure TDaemon1.DataModuleAfterUnInstall(Sender: TCustomDaemon);
begin
// Nothing to do here, just for logging
LogToFile(Format('Daemon %s uninstalled', [DAEMON_VERSION]));
end;
// Service pause and continue signals
procedure TDaemon1.DataModulePause(Sender: TCustomDaemon; var OK: boolean);
begin
// Nothing to do here, just for logging
LogToFile('Daemon paused');
end;
procedure TDaemon1.DataModuleContinue(Sender: TCustomDaemon; var OK: boolean);
begin
// Nothing to do here, just for logging
LogToFile('Daemon continuing');
end;
// Service start and stop signals
procedure TDaemon1.DataModuleStart(Sender: TCustomDaemon; var OK: boolean);
begin
OK := True;
LogToFile('Daemon received start signal');
// Create a suspended worker thread
FDaemonThread := TDaemonThread.Create;
// Parametrize it
FDaemonThread.FreeOnTerminate := False;
// Start the worker
FDaemonThread.Start;
end;
procedure TDaemon1.DataModuleStop(Sender: TCustomDaemon; var OK: boolean);
var
i: integer;
begin
LogToFile('Daemon received stop signal');
// stop and terminate the worker
FDaemonThread.Terminate;
// Wait for the thread to terminate.
FDaemonThread.WaitFor;
LogToFile('Daemon stopped');
OK := True;
end;
// Construction and destruction of the daemon class
procedure TDaemon1.DataModuleCreate(Sender: TObject);
begin
// Nothing to do here, just for logging
LogToFile(Format('Daemon object %s created', [DAEMON_VERSION]));
end;
procedure TDaemon1.DataModuleDestroy(Sender: TObject);
begin
// Nothing to do here, just for logging
LogToFile('Daemon object destroyed');
end;
// --------------------------------
// Global unit init routine
// --------------------------------
procedure RegisterDaemon;
begin
RegisterDaemonClass(TDaemon1);
end;
initialization
// Register the daemon with the TCustomDaemonApplication application
RegisterDaemon;
end.
Source Code: [Sorry, not allowed to upload a zip at this time - contacting admin ...]
Service Installation
Windows
You can install the service by executing the process with the Install parameter. Windows service manager will do the rest for you. You can configure the service and its options from the service manager.
See also: ServiceManager
System codepage / UTF-8
A LazDeamon project is working with default, not UTF-8, codepage. The -dDisableUTF8RTL mode has to be activated with Project Options ... -> Compiler Options -> Additions and Overrides -> Use system encoding.
Linux (only for older Debian)
Download, configure, and "Save As" - the sample script located at Web Archive: [1] (The original link is dead for a long time).
- SVC_ALIAS is the long description of your application
- SVC_FILENAME is the actual file name of your compiled service application
- SVC_DIR is the place your you copied the service application
- SVC_SERVICE_SCRIPT is the final name of the service.sh when you "Save As" the customized debian-service.sh script.
Place your script in the /etc/init.d/ folder
start the service by running "sudo service Name_Of_Your_Script start"
Note: sudo has some variations, e.g.:
sudo -s #
sudo -H #
sudo -i #
sudo su #
sudo sh #
In order to auto-run the service at startup you can try update-rc.d or else will need a third party tool that will do this.
Option 1
sudo update-rc.d Name_Of_Your_Script defaults
Option 2
sudo apt-get install chkconfig
sudo chkconfig --add Name_Of_Your_Script
sudo chkconfig --level 2345 Name_Of_Your_Script on
systemd (Fedora, Debian, SLES12)
Presently, linux flavors are trending away from differing daemon launching and into a unified service model.
Fedora and SuSE Enterprise Linux Server 12 use systemd and with that commands to start/stop services are the same as on debian but there are differences on the configuration files.
- From the command prompt sudo gedit
- Copy and Paste
[Unit]
Description=Long description of your application
After=network.target
[Service]
Type=simple
ExecStart=complete_path_and_file_name -r
RemainAfterExit=yes
TimeoutSec=25
[Install]
WantedBy=multi-user.target
- Edit the following values
- Description - Long Description of your service application
- ExecStart - complete-path_and_file_name is the name of your compiled service application with its complete path
- Save As Dialog
- Navigate to /lib/systemd/system/
- Name the file the name_of_your_service.service
See also
- ServiceManager Example for the Free Pascal unit for managing Windows services
- macOS daemons and agents - macOS native using launchd
- Office Automation
- Taming the daemon: PDF by Michaël Van Canneyt
- Useful discussion with small working daemon application
- Docker Containerization - Containerization as a means to create daemons / services easily