nixlarfs
nixlarfs

Reputation: 104

Haskell: getProcessStatus blocking SIGINT

I'm trying to write a simple shell in Haskell, but I cant get the signal handling to work. If no command is running, sending SIGINT to the shell process triggers the signal handler. But when a blocking call to getProcessStatus is made, the signal is ignored. Sending a signal immediately to the child process of course kills the child and makes the blocking call return.

Replacing the blocking call with Control.Concurrent.threadDelay does not prevent the signal, i.e., everything works as intended. Replacing the blocking flag to getProcessStatus with False makes the function return before the child process has finished.

Reference to process package: https://hackage.haskell.org/package/unix-2.7.1.0/docs/System-Posix-Process.html#v:getProcessStatus

The relevant code is below, see the (only) commented line.

main :: IO ()
main = do
    pidRef <- (newIORef [] :: IO (IORef [ProcessID]))
    setSigHant pidRef
    doPrompt pidRef

printPrompt :: IO ()
printPrompt = fdWrite stdError "λ➔ " >> return ()

doPrompt :: IORef [ProcessID] -> IO ()
doPrompt pidRef = do
    printPrompt
    tryLine <- try getLine :: IO (Either SomeException String)
    case tryLine of 
        Left _ -> do
            putStrLn ""
            exitSuccess
        Right line -> do
            tryCl <- try (parse line) :: IO (Either SomeException [Command])
            case tryCl of 
                Left e -> fdWrite stdError (show e ++ "\n") >> return ()
                Right cl -> 
                    if length cl > 0 && (cmd . head) cl == "cd" then
                        cd (head cl)
                    else do
                        execCommands pidRef cl (stdInput, stdOutput)
                        pids <- readIORef pidRef

                        -- This call to getProcessStatus blocks the signals
                        _ <- sequence $ map (getProcessStatus True False) pids
                        _ <- writeIORef pidRef []
                        return ()
            doPrompt pidRef

setSigHant :: (IORef [ProcessID]) -> IO ()
setSigHant pidRef = do
    let handler = Catch (sigIntHandler pidRef)
    installHandler sigINT handler Nothing
    return ()


sigIntHandler :: (IORef [ProcessID]) -> IO ()
sigIntHandler pidRef = do
    pids <- readIORef pidRef
    sequence_ $ map (signalProcess sigINT) pids
    fdWrite stdError "\n"
    printPrompt

Upvotes: 1

Views: 203

Answers (1)

danidiaz
danidiaz

Reputation: 27766

getProcessStatus uses an interruptible FFI call internally. But why is -threaded necessary?

This blog post about handling ctrl-c in Haskell suggests that signal handling is done in a separate thread that kills the main thread using an asynchronous exception:

When the user hits Ctrl-C, GHC raises an async exception of type UserInterrupt on the main thread. This happens because GHC installs an interrupt handler which raises that exception, sending it to the main thread with throwTo.

But the documentation for the async package mentions that:

Different Haskell implementations have different characteristics with regard to which operations block all threads.

Using GHC without the -threaded option, all foreign calls will block all other Haskell threads in the system, although I/O operations will not. With the -threaded option, only foreign calls with the unsafe attribute will block all other threads.

So maybe that's why proper handling of SIGINT in presence of interruptible ffi calls requires -threaded: otherwise, the thread that throws the asynchronous exception will be prevented from running.

Upvotes: 1

Related Questions