Haskell:如何使运行外部命令的函数超时

2024-03-15

我在函数内调用外部程序。现在我想让这个函数超时,而不仅仅是外部程序。但是在函数超时之后,外部程序仍然在我的计算机上运行(我使用的是debian)直到它完成计算,之后它的线程仍然作为我的主程序的子线程保留在进程表中,直到主程序终止。

这是两个最小的例子,说明了我想做的事情。第一个使用 unsafePerformIO,第二个完全在 IO monad 中。我并不真正依赖 unsafePerformIO,但如果可能的话希望保留它。无论有没有它都会出现所描述的问题。

使用 unsafePerformIO

module Main where

import System.Timeout
import Criterion.Measurement
import System.IO.Unsafe
import System.Process

main = do
    x <- time $ timeoutP (1 * 1000000) $ mytest 2
    y <- getLine
    putStrLn $ show x ++ y

timeoutP :: Int -> a -> IO (Maybe a)
timeoutP t fun = timeout t $ return $! fun

mytest :: Int -> String
mytest n =
  let
    x = runOnExternalProgram $ n * 1000
  in
    x ++ ". Indeed."

runOnExternalProgram :: Int -> String
runOnExternalProgram n = unsafePerformIO $ do
    -- convert the input to a parameter of the external program
    let x = show $ n + 12
    -- run the external program
    -- (here i use "sleep" to indicate a slow computation)
    answer <- readProcess "sleep" [x] ""
    -- convert the output as needed
    let verboseAnswer = "External program answered: " ++ answer
    return verboseAnswer

没有 unsafePerformIO

module Main where

import System.Timeout
import Criterion.Measurement
import System.IO.Unsafe
import System.Process

main = do
    x <- time $ timeout (1 * 1000000) $ mytest 2
    y <- getLine
    putStrLn $ show x ++ y

mytest :: Int -> IO String
mytest n = do
    x <- runOnExternalProgram $ n * 1000
    return $ x ++ ". Indeed."

runOnExternalProgram :: Int -> IO String
runOnExternalProgram n = do
    -- convert the input to a parameter for the external program:
    let x = show $ n + 12
    -- run the external program
    -- (here i use "sleep" to indicate a slow computation):
    answer <- readProcess "sleep" [x] ""
    -- convert the output as needed:
    let verboseAnswer = "External program answered: " ++ answer
    return verboseAnswer

也许支架在这里可以有所帮助,但我真的不知道如何。

编辑:我采纳了约翰·L的回答。现在我正在使用以下内容:

import Control.Concurrent
import Control.Exception
import System.Exit
import System.IO
import System.IO.Error
import System.Posix.Signals
import System.Process
import System.Process.Internals

safeCreateProcess :: String -> [String] -> StdStream -> StdStream -> StdStream
                  -> ( ( Maybe Handle
                       , Maybe Handle
                       , Maybe Handle
                       , ProcessHandle
                       ) -> IO a )
                  -> IO a
safeCreateProcess prog args streamIn streamOut streamErr fun = bracket
    ( do
        h <- createProcess (proc prog args) 
                 { std_in  = streamIn
                 , std_out = streamOut
                 , std_err = streamErr
                 , create_group = True }
        return h
    )
