为什么“forever”意味着此代码不会从套接字读取或打印到标准输出?
Why does `forever` mean this code doesn't read from a socket or print to stdout?
我在Haskell中写了一个systemd套接字激活服务。这个想法是当消息发送到它的套接字时服务应该自动启动,服务应该处理所有等待套接字的消息然后退出。
注意:服务应在处理完所有等待消息后关闭(而不是永远 运行ning)的原因是套接字激活之间应间隔数小时或数天。
部署-trigger.socket:
[Socket]
ListenStream=/var/run/deploy-trigger.socket
[Install]
WantedBy=sockets.target
部署-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
编译后(并与 stack install
一起安装),然后通过使用以下命令向套接字发送一些文本来激活:
printf 'Hello world\r\n' | nc -U /var/run/deploy-trigger.socket
以下内容打印到 systemd 日志(我正在使用 journalctl -f
查看日志):
systemd[1]: Starting deploy-trigger.service...
没有打印其他内容;进程 运行 永远持续下去,并使计算机的所有 CPU 核心都达到极限。为什么会发生这种情况,有没有办法将行为更改为第一段中描述的行为?
将 main
更改为以下内容:
main = withSocketsDo $ getActivatedSockets >>= processSocks
因此删除 forever
,再次 stack install
ing 并将一些文本发送到套接字将以下内容打印到日志:
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
然后干净地退出。这样做的缺点是,对于发送到套接字的每条消息,二进制文件似乎都是 运行,这是不可取的。
注意:我怀疑问题的根源在于我对 UNIX 套接字的无能。任何关于我误解的支持信息的答案,纠正我的 duff 术语将是一个奖励。
似乎是因为 stdout
没有连接到终端,所以 putStrLn
的小输出保持缓冲状态,因此不会出现在日志中。这可以通过定期调用 hFlush
来解决,例如:
main = withSocketsDo $ forever $
getActivatedSockets >>= processSocks >>= \_ -> hFlush stdout
好的,首先关于丢失的输出,正如 Li-yao Xia
所说,在 Linux 上,如果写入管道,输出是块缓冲的。
将您的 main
更改为
main = do
hSetBuffering stdout LineBuffering
withSocketsDo $ forever $ getActivatedSockets >>= processSocks
您将在 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).
在此(预期的)输出之后,您的程序将挂起。
如何找出它挂在哪里?当然用strace
(有传言说95%的电脑问题都可以用strace
解决)。
% sudo strace -fp $(pidof deploy-trigger-exe)
strace: Process 14197 attached
正如我们所见,该程序现在已在 accept sock
中被阻止。这是有道理的(因为对方已经断开连接)。
另一件您可能会感到困惑的事情是为什么它会第二次打印 Got socket(s)
。
我认为您对 getActivatedSockets
的工作原理有误解。我从你写的 forever $ getActivatedSockets >>= ...
中得出结论。这向我暗示你期望你第二次调用 getActivatedSockets
,它会 return 不同于第一次的东西(特别是,我怀疑你认为它会 return Nothing
在以某种方式获得 "processed" 套接字之后)。
但是看code of getActivatedSockets
,总是return相同的结果(因为只是读取了一些环境变量的内容)。因此,将其包装在 forever
.
中似乎没有意义
你写了
the service should process all messages waiting on the socket and then exit
为此,我认为您应该删除 forever
:
main = do
hSetBuffering stdout LineBuffering
withSocketsDo $ getActivatedSockets >>= processSocks
putStrLn "End of main, exiting"
(尝试修改后的代码时,不要忘记先杀死静止的-运行 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
我认为这就是您要找的。
另一个提示:考虑到如果您向套接字发送一个大消息,您将不得不循环 recv sock ...
以接收所有数据。
(快速插入:我正在帮助解决此类问题。)
我在Haskell中写了一个systemd套接字激活服务。这个想法是当消息发送到它的套接字时服务应该自动启动,服务应该处理所有等待套接字的消息然后退出。
注意:服务应在处理完所有等待消息后关闭(而不是永远 运行ning)的原因是套接字激活之间应间隔数小时或数天。
部署-trigger.socket:
[Socket]
ListenStream=/var/run/deploy-trigger.socket
[Install]
WantedBy=sockets.target
部署-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
编译后(并与 stack install
一起安装),然后通过使用以下命令向套接字发送一些文本来激活:
printf 'Hello world\r\n' | nc -U /var/run/deploy-trigger.socket
以下内容打印到 systemd 日志(我正在使用 journalctl -f
查看日志):
systemd[1]: Starting deploy-trigger.service...
没有打印其他内容;进程 运行 永远持续下去,并使计算机的所有 CPU 核心都达到极限。为什么会发生这种情况,有没有办法将行为更改为第一段中描述的行为?
将 main
更改为以下内容:
main = withSocketsDo $ getActivatedSockets >>= processSocks
因此删除 forever
,再次 stack install
ing 并将一些文本发送到套接字将以下内容打印到日志:
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
然后干净地退出。这样做的缺点是,对于发送到套接字的每条消息,二进制文件似乎都是 运行,这是不可取的。
注意:我怀疑问题的根源在于我对 UNIX 套接字的无能。任何关于我误解的支持信息的答案,纠正我的 duff 术语将是一个奖励。
似乎是因为 stdout
没有连接到终端,所以 putStrLn
的小输出保持缓冲状态,因此不会出现在日志中。这可以通过定期调用 hFlush
来解决,例如:
main = withSocketsDo $ forever $
getActivatedSockets >>= processSocks >>= \_ -> hFlush stdout
好的,首先关于丢失的输出,正如 Li-yao Xia
所说,在 Linux 上,如果写入管道,输出是块缓冲的。
将您的 main
更改为
main = do
hSetBuffering stdout LineBuffering
withSocketsDo $ forever $ getActivatedSockets >>= processSocks
您将在 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).
在此(预期的)输出之后,您的程序将挂起。
如何找出它挂在哪里?当然用strace
(有传言说95%的电脑问题都可以用strace
解决)。
% sudo strace -fp $(pidof deploy-trigger-exe)
strace: Process 14197 attached
正如我们所见,该程序现在已在 accept sock
中被阻止。这是有道理的(因为对方已经断开连接)。
另一件您可能会感到困惑的事情是为什么它会第二次打印 Got socket(s)
。
我认为您对 getActivatedSockets
的工作原理有误解。我从你写的 forever $ getActivatedSockets >>= ...
中得出结论。这向我暗示你期望你第二次调用 getActivatedSockets
,它会 return 不同于第一次的东西(特别是,我怀疑你认为它会 return Nothing
在以某种方式获得 "processed" 套接字之后)。
但是看code of getActivatedSockets
,总是return相同的结果(因为只是读取了一些环境变量的内容)。因此,将其包装在 forever
.
你写了
the service should process all messages waiting on the socket and then exit
为此,我认为您应该删除 forever
:
main = do
hSetBuffering stdout LineBuffering
withSocketsDo $ getActivatedSockets >>= processSocks
putStrLn "End of main, exiting"
(尝试修改后的代码时,不要忘记先杀死静止的-运行 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
我认为这就是您要找的。
另一个提示:考虑到如果您向套接字发送一个大消息,您将不得不循环 recv sock ...
以接收所有数据。
(快速插入:我正在帮助解决此类问题。)