FreeBSD and Python - Writing a Daemon Script Part 1

A few additional notes. Although the word “daemon” may sound like something evil, it has nothing to do with Satan, in fact the word comes from the Greek word δαίμων, which refers to the spirits that humans possess and that ultimately define them.

A daemon is a process. Nothing more and nothing less than your browser processes. The main difference is that a daemon is a process that does not require user input to function. Imagine, for example, about a web server that does not wait for its own users to perform an action, but instead waits for other hosts on the network to perform requests. The request needs to be processed without any human taking part in it.

The daemon utility detaches from the controlling terminal and executes the program specified by its arguments. Privileges can be assigned to specific users. The output of a daemonized process can be redirected to syslog and to a log file. We can create daemon processes in Python via the “daemon” argument to the multiprocessing constructor or via the “daemon” property.

Each Python program is executed in a Process, which is a new instance of the Python interpreter. This process has the name MainProcess and has one thread that is used to execute program instructions called MainThread. Both processes and threads are created and managed by the underlying operating system. Sometimes we may need to create a new child process in our program to execute code simultaneously. Python provides the ability to create and manage new processes through the multiprocessing.Process class.

Daemon is pronounced “dee-mon”, like the alternative spelling “demon”. The idea is that background processes are like “daemons” or spirits (from ancient Greek) that do tasks for you in the background. You might also call a daemon process a daemonic.

A process can be configured to be a daemon or not, and most processes in concurrent programming, including the main parent process, are non-daemon processes (not background processes) by default. The property of being a daemon process or not may not be supported by the underlying operating system that actually creates and manages the execution of threads and processes.


1. What is the Python Daemon

In 2009 PEP 3143 was created, its aim was to create “a package in the Python standard library that provides a simple interface for the task of being a daemon process. While this goal was not impossible and enough people were interested in seeing the project succeed, it was not successful. The person responsible for doing it didn't have enough time left and no one stepped in to save the project. A sad death for a good project

This tragedy doesn't affect the functionality of python-daemon so much (it basically includes everything needed for a daemon), but rather the documentation. As I said before, the weakest point is the documentation in the PEP and some of it is in the code itself.

Since the python daemon first appeared, the python library introduced a daemon runner to handle daemons. However, currently the Daemon Runner library is no longer used, and instead the DaemonContext API library used in DaemonRunner is used. It's true that DaemonRunner extends the functionality of DaemonContext, but it does it in a very old fashioned way (e.g. not using argparse). This may be the reason why it was eventually deprecated. Without further ado, DaemonContext makes it very easy to start daemon jobs with just a context manager.


2. How to Create a Python Daemon

The first step to use the Python Daemon is to first install the Python Daemon application. The following commands are used to install the python daemon.

root@ns1:~ # pip install python-daemon

To create a python daemon and to make it easier to learn, below will be an example of a daemon file. We create a file with the name /tmp/gunungsemeru.py and enter the following script into the file /tmp/gunungsemeru.py.

root@ns1:/ # ee /tmp/gunungsemeru.py
#!/usr/local/bin/python

# Imports #
import time

# Globals #
TTL_SECONDS = 30
TTL_CHECK_INTERVAL = 5

# Fuctions #

# Main #
print("Started!")
for i in range(1, TTL_SECONDS + 1):
time.sleep(1)
if i % TTL_CHECK_INTERVAL == 0:
print("Running for " + str(i) + " seconds...")
print("TTL reached, terminating!")
exit(0)


Then create file permissions on the /tmp/gunungsemeru.py file.

root@ns1:/tmp # chmod +x /tmp/gunungsemeru.py

Now we test the /tmp/gunungsemeru.py file, whether the Python daemon is running or not.

root@ns1:~ # cd /tmp
root@ns1:/tmp #
./gunungsemeru.py restart
Started!
Running for 5 seconds...

The main part of the program first outputs an initial message in the terminal, and then enters a for loop, which counts from 1 to 30. In Python we can do this by providing a list of values after the in keyword. Counting to 5 can be written as i in [1, 2, 3, 4, 5].

The next thing to explore is signal handling. Since a daemon is basically a program that runs in the background, we need a way to tell it to stop for example. This is usually done using signals. We can send some of them to a normal program running in the terminal by pressing a key combination, while all of them can be sent with the kill command.

If we press CTRL + C for example, we send a SIGINT to the running application, telling it to “abort operation”. Something somewhat similar is SIGTERM, which means “hey, please stop”. This is a good termination signal, allowing the program to perform cleanup and then shut down properly.

