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.
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
forkIObelong to your GHCi session (all evaluations are usually sandboxed inforkIO), the underlying yesod application, or to the threaded RTS (which has at least one call toforkIO- which basically ensures that all event managers get shut down). Currently, this isn't too bad, since GHCi runs in themainthread 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 callsioManagerDie()(killing all event managers) andexitScheduler(..)(see scheduler, which basically kills all threads. None of those functions have an appropriate FFI wrapper.At the time
hs_exit()is called, themainfrom the Haskell world has already finished. Since none of those functions have an appropriate equivalent in theGHC.*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 inhs_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 theThreadIdin a globalMVar, and replace all offorkIOin the sources. That can be very cumbersome, especially if you're working with a library, since you need to ensure thatforkIOis replaced everywhere.You could, of course, meddle with the
basepackage, but that's probably madness (still possible though), changeforkIOthere and addkillAllforkIOstoControl.Concurrent. (I've already said that this is probably madness, right?)