-- "interruptProcessGroupOf" is in the new System.Process. Since some
-- programs return funny exit codes i implemented a "terminateProcessGroupOf".
--    (\(_, _, _, ph) -> interruptProcessGroupOf ph >> waitForProcess ph)
    (\(_, _, _, ph) -> terminateProcessGroup ph >> waitForProcess ph)
    fun
{-# NOINLINE safeCreateProcess #-}

safeReadProcess :: String -> [String] -> String -> IO String
safeReadProcess prog args str =
    safeCreateProcess prog args CreatePipe CreatePipe Inherit
      (\(Just inh, Just outh, _, ph) -> do
        hPutStr inh str
        hClose inh
        -- fork a thread to consume output
        output <- hGetContents outh
        outMVar <- newEmptyMVar
        forkIO $ evaluate (length output) >> putMVar outMVar ()
        -- wait on output
        takeMVar outMVar
        hClose outh
        return output
-- The following would be great, if some programs did not return funny
-- exit codes!
--            ex <- waitForProcess ph
--            case ex of
--                ExitSuccess -> return output
--                ExitFailure r ->
--                    fail ("spawned process " ++ prog ++ " exit: " ++ show r)
      )

terminateProcessGroup :: ProcessHandle -> IO ()
terminateProcessGroup ph = do
    let (ProcessHandle pmvar) = ph
    ph_ <- readMVar pmvar
    case ph_ of
        OpenHandle pid -> do  -- pid is a POSIX pid
            signalProcessGroup 15 pid
        otherwise -> return ()

这解决了我的问题。它会在正确的时间杀死生成进程的所有子进程。

亲切的问候。


编辑:可以获得生成进程的 pid。您可以使用如下代码来执行此操作:

-- highly non-portable, and liable to change between versions
import System.Process.Internals

-- from the finalizer of the bracketed function
-- `ph` is a ProcessHandle as returned by createProcess
  (\(_,_,_,ph) -> do
    let (ProcessHandle pmvar) = ph
    ph_ <- takeMVar pmvar
    case ph_ of
      OpenHandle pid -> do  -- pid is a POSIX pid
        ... -- do stuff
        putMVar pmvar ph_

如果你杀死进程,而不是打开ph_在 mvar 中,您应该创建一个适当的ClosedHandle然后把它放回去。重要的是,此代码要以屏蔽方式执行(括号将为您执行此操作)。

现在您已经有了 POSIX id,您可以根据需要使用系统调用或 shell out 来终止。如果你走这条路,请注意你的 Haskell 可执行文件不在同一个进程组中。

/结束编辑

这种行为似乎有点明智。的文档timeout声称它对于非 Haskell 代码根本不起作用,事实上我没有看到它可以通用的任何方法。发生的事情是这样的readProcess产生一个新进程,但在等待该进程的输出时超时。看起来readProcess当异常中止时,不会终止生成的进程。这可能是一个错误readProcess,或者可能是设计使然。

作为一种解决方法,我认为您需要自己实施其中的一些内容。timeout通过在生成的线程中引发异步异常来工作。如果你把你的runOnExternalProgram在异常处理程序中,您将得到您想要的行为。

这里的关键功能是新的runOnExternalProgram,它是原始函数和readProcess。制作一个新的会更好(更模块化、更可重用、更可维护)readProcess当引发异常时,它会杀死生成的进程,但我将把它留作练习。

module Main where

import System.Timeout
import Criterion.Measurement
import System.IO.Unsafe
import System.Process
import Control.Exception
import System.IO
import System.IO.Error
import GHC.IO.Exception
import System.Exit
import Control.Concurrent.MVar
import Control.Concurrent

main = do
    x <- time $ timeoutP (1 * 1000000) $ mytest 2
    y <- getLine
    putStrLn $ show x ++ y

timeoutP :: Int -> IO a -> IO (Maybe a)
timeoutP t fun = timeout t $ fun

mytest :: Int -> IO String
mytest n = do
  x <- runOnExternalProgram $ n * 1000
  return $ x ++ ". Indeed."

runOnExternalProgram :: Int -> IO String
runOnExternalProgram n = 
    -- convert the input to a parameter of the external program
    let x = show $ n + 12
    in bracketOnError
        (createProcess (proc "sleep" [x]){std_in = CreatePipe
                                         ,std_out = CreatePipe
                                         ,std_err = Inherit})
        (\(Just inh, Just outh, _, pid) -> terminateProcess pid >> waitForProcess pid)

        (\(Just inh, Just outh, _, pid) -> do
          -- fork a thread to consume output
          output <- hGetContents outh
          outMVar <- newEmptyMVar
          forkIO $ evaluate (length output) >> putMVar outMVar ()

          -- no input in this case
          hClose inh

          -- wait on output
          takeMVar outMVar
          hClose outh

          -- wait for process
          ex <- waitForProcess pid

          case ex of
            ExitSuccess -> do
              -- convert the output as needed
              let verboseAnswer = "External program answered: " ++ output
              return verboseAnswer
            ExitFailure r ->
              ioError (mkIOError OtherError ("spawned process exit: " ++ show r) Nothing Nothing) )
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Haskell:如何使运行外部命令的函数超时 的相关文章

  • 如何处理或避免BlockedIndefinitelyOnSTM异常?

    我花了很多时间来解决我正在处理的应用程序中遇到的问题 该应用程序是一个 Web 应用程序 使用 scotty 公开 REST 端点 它使用一个TVar保持其更新的状态STM a由前端层触发的动作 由于该应用程序基于事件溯源原则 因此业务层生
  • Haskell 中的多态函数作为参数

    我有一个带有两个构造函数的 ADT 一个包裹着一个Double和一个包裹着Integer 我想创建一个函数 它采用一元函数Numtypeclass 并返回一个函数 该函数将该一元函数应用于我的 ADT 的内容 我试过这个 data X Y
  • 为什么 Haskell (Hugs) 中的 Show 实例会导致堆栈溢出错误?

    下面是 Haskell 中的多态数据类型 由 Hugs 解释 我正在尝试创建一个 Show for Equality 的实例 实例声明表示 如果类型 a 在 Show 中 则相等 a 在 Show 中 它应该以 a b 的形式打印构造函数
  • haskell复制目录的方法是什么

    我发现自己用 Haskell 编写越来越多的脚本 但在某些情况下 我真的不确定如何 正确 地做到这一点 例如递归地复制目录 a la unixcp r 由于我主要使用 Linux 和 Mac OS 所以我通常会作弊 import Syste
  • 如何在 GHCJS 程序中定期执行操作?

    应该有人使用setInterval通过Javascript 或者使用一些更惯用的基于线程的解决方案 Using setInterval posed 一些挑战 https stackoverflow com questions 3357661
  • 计算出类型索引的自由 monad 的细节

    我一直在使用免费的 monad 来构建 DSL 作为语言的一部分 有一个input命令 目标是在类型级别反映输入原语期望的类型 以提高安全性 例如 我希望能够编写以下程序 concat Action String String concat
  • 如何计算函数被调用的次数,FP方式

    我目前正在通过SICP http mitpress mit edu sicp 与哈斯克尔 练习 1 15 询问一个函数被调用了多少次 这个想法可能是您应该使用替换方法 但我想知道如何在代码中执行此操作 在命令式语言中 我们可以保留一个全局变
  • 如何从有向无环图导出FRP?

    我目前正在研究我的下一个项目 目前处于预规划阶段 因此这个问题只是为了了解现有技术的概述 Setup 我有一个具有多个输入和输出的有向无环图 DAG 现在考虑人工神经网络 处理这种结构的常见方法是在每个 时间 步骤上处理整个网络 我相信这是
  • 如何对 Ant 任务应用超时?

    如果不编写自定义 Ant 任务 有没有办法在常规 ant 目标上使用超时 提供一些背景信息 我们使用 删除 任务来删除给定目录的内容 有时这个目录很大 有很多生成的文件夹和文件 我们希望任务在 5 分钟后超时 您可能会使用parallel
  • enumFromTo 如何工作?

    我无法将号码添加到Char 以下内容将无法编译 a 1 但是 a z 成功创建一个字符串 其中每个字符值都会递增 有没有一个特殊的函数可以增加Char 我知道我能做到chr ord c 1 如何 a z 或底层enumFromTo函数增加结
  • 使用 GHC.Generics 恢复类型定义

    昨天我尝试回答这个问题是关于数据类型的表示 https stackoverflow com questions 22715572 a serializable representation of a data type for client
  • 无法让 wxHaskell 在 Mac 上从 ghci 工作

    我正在尝试跑步一个例子 http www haskell org haskellwiki WxHaskell Quick start Hello world in wxHaskell using EnableGUI function htt
  • 在 Web.Scotty 中使用 StateT

    我正在尝试制作一个愚蠢的网络服务器 将数据存储为State 我在用着Web Scotty http hackage haskell org package scotty 我之前用过 ReaderT 和 scotty 来访问配置 https
  • Haskell - 让函数返回空字符

    我正在尝试创建一个函数来删除字符串中的每个第 n 个元素 dropEvery String gt Int gt String dropEvery str n map char indx gt if indx mod n 0 then cha
  • 带边界的 haskell 列表数据类型

    我有以下类型定义来表示卡片 data Suit Hearts Spades Diamonds Clubs data Rank Numeric Integer Jack Queen King Ace data Card Card Rank S
  • Haskell 中的模式匹配正则表达式模式

    在 Scala 中 我有一个正则表达式模式匹配 如下所示 val Regex d 4 d 2 d 2 r val Regex year month day 2013 01 06 结果是 year String 2013 month Stri
  • 如何构图“也许”镜头?

    如果我有嵌套记录的镜头 其中每个镜头返回一个Maybe 我怎样才能让它们组合起来 这样如果 遍历 中有任何东西返回一个Nothing最终结果是Nothing data Client Client clientProperties Maybe
  • Haskell:打印文本编码

    Haskell 新手在这里 ghc version The Glorious Glasgow Haskell Compilation System version 6 12 1 在尝试调试第三方 Haskell 程序中与区域设置相关的奇怪错
  • 列出树中叶子的路径

    我正在尝试编写一个函数来查找树中叶子的所有路径 例如 给定一棵如下所示的树 1 2 5 3 4 6 输出列表将是 1 2 3 1 2 4 1 5 6 该函数的类型签名是 branches Tree a gt a 请注意 这使用了中定义的 T
  • 当测试文件定义为模块时,使用堆栈调用 hspec 定义的测试会抛出错误

    我试图弄清楚为什么包含定义为模块的单元测试的测试文件在运行时失败stack build test 假设有一个从头开始定义的简单测试模块 stack new test module cd test module vim package yam

随机推荐

  • jsf 安全约束在用户未登录时保护链接?

    我有一个定义了安全约束的 JSF2 GlassFish 3 0 应用程序 示例如下 我的问题是 我有一个 注册 链接 当用户登录时不应访问该链接 也就是说 如果他们尝试点击 signup jsf 如果他们已登录 他们应该能够访问 所以如果他
  • Rails 模型 has_many 具有多个foreign_keys

    对 Rails 来说相对较新 并尝试使用具有姓名 性别 father id 和 mother id 2 个父母 的单个 Person 模型来建模非常简单的家庭 树 下面基本上是我想做的 但显然我不能在 has many 中重复 childr
  • 是否可以从 Android 应用程序执行 Shell 脚本

    我一直在尝试 Android 终端模拟器 Termux 这是一款优秀的应用程序 无需 root 访问即可访问 Android 操作系统 我希望能够实现的是从同一设备上安装的另一个 Android 应用程序中执行 Termux 中的脚本 命令
  • Storm程序的执行流程

    我是 Storm 的新手 试图了解不同方法的执行流程spout to bolt 就像spout有不同的方法一样 下一个元组 open 声明输出字段 启用 停用 Bolt 有类似的方法 准备 执行 清理 声明输出字段 那么谁能告诉我这些方法的
  • 将数据传递给 Polymer 元素

    使用 Web UI 时 我可以将数据传递给组件 如下所示
  • 卷积神经网络的 float16 与 float32

    标准是float32 但我想知道在什么条件下可以使用float16 我比较了运行相同的 covnet 和两种数据类型 没有发现任何问题 对于大型数据集 我更喜欢 float16 因为我可以更少担心内存问题 令人惊讶的是 使用 16 位是完全
  • 在docker中挂载两个嵌套的只读挂载点

    我有一个docker compose文件运行 PHP 并以嵌套方式安装一些卷 它已经工作了两年了 最近我又尝试了一次 但失败了 至少在 Docker for WSL 2 上是这样 以下是 docker compose 文件中挂载的卷 vol
  • 如何设置 git 提交消息的模式?

    我想限制提交的人具有特定的提交消息格式 我该怎么做 例如 Pair Name Story Number Commit Message 有一个pre commit msg or commit msg钩子 你可以使用 http www kern
  • 使用函数给定的值初始化 numpy 数组的最快方法

    我主要对 d1 d2 numpy 数组 矩阵 感兴趣 但这个问题对于具有更多轴的数组来说是有意义的 我有函数 f i j 我想通过该函数的某些操作来初始化数组 A np empty d1 d2 for i in range d1 for j
  • JFrame java中的无限消失-重新出现循环

    作为继这个帖子 https stackoverflow com questions 34593471 why do i need to reset settext in a jlabel to prevent errors 34598241
  • 如何获取侧边栏下拉列表中所有工作表的列表

    我正在谷歌工作表的侧边栏菜单中工作 以设置我的脚本中需要的一些变量 我的脚本已经可以运行了 这很简单 在提交表单时 它将在我的日历中创建一个事件 但是 为了简化脚本的安装 我想在侧边栏中显示一个下拉列表 其中包含工作表中所有工作表的名称 我
  • 克隆Conda根环境不会克隆conda和condo-build

    我在 OS X El Capitan 10 11 4 上使用 conda 4 2 9 我使用以下命令克隆了根环境 conda create n rootclone clone root 它给出了以下消息 The following pack
  • 为什么 spread() 方法在 Sequelize 中不起作用?

    我正在使用一个Sequelize for my node js应用程序 我用findOrCreate 方法创建新用户 如果不存在 据此docs http docs sequelizejs com manual tutorial models
  • 如何更改微调器背景颜色?

    如何更改微调器背景颜色 这种黑色来自风格 如何更改弹出窗口的样式颜色 我想将背景颜色更改为白色而不是黑色 我该如何改变 spinnner
  • 使用 Moose 时在构造时分配方法体的最佳方法是什么?

    我在用着Moose 具体来说MooseX Declare 创建一个迭代器对象 Iter其中有一个next前进状态并返回的方法0 or 1根据需要使用在while陈述 我遇到的问题是 根据构造参数之一的存在 next需要执行两组截然不同的操作
  • 如何将 jquery ui 与 Bower 一起使用?

    我正在尝试yeoman http yeoman io and bower http bower io 我使用以下命令创建了一个 yeoman webapp yo webapp 我想用jqueryui http jqueryui com 所以
  • 如何在组件绘制时创建“请稍候”Swing 对话框

    对于 Swing 来说仍然相对较新 但经过几个小时的搜索 我无法在网上找到答案 因此写了这篇文章 抱歉 如果已经回答但我忽略了它 我在 Swing 应用程序中使用 JFreeChart 有些图表相对较重 180k 数据点 并且 JFreeC
  • 使用javascript正则表达式验证日期的问题[关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 我正在尝试
  • React Js Es6 风格的三元运算符

    我正在尝试添加以下三元运算符以在我登录时显示我的按钮并且如果我不隐藏它 下面的内容不断向我抛出错误 img src this state photo alt style display display none 您提供给 style 属性的
  • Haskell:如何使运行外部命令的函数超时

    我在函数内调用外部程序 现在我想让这个函数超时 而不仅仅是外部程序 但是在函数超时之后 外部程序仍然在我的计算机上运行 我使用的是debian 直到它完成计算 之后它的线程仍然作为我的主程序的子线程保留在进程表中 直到主程序终止 这是两个最