【问题标题】:How to make FFI call interruptible如何使 FFI 呼叫可中断
【发布时间】:2018-09-05 22:41:23
【问题描述】:

根据GHC user guide外呼可以标记interruptible,但是我不能让它工作。我在 GNU/Linux 上使用 ghc 8.4.3

例如看这个 cbits.h:

/* cbits.h */

void loopForever();

cbits.c:

/* cbits.c */

#include <stdio.h>
#include <unistd.h>

#include "cbits.h"

void loopForever()
{
  for (;;)
  {
    printf("Tick\n");
    sleep(1);
  }
}

最后是 Test.hs:

-- Test.hs

{-# LANGUAGE InterruptibleFFI #-}

module Main where

import Control.Concurrent
import Control.Concurrent.Async

main :: IO ()
main = race_ loopForever $ do
  threadDelay 2000000
  putStrLn "Finished"

foreign import ccall interruptible "cbits.h"
  loopForever :: IO ()

我和ghc -threaded -o a.out cbits.c Test.hs一起编译的。

现在,我预计代码会在 2 秒后停止,但它会继续运行,即使在打印“完成”之后也是如此。它确实在用户指南中提到了This is **usually** enough to cause a blocking system call to return &lt;...&gt;,所以这是我的 c 函数特别糟糕的情况,还是我在 Haskell 方面做错了什么?

【问题讨论】:

    标签: haskell interrupt ghc ffi


    【解决方案1】:

    根据文档,RTS 尝试中断参与外部调用的线程的方式是向其发送 SIGPIPE 信号。由于 RTS 安装的处理程序会忽略该信号,因此唯一的影响是——如果线程正在进行长时间运行的系统调用——该调用可能会立即返回一个 EINTR。由于您的外部函数不会检查 printfsleep 的返回调用以查看它们是否被中断,因此线程会愉快地进行。

    现在在一个理想的世界里,修改你的函数来检查返回值是否表明函数已被中断就足够了,就像这样:

    void loopForever()
    {
      for (;;)
      {
        if (printf("Tick\n") < 0) break;
        if (sleep(1) != 0) break;
      }
    }
    

    不幸的是,sleep() 的接口已经死了——如果它被中断,它会返回剩余的整秒数,如果它是零——它总是在你的函数中——它返回零。叹息……

    您可以切换到usleep,如果被中断,它会明智地返回-1,或者您可以使用将errno 设置为零并检查printfsleep 是否更改它(到EINTR,但是你也可以在任何非零数字上中止):

    /* cbits.c */
    
    #include <errno.h>
    #include <stdio.h>
    #include <unistd.h>
    
    #include "cbits.h"
    
    void loopForever()
    {
      errno = 0;
      while (!errno)
      {
        printf("Tick\n");
        sleep(1);
      }
    }
    

    这应该可以满足您的需求。

    现在,根据您的实际用例,您可能会发现安装 SIGPIPE 处理程序更有帮助,特别是如果您希望线程在长时间运行的计算中被阻塞(没有任何系统调用可中断)。完成后请务必卸载处理程序。这是一个例子:

    /* cbits.c */
    
    #include <errno.h>
    #include <signal.h>
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    
    #include "cbits.h"
    
    volatile sig_atomic_t stop = 0;
    
    void sigpipe_handler()
    {
      stop = 1;
    }
    
    void loopForever()
    {
      struct sigaction oldact, newact;
      bzero(&newact, sizeof(newact));
      newact.sa_handler = sigpipe_handler;
      sigaction(SIGPIPE, &newact, &oldact);
      while (!stop)
      {
        // loop forever until interrupted
      }
      printf("Stopped!");
      sigaction(SIGPIPE, &oldact, NULL);
    }
    

    【讨论】:

    • 这可能是正确的想法。也许还需要声明volatile int stop (?)。此外,我猜 SIGPIPE 可能会在安装信号处理程序之前引发,并且有一个用于竞争条件的小窗口——不过,这可能是不可避免的。
    • 我添加了volatile。我也看不出如何处理最初的比赛。
    • 如果 c 循环不在我的控制之下,我能做些什么吗?如果库提供了,我可以设置处理程序并调用 end_loop,但如果没有呢?
    • @Cthulhu 我认为在这种情况下没有什么明智的做法。我的意思是,可以尝试在 OS 线程中运行该调用,然后终止该线程以强制其终止,但这可能会使 C 端处于不一致状态,从而在我们下次进入 C 领域时触发未定义的行为,如果不是更早的话.我的意思是,假设你的循环正在反转一个 C 双向链表,像往常一样处理指针。您不能真正在中间中断该循环并希望您的指针仍然形成一个列表,即您的“列表”处于一致状态。
    • @chi 我想这是有道理的,C 语言级别太低,并且没有为类似异常的流程提供工具。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多