However, if we use kill-9, we will send SIGKILL, an impassable kill signal, which effectively means "dead!" for the targeted process (if we've ever done that to a live database or other sensitive application, we know that we really have to think before using it or we might run into all sorts of difficulties over the next few hours).

Below is a script to give a signal to the Python daemon. First delete the contents of the /tmp/gunungsemeru.py file and replace it with the script below.

root@ns1:/ # ee /tmp/gunungsemeru.py
#!/usr/local/bin/python

# Imports #
import signal
import time

# Globals #
TTL_SECONDS = 30
TTL_CHECK_INTERVAL = 5

# Fuctions #
def signal_handler(signum, frame):
print("Received signal" + str(signum) + "!")
if signum == 2:
exit(0)

# Main #
signal.signal(signal.SIGHUP, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGQUIT, signal_handler)
signal.signal(signal.SIGILL, signal_handler)
signal.signal(signal.SIGTRAP, signal_handler)
signal.signal(signal.SIGABRT, signal_handler)
signal.signal(signal.SIGEMT, signal_handler)
#signal.signal(signal.SIGKILL, signal_handler)
signal.signal(signal.SIGSEGV, signal_handler)
signal.signal(signal.SIGSYS, signal_handler)
signal.signal(signal.SIGPIPE, signal_handler)
signal.signal(signal.SIGALRM, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
#signal.signal(signal.SIGSTOP, signal_handler)
signal.signal(signal.SIGTSTP, signal_handler)
signal.signal(signal.SIGCONT, signal_handler)
signal.signal(signal.SIGCHLD, signal_handler)
signal.signal(signal.SIGTTIN, signal_handler)
signal.signal(signal.SIGTTOU, signal_handler)
signal.signal(signal.SIGIO, signal_handler)
signal.signal(signal.SIGXCPU, signal_handler)
signal.signal(signal.SIGXFSZ, signal_handler)
signal.signal(signal.SIGVTALRM, signal_handler)
signal.signal(signal.SIGPROF, signal_handler)
signal.signal(signal.SIGWINCH, signal_handler)
signal.signal(signal.SIGINFO, signal_handler)
signal.signal(signal.SIGUSR1, signal_handler)
signal.signal(signal.SIGUSR2, signal_handler)
#signal.signal(signal.SIGTHR, signal_handler)

print("Started!")
for i in range(1, TTL_SECONDS + 1):
time.sleep(1)
if i % TTL_CHECK_INTERVAL == 0:
print("Running for " + str(i) + " seconds...")
print("TTL reached, terminating!")
exit(0)

The example script above has added a function called “signal_handler”, because that's what it's for. If this program is run, it will handle every signal that can be sent on a FreeBSD system (run kill -l to list all signals available on Unix-like operating systems).

A logging system should also be considered when dealing with processes running in the Daemon background regardless of TTY. This means it can no longer receive input as usual from STDIN. But we investigated the signal so it doesn't matter. However this also means the daemon cannot use STDOUT or STDERR to print anything to the terminal.

Where does the data written by the daemon go for example STDOUT? This goes to the system log. If there is no special configuration for it, you can find it in /var/log/messages. Since we expect quite a lot of debug output during the development phase, we don't want to clutter /var/log/messages with all that. So to write a well-behaved little daemon, there is one more topic we should pay attention to which is "Logging". The following is a logging script for the Puthon daemon.

root@ns1:/ # ee /tmp/gunungsemeru.py
#!/usr/local/bin/python

# Imports #
import logging
import signal
import time

# Globals #
TTL_SECONDS = 30
TTL_CHECK_INTERVAL = 5

# Fuctions #
def handler_sigterm(signum, frame):
logging.debug("Exiting on SIGTERM")
exit(0)

def handler_sigint(signum, frame):
logging.debug("Not going to quit, there you have it!")

# Main #
signal.signal(signal.SIGINT, handler_sigint)
signal.signal(signal.SIGTERM, handler_sigterm)
try:
logging.basicConfig(filename='bdaemon.log', format='%(levelname)s:%(message)s', level=logging.DEBUG)
except:
print("Error: Could not create log file! Exiting...")
exit(1)

logging.info("Started!")
for i in range(1, TTL_SECONDS + 1):
time.sleep(1)
if i % TTL_CHECK_INTERVAL == 0:
logging.info("Running for " + str(i) + " seconds...")
logging.info("TTL reached, terminating!")
exit(0)

The only thing to do is change the print statement so that we use logging. Depending on how important the log entry is, we can also use different levels from least important (DEBUG) to most important (CRITICAL). We can wait until the program finishes and then look at the log, or can open a second terminal and type -f bdaemon.log there to see the output while the program is running.

Okay! with the article in stage 1, we have everything needed to daemonize a python program.
Iwan Setiawan

I Like Adventure: Mahameru Mount, Rinjani Mount I Like Writer FreeBSD

Post a Comment

Previous Post Next Post