如何使 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 .由于您的外部函数不检查 printf
和 sleep
的 return 调用以查看它们是否被中断,因此线程会愉快地进行。
现在在一个理想的世界中,修改您的函数以检查 return 值表明函数已被中断就足够了,如下所示:
void loopForever()
{
for (;;)
{
if (printf("Tick\n") < 0) break;
if (sleep(1) != 0) break;
}
}
不幸的是,sleep()
的界面是脑残的——如果它被打断,它 return 是剩余的完整秒数,如果这是零 - - 它始终在您的函数中 - 它 return 为零 。叹息...
您可以切换到 usleep
,如果被打断,这会明智地 returns -1
,或者您可以使用将 errno
设置为零并检查是否 printf
或 sleep
更改它(到 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);
}
据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 .由于您的外部函数不检查 printf
和 sleep
的 return 调用以查看它们是否被中断,因此线程会愉快地进行。
现在在一个理想的世界中,修改您的函数以检查 return 值表明函数已被中断就足够了,如下所示:
void loopForever()
{
for (;;)
{
if (printf("Tick\n") < 0) break;
if (sleep(1) != 0) break;
}
}
不幸的是,sleep()
的界面是脑残的——如果它被打断,它 return 是剩余的完整秒数,如果这是零 - - 它始终在您的函数中 - 它 return 为零 。叹息...
您可以切换到 usleep
,如果被打断,这会明智地 returns -1
,或者您可以使用将 errno
设置为零并检查是否 printf
或 sleep
更改它(到 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);
}