FFI, signals and exceptions

Edward Z. Yang ezyang at MIT.EDU
Thu Aug 26 12:56:54 EDT 2010


Excerpts from Simon Marlow's message of Thu Aug 26 04:08:06 -0400 2010:
> You don't want to do this for a bound thread (when target->bound != 
> NULL), because the OS thread will have interesting things on its C stack 
> and pthread_cancel discards the entire stack.  A worker thread on the 
> other hand has an uninteresting stack and we can easily make another one.

It seems possible that under certain (limited) circumstances, this would
be desirable behavior: for example, if we truly wanted to destroy the bound
thread-local state and start over from scratch.

> So you don't want to do blockedThrowTo, instead call raiseAsync to raise 
> the exception, and that should put the TSO back on the the run queue.

With:

    raiseAsync(cap, target, msg->exception, rtsFalse, NULL)
    // ....
    return THROWTO_SUCCESS;

the thread is successfully able to catch the exception!

        case BlockedOnCCall:
        case BlockedOnCCall_NoUnblockExc:
        {
    #ifdef THREADED_RTS
            Task *task = NULL;
            raiseAsync(cap, target, msg->exception, rtsFalse, NULL);
            if (!target->bound) {
                // walk all_tasks to find the correct worker thread
                for (task = all_tasks; task != NULL; task = task->all_link) {
                    if (task->incall->suspended_tso == target) {
                        break;
                    }
                }
            }
            if (task != NULL) {
                pthread_cancel(task->id);
                task->cap = NULL;
                task->stopped = rtsTrue;
            }
            return THROWTO_SUCCESS;
    #else
            blockedThrowTo(cap,target,msg);
            return THROWTO_BLOCKED;
    #endif
        }

Here is a new (working) implementation interruptible:

    interruptible :: a -> IO a -> IO a
    interruptible defaultVal m = do
        mresult <- newEmptyMVar -- transfer exception to caller
        mtid    <- newEmptyMVar
        let install = do
                installIntHandler (Catch ctrlc)
            cleanup oldHandler = do
                _ <- installIntHandler oldHandler
                return ()
            ctrlc = do
                hPutStrLn stderr "Caught signal"
                tid <- readMVar mtid
                throwTo tid E.UserInterrupt
            bracket = reportBracket . E.bracket install cleanup . const
            reportBracket action = do
                putMVar mresult =<< E.catches (liftM Right action)
                    [ E.Handler (\(e :: E.AsyncException) ->
                        return $ case e of
                            E.UserInterrupt -> Right defaultVal
                            _ -> Left (E.toException e)
                        )
                    , E.Handler (\(e :: E.SomeException) -> return (Left e))
                    ]
        putMVar mtid =<< forkIO (bracket m)
        either E.throw return =<< readMVar mresult -- one write only

Do you have any suggestions for stress-testing this code?

Cheers,
Edward


More information about the Glasgow-haskell-users mailing list