如何使FFI通话可中断

时间:2018-09-05 22:41:24

标签: haskell interrupt ghc ffi

根据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 <...>,是因为我的c函数特别糟糕,还是在Haskell方面做错了?

1 个答案:

答案 0 :(得分:3)

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

现在在理想的世界中,只需修改您的函数以检查指示函数已被中断的返回值就足够了,就像这样:

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

不幸的是,sleep()的接口是死脑筋-如果被中断,它将返回剩余的整秒数,如果为零,则它始终在您的函数中- -返回零。 igh ...

您可以切换到usleep并在中断时明智地返回-1,也可以使用将errno设置为零并检查printf或{{ 1}}对其进行更改(更改为EINTR,但您也可以放弃任何非零数字):

sleep

那应该做你想要的。

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

/* 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);
  }
}