Как сделать так, чтобы звонок FFI прерывался - PullRequest
0 голосов
/ 06 сентября 2018

Согласно Руководству пользователя GHC внешний вызов может быть помечен interruptible, однако я не могу заставить его работать. Я использую ghc 8.4.3 в GNU / Linux.

См., Например, этот 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 голосов
/ 06 сентября 2018

Согласно документации, RTS пытается прервать поток, занятый внешним вызовом, путем отправки ему сигнала SIGPIPE. Так как обработчик, установленный RTS, игнорирует сигнал, единственный эффект состоит в том, что - если поток занят длительным системным вызовом - этот вызов, вероятно, немедленно возвратится с EINTR. Поскольку ваша внешняя функция не проверяет возвратные вызовы printf и sleep, чтобы определить, были ли они прерваны, поток весело продолжает свой путь.

Теперь в идеальном мире было бы достаточно изменить вашу функцию для проверки возвращаемых значений, указывающих, что функции были прерваны, например:

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

К сожалению, интерфейс для sleep() является braindead - если он прерван, он возвращает количество полных секунд, оставшихся до конца, , а если это ноль - что всегда есть в вашей функции - он возвращает ноль . Вздох ...

Вы можете либо переключиться на usleep, который разумно вернет -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);
}
...