为什么`forever`意味着这段代码不能从套接字读取或打印到stdout?

use*_*448 8 sockets haskell unix-socket systemd

我在Haskell中编写了一个systemd socket激活服务.这个想法是服务应该在消息发送到其套接字时自动启动,服务应该处理在套接字上等待的所有消息,然后退出.

注意:处理所有等待消息后服务应该关闭的原因(而不是永远运行)是套接字激活之间应该有几个小时或几天.

部署-trigger.socket:

[Socket]
ListenStream=/var/run/deploy-trigger.socket

[Install]
WantedBy=sockets.target
Run Code Online (Sandbox Code Playgroud)

部署-trigger.service:

[Service]
ExecStart=/home/user4301448/.local/bin/deploy-trigger-exe
StartLimitInterval=0
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

编译(和安装stack install)时,使用以下命令通过向套接字发送一些文本来激活:

printf 'Hello world\r\n' | nc -U /var/run/deploy-trigger.socket
Run Code Online (Sandbox Code Playgroud)

以下内容打印到systemd日志(我journalctl -f用来查看日志):

systemd[1]: Starting deploy-trigger.service...
Run Code Online (Sandbox Code Playgroud)

什么都没有印; 该过程永远运行并最大化所有计算机的CPU核心.为什么会发生这种情况并且有没有办法将行为更改为第一段中描述的行为?

更改main为以下内容:

main = withSocketsDo $ getActivatedSockets >>= processSocks
Run Code Online (Sandbox Code Playgroud)

因此删除forever,stack install再次发送一些文本到套接字打印以下日志:

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.
Run Code Online (Sandbox Code Playgroud)

deploy-trigger-exe然后干净利落地退出.这样做的缺点是,对于发送到套接字的每条消息,二进制文件似乎由systemd运行,这是不可取的.

注意:我怀疑这个问题源于我对UNIX套接字的无能.任何答案都有关于我误解的内容的支持信息,纠正我的duff术语将是一个奖励.

nh2*_*nh2 2

好的,首先关于丢失的输出,正如Li-yao Xia所说的,在 Linux 上,如果写入管道,输出将被块缓冲。

将您的更改main

main = do
    hSetBuffering stdout LineBuffering
    withSocketsDo $ forever $ getActivatedSockets >>= processSocks
Run Code Online (Sandbox Code Playgroud)

你会看到journalctl -f

systemd[1]: Started deploy-trigger.service.
deploy-trigger-exe[14197]: Got socket(s).
deploy-trigger-exe[14197]: Printing message from socket:
deploy-trigger-exe[14197]: Hello world
deploy-trigger-exe[14197]: Finished processing socket(s).
deploy-trigger-exe[14197]: Got socket(s).
Run Code Online (Sandbox Code Playgroud)

在这个(预期的)输出之后,您的程序将挂起。

如何找出它挂在哪里?当然可以用strace(有传言95%的计算机问题都可以用strace)来解决。

% sudo strace -fp $(pidof deploy-trigger-exe)
strace: Process 14197 attached
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,该程序现在被阻止在accept sock. 这是有道理的(因为另一端已断开连接)。


您可能会感到困惑的另一件事是为什么它会Got socket(s)第二次打印。

我认为您对工作原理存在误解getActivatedSockets。我从你的写作中得出这样的结论forever $ getActivatedSockets >>= ...。这对我来说意味着您期望第二次调用 时getActivatedSockets,它会返回第一次以外的其他内容(特别是,我怀疑您认为它会Nothing在以某种方式“处理”套接字后返回)。

但看看 的代码getActivatedSockets,它总是会返回相同的结果(因为它只是读取了一些环境变量的内容)。因此,将其包装在 中似乎没有意义forever

你写了

该服务应该处理套接字上等待的所有消息,然后退出

为了实现这一点,我认为你应该删除forever

main = do
    hSetBuffering stdout LineBuffering
    withSocketsDo $ getActivatedSockets >>= processSocks
    putStrLn "End of main, exiting"
Run Code Online (Sandbox Code Playgroud)

(尝试此更改的代码时,不要忘记先终止仍在运行的代码deploy-trigger-exe。)

你会得到:

systemd[1]: Started deploy-trigger.service.
deploy-trigger-exe[15881]: Got socket(s).
deploy-trigger-exe[15881]: Printing message from socket:
deploy-trigger-exe[15881]: Hello world
deploy-trigger-exe[15881]: Finished processing socket(s).
deploy-trigger-exe[15881]: End of main, exiting
Run Code Online (Sandbox Code Playgroud)

我认为这就是你正在寻找的。


另一个提示:考虑一下,如果您向套接字发送一条大消息,则必须循环recv sock ...接收所有数据。

(快速插入:我的工作就是帮助解决此类问题。)