IO manager startup procedure somewhat odd
Currently the RTS startup procedure for the threaded runtime with a single capability looks something like this:
1. Capability gets initialized
hs_main real_main hs_init_ghc initScheduler initCapabilities
2. IO Manager is started
hs_main real_main hs_init_ghc ioManagerStart
The IO manager of course is mostly written in Haskell, so
cap = rts_lock(); rts_evalIO(&cap,&base_GHCziConcziIO_ensureIOManagerIsRunning_closure,NULL); rts_unlock(cap);
4. Bound task is created and we start running Haskell code
Since this is the first call to
rts_lock, it creates a bound task to correspond to the current OS thread.
5. Haskell code does a safe FFI call: create worker task 1
The first safe FFI call that the IO manager makes (ignoring
labelThread) is to
c_setIOManagerControlFd. At this point, the bound task gets suspended and we create a worker task.
6. Worker task 1 starts executing
The worker tasks gets suspended due to an explicit call to
GHC.Event.Manager.step, at which point the bound task continues execution. It continues and finishes (
ensureIOManagerRunning completes). The worker task takes over.
7. Worker task 1 does a safe FFI call to
I.poll, create new worker task 2
(This safe call is from
8. Worker task 2 executes
GHC.Event.TimerManager.loop, and does safe FFI call to
I.poll; create worker task 3
This worker task will remain spare for now.
9. Start executing the actual main function
The IO manager calls
rts_unlock, which verifies if there are any spare workers available; it finds that there are and doesn't start a new one.
Then when we start
main itself of course we go through this
rts_lock, rts_evalIO, rts_unlock sequence again. This is somewhat confusing, and makes tracing the startup of the code more difficult to interpret. It seems that this could be simpler: only a single
rts_lock should be necessary, in which we first call
ensureIOManagerIsRunning and then
main, followed by a single
rts_unlock. A similar simplification should be possible in