💾 Archived View for missbanal.net › how-to-manage-shell-daemons captured on 2022-04-29 at 11:10:10. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2022-03-01)

➡️ Next capture (2023-01-29)

-=-=-=-=-=-=-

Published the 2022-01-14 on Stacy Harper's blog

How to manage shell daemons

While writing shell script, it is pretty common to need to manage some daemons.

Example inspired from sxmo-utils where we got a sxmo_modemmonitor.sh script that listen to dbus signals to dispatch notifications.

	#!/bin/sh

	dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Voice',type='signal',member='CallAdded'" | \
		while read -r line; do
			notify-send "$line"
		done &

	dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Messaging',type='signal',member='Added'" | \
		while read -r line; do
			notify-send "$line"
		done &

This script got a huge issue. It will exit itself after reaching the end of the file. That mean you cannot control the two dbus-monitor subprocess anymore. You'll have to kill each of them manually.

	#!/bin/sh

	dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Voice',type='signal',member='CallAdded'" | \
		while read -r line; do
			notify-send "$line"
		done &

	dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Messaging',type='signal',member='Added'" | \
		while read -r line; do
			notify-send "$line"
		done &

	wait
	wait

This is a better idea. The two wait will make the script to wait for subjobs to finish. But we still doesnt manage the subprocesses.

If you want to be able to clear subprocesses with Ctrl+c or with a kill signal you will do something like this:

	#!/bin/sh

	dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Voice',type='signal',member='CallAdded'" | \
		while read -r line; do
			notify-send "$line"
		done &
	PID1=$!

	dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Messaging',type='signal',member='Added'" | \
		while read -r line; do
			notify-send "$line"
		done &
	PID2=$!

	gracefulexit() {
		kill "$PID1"
		kill "$PID2"
		exit 0
	}
	trap "gracefulexit" INT TERM

	wait "$PID1"
	wait "$PID2"

This is the most common way to handle subprocess in shell scripts. But it got one main big issue:

`$!` is the pid of the while loop only, not the `dbus-monitor`, not the both of them.

In consequence, killing those PID in the trap handler will leave alone the dbus-monitors subprocesses unmanaged.

So how to manage those ? This is the clean way :

	#!/bin/sh

	FIFO1="/tmp/fifo1"
	mkfifo "$FIFO1"

	dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Voice',type='signal',member='CallAdded'" \
		>> "$FIFO1" &
	PID1=$!

	while read -r line; do
		notify-send "$line"
	done < "$FIFO1" &
	PID2=$!

	FIFO2="/tmp/fifo2"
	mkfifo "$FIFO2"

	dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Messaging',type='signal',member='Added'" \
		>> "$FIFO2" &
	PID3=$!

	while read -r line; do
		notify-send "$line"
	done < "$FIFO2" &
	PID4=$!

	gracefulexit() {
		kill "$PID1"
		kill "$PID2"
		kill "$PID3"
		kill "$PID4"
		rm "$FIFO1"
		rm "$FIFO2"
		exit 0
	}
	trap "gracefulexit" INT TERM EXIT

	wait "$PID1"
	wait "$PID2"
	wait "$PID3"
	wait "$PID4"

This use named pipe fifo files to separate both commands and to grab each pids.

Here is two tips to make it simple and clean :

	#!/bin/sh

	daemon_pids_cache="$(mktemp)"
	start_daemon() {
		"$@" &
		printf "%s\n" "$!" >> "$daemon_pids_cache"
	}
	stop_daemons() {
		while read -r PID; do
			kill "$PID"
		done < "$daemon_pids_cache"
		rm "$daemon_pids_cache"
	}

	PIDS=""

	start_daemon dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Voice',type='signal',member='CallAdded'" | \
		while read -r line; do
			notify-send "$line"
		done &
	PIDS="$PIDS $!"

	start_daemon dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Messaging',type='signal',member='Added'" | \
		while read -r line; do
			notify-send "$line"
		done &
	PIDS="$PIDS $!"

	gracefulexit() {
		stop_daemons
		for PID in $PIDS; do
			kill "$PID"
		done
		exit 0
	}
	trap "gracefulexit" INT TERM EXIT

	for PID in $PIDS; do
		wait "$PID"
	done

If this post inspired you, feels free to leave a comment !

The Cogitatis mailing list