I have written a systemd socket activated service in Haskell. The idea is the service should be started automatically when a message is sent to its socket, the service should process all messages waiting on the socket and then exit.
Note: the reason the service should close after processing all waiting messages (as opposed to running forever) is there shall be several hours or days between socket activations.
deploy-trigger.socket:
[Socket]
ListenStream=/var/run/deploy-trigger.socket
[Install]
WantedBy=sockets.target
deploy-trigger.service:
[Service]
ExecStart=/home/user4301448/.local/bin/deploy-trigger-exe
StartLimitInterval=0
Main.hs
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad (forever)
import qualified Data.ByteString.Char8 as BS (putStrLn)
import Data.Foldable (foldl')
import Network.Socket (withSocketsDo, accept, close, SockAddr(SockAddrUnix), Socket)
import Network.Socket.ByteString (recv)
import Network.Socket.Activation (getActivatedSockets)
import System.Exit (exitWith, ExitCode(..))
main :: IO ()
main = withSocketsDo $ forever $ getActivatedSockets >>= processSocks
processSocks :: Maybe [Socket] -> IO ()
processSocks (Just socks) = do
putStrLn "Got socket(s)."
traverse_ (\sock -> accept sock >>= printMsgFromSock) socks
putStrLn "Finished processing socket(s)."
processSocks Nothing = do
putStrLn "Received no socket(s)."
exitWith ExitSuccess
printMsgFromSock :: (Socket, SockAddr) -> IO ()
printMsgFromSock (sock, sockaddr) = do
msg <- recv sock 2048
case sockaddr of
SockAddrUnix s -> putStrLn ("Printing message from socket: " ++ s)
_ -> putStrLn "Printing message from something that is not a UNIX socket."
BS.putStrLn msg
close sock
When compiled (and installed with stack install
), then activated by sending some text to the socket using the following command:
printf 'Hello world\r\n' | nc -U /var/run/deploy-trigger.socket
the following is printed to the systemd journal (I'm using journalctl -f
to watch the logs):
systemd[1]: Starting deploy-trigger.service...
nothing else is printed; the process runs forever and maxes out all the computer's CPU cores. Why does this happen and is there a way to change the behaviour to that described in the first paragraph?
Changing main
to the following:
main = withSocketsDo $ getActivatedSockets >>= processSocks
thus removing forever
, stack install
ing again and sending some text to the socket prints the following to the journal:
systemd[1]: Starting deploy-trigger.service...
deploy-trigger-exe[14800]: Got socket(s).
deploy-trigger-exe[14800]: Printing message from socket:
deploy-trigger-exe[14800]: Hello world
deploy-trigger-exe[14800]: Finished processing socket(s).
systemd[1]: Started deploy-trigger.service.
deploy-trigger-exe
then exits cleanly. The downside to this is the binary appears to be run by systemd for every message sent to the socket, which is not desirable.
Note: I suspect the issue is rooted in my incompetence regarding UNIX sockets. Any answers with supporting information on what I'm misunderstanding, correcting my duff terminology would be a bonus.