Skip to content

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

3. ensureIOManagerIsRunning

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.

Trac metadata
Trac field Value
Version 7.8.2
Type Bug
TypeOfFailure OtherFailure
Priority low
Resolution Unresolved
Component Compiler
Test case
Differential revisions
BlockedBy
Related
Blocking
CC
Operating system
Architecture
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information