IO manager startup procedure somewhat odd
|Reported by:||edsko||Owned by:||simonmar|
|Type of failure:||None/Unknown||Test Case:|
|Related Tickets:||Differential Revisions:|
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 ioManagerStart does
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 GHC.Event.Manager.loop
The worker tasks gets suspended due to an explicit call to yield in 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 GHC.Event.Manager.step)
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 forkProcess.