Opened 3 years ago

Last modified 4 months ago

#9285 patch task

IO manager startup procedure somewhat odd

Reported by: edsko Owned by: simonmar
Priority: low Milestone: 8.4.1
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 (6)

comment:1 Changed 3 years 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 3 years 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 3 years ago by AndreasVoellmy

Cc: AndreasVoellmy added

comment:4 Changed 3 years ago by thomie

Cc: simonmar added
Component: CompilerRuntime System
Owner: set to simonmar

comment:5 Changed 3 years ago by thomie

Status: newpatch
Type: bugtask

comment:6 Changed 4 months ago by dfeuer

Milestone: 8.4.1

Setting a milestone.

Note: See TracTickets for help on using tickets.