使用Haskell Streamly-process后台运行GHCi REPL遇阻塞问题求助
问题:Streamly-Process 与后台GHCi通信时用户输入无响应
尝试用streamly-process与后台REPL(以GHCi为例)通信,当用Stream.fromList传入预设命令时,GHCi可正常执行并返回结果;但改用Stream.repeatM getNewLn获取用户输入时,输入内容后无任何响应,求问题原因及解决方法。
代码示例
import Data.Word import qualified Streamly.Data.Stream.Prelude as Stream import qualified Streamly.Data.Fold.Prelude as Fold import qualified Streamly.System.Process as Process import qualified Streamly.Console.Stdio as Stdio import qualified Streamly.Data.Array.Foreign as Array stringToByteArray :: String -> Array.Array Word8 stringToByteArray = Array.fromList . map (fromIntegral . fromEnum) -- 保留换行符的getLine版本 getNewLn :: IO String getNewLn = fmap (++ "\n") getLine main :: IO () main = do Stream.fold (Fold.takeEndBy ( == stringToByteArray "Leaving GHCi.") Stdio.writeChunks) $ Process.pipeChunks "ghci" [] $ fmap stringToByteArray $ Stream.repeatM getNewLn -- 使用Stream.fromList时正常,取消注释以下代码测试: -- Stream.fromList ["putStrLn \"This works ! \"\n", " 3 + 4\n"]
现象对比
使用Stream.fromList的正常输出
GHCi, version 9.8.2: https://www.haskell.org/ghc/ :? for help ghci> This works ! ghci> 7 ghci> Leaving GHCi.
使用Stream.repeatM getNewLn的异常情况
GHCi, version 9.8.2: https://www.haskell.org/ghc/ :? for help ghci> I'm typing the return key several times but nothing happens ...
问题原因
GHCi会根据标准输入的类型自动切换缓冲模式:
- 当stdin是终端时,采用行缓冲,输入一行就立即处理;
- 当stdin是管道(
Process.pipeChunks创建的管道属于此类)时,会切换为块缓冲,只有当缓冲区被填满时才会读取并处理输入。
用Stream.fromList时,所有预设命令会一次性发送到GHCi的stdin,直接填满缓冲区触发处理;但用Stream.repeatM getNewLn时,每次仅发送一行数据,远未达到块缓冲的阈值,因此GHCi不会立即处理输入,表现为无响应。
解决方案
核心思路是让GHCi认为自己的stdin是终端,保持行缓冲模式,以下是两种可行方案:
方案1:用script命令包装GHCi
script命令会创建伪终端(PTY)让GHCi运行,使其识别stdin为终端,从而使用行缓冲。修改启动命令即可:
main :: IO () main = do Stream.fold (Fold.takeEndBy ( == stringToByteArray "Leaving GHCi.") Stdio.writeChunks) $ Process.pipeChunks "script" ["-q", "-c", "ghci", "/dev/null"] $ fmap stringToByteArray $ Stream.repeatM getNewLn
参数说明:-q关闭script自身的冗余输出,-c ghci指定要运行的命令,/dev/null作为日志文件避免生成临时文件。
方案2:使用Streamly的伪终端API(针对新版Streamly)
部分新版本的Streamly提供了直接分配伪终端的API,可以为子进程指定PTY,确保stdin被识别为终端。示例代码如下(需参考对应版本的Streamly文档确认API细节):
import qualified Streamly.System.Process as Process main :: IO () main = Process.withPty $ \pty -> do Stream.fold (Fold.takeEndBy ( == stringToByteArray "Leaving GHCi.") Stdio.writeChunks) $ Process.pipeChunksWithPty pty "ghci" [] $ fmap stringToByteArray $ Stream.repeatM getNewLn
内容的提问来源于stack exchange,提问作者141592653
相关产品推荐
相关产品推荐

