如何使 FFI 调用可中断

How to make FFI call interruptible

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 秒后停止,但是即使在打印 "Finished" 之后它仍会继续运行 .它确实在用户指南中提到了 This is **usually** enough to cause a blocking system call to return <...>,所以这种情况是我的 c 函数特别糟糕,还是我在 Haskell 方面做错了什么?

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

现在在一个理想的世界中,修改您的函数以检查 return 值表明函数已被中断就足够了,如下所示:

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

不幸的是,sleep() 的界面是脑残的——如果它被打断,它 return 是剩余的完整秒数,如果这是零 - - 它始终在您的函数中 - 它 return 为零 。叹息...

您可以切换到 usleep,如果被打断,这会明智地 returns -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);
}