Opened 23 months ago

Last modified 14 months ago

#9285 patch task

IO manager startup procedure somewhat odd

Reported by: edsko Owned by: simonmar
Priority: low Milestone:
Component: Runtime System Version: 7.8.2
Keywords: Cc: AndreasVoellmy, simonmar
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:


Currently the RTS startup procedure for the threaded runtime with a single capability looks something like this:

1. Capability gets initialized


2. IO Manager is started


3. ensureIOManagerIsRunning

The IO manager of course is mostly written in Haskell, so ioManagerStart does

	cap = rts_lock();

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.

Change History (5)

comment:1 Changed 23 months ago by duncan

Actually it looks like forkProcess is already simpler. It only calls ioManagerStartCap(&cap); because it's already in the context of holding the capability.

So in summary, we're suggesting that in the rts startup we should be using ioManagerStartCap(&cap); in a context where we already hold the initial cap and before we run main.

comment:2 Changed 23 months ago by duncan

So I suppose I'm suggesting something like this:

diff --git a/rts/RtsStartup.c b/rts/RtsStartup.c
index 8e7e11d..38435d8 100644
--- a/rts/RtsStartup.c
+++ b/rts/RtsStartup.c
@@ -258,11 +258,6 @@ hs_init_ghc(int *argc, char **argv[], RtsConfig rts_config)
     // ToDo: make this work in the presence of multiple hs_add_root()s.
-    // ditto.
-#if defined(THREADED_RTS)
-    ioManagerStart();
     /* Record initialization times */
diff --git a/rts/RtsMain.c b/rts/RtsMain.c
index df63716..68b2c79 100644
--- a/rts/RtsMain.c
+++ b/rts/RtsMain.c
@@ -60,6 +60,9 @@ static void real_main(void)
     /* ToDo: want to start with a larger stack size */
        Capability *cap = rts_lock();
+#if defined(THREADED_RTS)
+       ioManagerStartCap(&cap);
        rts_evalLazyIO(&cap,progmain_closure, NULL);
        status = rts_getSchedStatus(cap);

comment:3 Changed 23 months ago by AndreasVoellmy

  • Cc AndreasVoellmy added

comment:4 Changed 19 months ago by thomie

  • Cc simonmar added
  • Component changed from Compiler to Runtime System
  • Owner set to simonmar

comment:5 Changed 14 months ago by thomie

  • Status changed from new to patch
  • Type changed from bug to task
Note: See TracTickets for help on using tickets.