multithreadinghaskellghcyesodghcid

Is there a way to kill all forked threads in a GHCi session without restarting it?


Based on my previous question I'd like to ask if there's any way to kill all user created threads in a GHCi session?

The reason for this is that when a function exits in GHCi the threads that it spawned don't terminate automatically, persisting even through code reloads. Restarting GHCi solves this, but since my application takes a while to load, it would be great if there was a possible (even hacky) workaround.


Solution

  • No, and actually for almost the same reasons as in How can I build a ThreadId given that I know the actual number?: The library simply doesn't give you anything to get the ThreadIds of all (still running) threads or any other facility to work on any threads which doesn't belong to you.

    Furthermore, you cannot reliably guess which threads spawned with forkIO belong to your GHCi session (all evaluations are usually sandboxed in forkIO), the underlying yesod application, or to the threaded RTS (which has at least one call to forkIO - which basically ensures that all event managers get shut down). Currently, this isn't too bad, since GHCi runs in the main thread and the IO manager gets restarted if shut down anyway, but that could change in future versions.

    So why do threads even get collected on termination? hs_exit(). Essentially, it calls ioManagerDie() (killing all event managers) and exitScheduler(..) (see scheduler, which basically kills all threads. None of those functions have an appropriate FFI wrapper.

    At the time hs_exit() is called, the main from the Haskell world has already finished. Since none of those functions have an appropriate equivalent in the GHC.* modules, you cannot call them directly in Haskell and therefore in GHCi, since there isn't an appropriate :# command.

    So all in all, you can't. If one would add a command to restart the scheduler in GHCi, it would be easy as pie. But given that the scheduler gets started in hs_init() and stopped in hs_exit() in the RTS model, I doubt that this would be a easy extension.


    Depending on what you want to do, you can however cheat. You can write your own forkIOMem, which stores the ThreadId in a global MVar, and replace all of forkIO in the sources. That can be very cumbersome, especially if you're working with a library, since you need to ensure that forkIO is replaced everywhere.

    You could, of course, meddle with the base package, but that's probably madness (still possible though), change forkIO there and add killAllforkIOs to Control.Concurrent. (I've already said that this is probably madness, right?)