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

时间:2018-01-28 22:27:03

标签: sockets haskell systemd unix-socket

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

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

部署-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

因此再次删除foreverstack 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.

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

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

2 个答案:

答案 0 :(得分:3)

似乎因为stdout没有连接到终端,putStrLn的小输出仍然是缓冲的,因此不会出现在日志中。这可以通过定期调用hFlush来解决,例如:

main = withSocketsDo $ forever $
  getActivatedSockets >>= processSocks >>= \_ -> hFlush stdout

答案 1 :(得分:2)

好的,首先关于丢失的输出,正如<deadlock> <victim-list> <victimProcess id="process28fc21868" /> </victim-list> <process-list> <process id="process28fc21868" taskpriority="0" logused="2504" waitresource="KEY: 5:72057594562412544 (40fd182c0dd9)" waittime="5008" ownerId="299034576" transactionname="user_transaction" lasttranstarted="2018-02-01T12:22:55.580" XDES="0x140b2cc70" lockMode="X" schedulerid="1" kpid="3600" status="suspended" spid="87" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2018-02-01T12:22:55.580" lastbatchcompleted="2018-02-01T12:22:55.580" lastattention="2018-02-01T12:22:52.480" clientapp="EUROSTOP e-i Service" hostname="SRVAZBRWSQL01" hostpid="1328" loginname="sa" isolationlevel="read committed (2)" xactid="299034576" currentdb="5" lockTimeout="4294967295" clientoption1="673316896" clientoption2="128056"> <executionStack> <frame procname="ALL.dbo.trgi_u_Constants" line="705" stmtstart="37030" stmtend="37270" sqlhandle="0x03000500651fce5aad8fd3002aa5000000000000000000000000000000000000000000000000000000000000"> update myconstants set value = (select last_c_no from inserted) where batch = 'last_c_no' </frame> <frame procname="IF_TEST.myschema.SendTestData_Customers_SToALL_Customers" line="87" stmtstart="11420" stmtend="11650" sqlhandle="0x030007007f19ab663ac3e5005ca7000001000000000000000000000000000000000000000000000000000000"> update myschema.ALL_Constants set last_c_no = @nLastCNumber + @NumberOfInsertedCs </frame> <frame procname="IF_TEST.myschema.SendTestData_Customers" line="22" stmtstart="962" stmtend="1196" sqlhandle="0x030007009c954b7757b4d00054a7000001000000000000000000000000000000000000000000000000000000"> exec myschema.SendTestData_Customers_SToALL_Customers @MessageCode, @RejectAllOnValidationError </frame> <frame procname="IF_TEST.myschema.SendSubmittedFData" line="13" stmtstart="876" stmtend="1114" sqlhandle="0x030007009b4fb66e2db4d00054a7000001000000000000000000000000000000000000000000000000000000"> exec [myschema].[SendTestData_Customers] @MessageCode, @RejectAllOnValidationError -- Customer Orders </frame> <frame procname="adhoc" line="1" stmtstart="104" sqlhandle="0x010007008547740e50dc4dd90700000000000000000000000000000000000000000000000000000000000000"> Exec myschema.SendSubmittedFData @0, @1, @2, @3 </frame> <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"> unknown </frame> </executionStack> <inputbuf> (@0 nvarchar(4000),@1 int,@2 nvarchar(4000),@3 int);Exec myschema.SendSubmittedFData @0, @1, @2, @3 </inputbuf> </process> <process id="process2b6e94cf8" taskpriority="-6" logused="43652" waitresource="KEY: 5:72057594562412544 (d08358b1108f)" waittime="5009" ownerId="299033786" transactionname="user_transaction" lasttranstarted="2018-02-01T12:22:53.810" XDES="0x28262f130" lockMode="X" schedulerid="2" kpid="13408" status="suspended" spid="122" sbid="0" ecid="0" priority="-5" trancount="3" lastbatchstarted="2018-02-01T12:15:00.580" lastbatchcompleted="2018-02-01T12:15:00.580" lastattention="1900-01-01T00:00:00.580" clientapp="SQLAgent - TSQL JobStep (Job 0x24121E41ABD80643985B522FE6C248A7 : Step 1)" hostname="SRVAZBRWSQL01" hostpid="2252" loginname="SRVAZBRWSQL01\ALLSYSTEM" isolationlevel="read committed (2)" xactid="299033786" currentdb="5" lockTimeout="4294967295" clientoption1="673316896" clientoption2="128056"> <executionStack> <frame procname="ALL.dbo.trgi_u_Constants" line="726" stmtstart="38142" stmtend="38386" sqlhandle="0x03000500651fce5aad8fd3002aa5000000000000000000000000000000000000000000000000000000000000"> update myconstants set value = (select last_gs_rec_no from inserted) where batch = 'last_gs_rec_no' </frame> <frame procname="ALL.dbo.trgi_i_i_I_deliveries" line="1005" stmtstart="69046" stmtend="69220" sqlhandle="0x030005006cd104389a850d00a1a6000000000000000000000000000000000000000000000000000000000000"> update dbo.Constants set last_gs_rec_no = last_gs_rec_no + @nmyDeliveriesCreatedCount </frame> <frame procname="C_HELP.dbo.Client_myDeliveries_I_Std" line="86" stmtstart="7306" stmtend="8324" sqlhandle="0x030006000d09a1438efcba0074a8000001000000000000000000000000000000000000000000000000000000"> insert into [ALL].dbo.i_I_deliveries --[columns] select --[columns] from #Client_deliveries_PO_stg stg </frame> <frame procname="C_HELP.dbo.Client_myDeliveries_I_S" line="446" stmtstart="32832" stmtend="33032" sqlhandle="0x0300060028323b37f7a70101eca7000001000000000000000000000000000000000000000000000000000000"> exec dbo.Client_myDeliveries_I_Std @week_selector, @username, @factory_bin_location, @Parameter </frame> <frame procname="adhoc" line="6" stmtstart="216" sqlhandle="0x02000000e49e3111676b7e3aec714d06946692f70e3a8a880000000000000000000000000000000000000000"> exec Client_myDeliveries_I_S </frame> </executionStack> <inputbuf> DECLARE @deadlock_var NCHAR(3); SET @deadlock_var = N'LOW'; SET DEADLOCK_PRIORITY @deadlock_var; exec Client_myDeliveries_I_S </inputbuf> </process> </process-list> <resource-list> <keylock hobtid="72057594562412544" dbid="5" objectname="ALL.dbo.myconstants" indexname="pk_myconstants" id="lockd5ad7b00" mode="RangeS-U" associatedObjectId="72057594562412544"> <owner-list> <owner id="process2b6e94cf8" mode="RangeS-S" /> </owner-list> <waiter-list> <waiter id="process28fc21868" mode="X" requestType="convert" /> </waiter-list> </keylock> <keylock hobtid="72057594562412544" dbid="5" objectname="ALL.dbo.myconstants" indexname="pk_myconstants" id="lock9466df00" mode="RangeS-U" associatedObjectId="72057594562412544"> <owner-list> <owner id="process28fc21868" mode="RangeS-S" /> </owner-list> <waiter-list> <waiter id="process2b6e94cf8" mode="X" requestType="convert" /> </waiter-list> </keylock> </resource-list> </deadlock> 所说的那样,如果写入管道,Linux输出会被块缓冲。

Li-yao Xia更改为

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). (有传言说95%的计算机问题可以通过strace来解决。)

strace

正如我们所看到的,该程序现已在% sudo strace -fp $(pidof deploy-trigger-exe) strace: Process 14197 attached 中被阻止。这是有道理的(因为另一方已断开连接)。

您可能会感到困惑的另一件事是它第二次打印accept sock

我认为你对Got socket(s)的工作方式存在误解。我总结你写getActivatedSockets。这告诉我你第二次打电话给forever $ getActivatedSockets >>= ...时,它会返回别的东西而不是第一次(特别是,我怀疑你认为它会在#34}之后返回getActivatedSockets ;以某种方式处理&#34;套接字。)

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

你写了

  

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

为实现这一目标,我认为您应该删除forever

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 以接收所有数据。

(快速插件:我正在帮助解决像这样的问题。)