终止进程时捕获输出

Capture the output while terminating a process

我需要 运行 一个进程,在它 运行ning 时做一些事情,最后终止它。这 有问题的过程将我想保留的东西写入标准输出。很遗憾, 似乎这个过程在我可以连接并提取它的最后一句话之前就已经死了。拥有稀缺 异步编程的经验,我很难找到一个好的解决方案。它 如果我能在RIO.Process的框架内完成这个任务就很幸运了,虽然我 如果无法避免,我准备走出去。 (请注意 RIO 使用了一个不寻常的 通过回调系统调用外部进程的方式。)

下面是一个高度简化的运行我正在努力实现的可行示例。

这是程序的仿真 运行:
(将其放入名为 x.sh 的文件中,然后说 chmod +x x.sh 使其可执行。)

#!/bin/sh

trap 'echo "Terminating..."; exit 0' TERM

echo "Initialization complete."

while true; do sleep 1; done

这是我的代码:
(把它放在一个名为X.hs的文件中,然后用ghc -package rio X.hs编译。)

{-#   language   NoImplicitPrelude   #-}
{-#   language   BlockArguments      #-}
{-#   language   OverloadedStrings   #-}

module Main where

import RIO
import RIO.Process
import Data.Text.IO (hGetContents, hGetLine)

main :: IO ()
main = runSimpleApp do
    proc "./x.sh" [ ]
        \processConfig -> withProcessWait_ (setStdout createPipe processConfig)
            \processHandle -> bracket_
                (initialize processHandle)
                (terminate processHandle)
                (return ())

initialize :: (HasProcessContext env, HasLogFunc env) => Process () Handle () -> RIO env ()
initialize processHandle = do
    x <- liftIO $ hGetLine (getStdout processHandle)
    if x == "Initialization complete." then return () else error "This should not happen."

terminate :: HasLogFunc env => Process () Handle () -> RIO env ()
terminate processHandle = do
    log' <- async $ liftIO $ hGetContents (getStdout processHandle)
    stopProcess processHandle
    log <- wait log'
    logInfo $ display log

事情是这样的:

% ./X
X: fd:3: hGetBuffering: illegal operation (handle is closed)

x.sh 在说什么,但我听不见。

管理此问题的正确方法是什么?

来自 the documentation for stopProcess:

Close a process and release any resources acquired. This will ensure terminateProcess is called, wait for the process to actually exit, and then close out resources allocated for the streams. In the event of any cleanup exceptions being thrown this will throw an exception.

(强调我的)你不希望 stopProcess 在你阅读输出之前这样做。你只想要terminateProcesswithProcessWait_ 会处理剩下的事情。不幸的是,你必须走出 RIO 才能做到这一点,使用 import System.Process (terminateProcess) 然后 liftIO $ terminateProcess (unsafeProcessHandle processHandle).

旁注:您有点误用 bracket_。由于 bracket_ 的 "middle" 是一个空操作,尤其是现在开始和结束实际上并没有获取或释放任何资源,所以它有点毫无意义。此外,完全不用 async,您可以在终止进程后正常读取输出,因为进程已经产生的输出不会在终止时消失。

这是您的代码,上面所有内容都已修复:

{-#   language   NoImplicitPrelude   #-}
{-#   language   BlockArguments      #-}
{-#   language   OverloadedStrings   #-}

module Main where

import RIO
import RIO.Process
import Data.Text.IO (hGetContents, hGetLine)
import System.Process (terminateProcess)

main :: IO ()
main = runSimpleApp do
    proc "./x.sh" [ ]
        \processConfig -> withProcessWait_ (setStdout createPipe processConfig)
            \processHandle -> do
                initialize processHandle
                terminate processHandle

initialize :: (HasProcessContext env, HasLogFunc env) => Process () Handle () -> RIO env ()
initialize processHandle = do
    x <- liftIO $ hGetLine (getStdout processHandle)
    if x == "Initialization complete." then return () else error "This should not happen."

terminate :: HasLogFunc env => Process () Handle () -> RIO env ()
terminate processHandle = do
    liftIO $ terminateProcess (unsafeProcessHandle processHandle)
    log <- liftIO $ hGetContents (getStdout processHandle)
    logInfo $ display log