[Git][ghc/ghc][wip/io-manager-deadlock-detection] Deleted 5 commits: Make the IOManager API use CapIOManager rather than Capability
Duncan Coutts pushed to branch wip/io-manager-deadlock-detection at Glasgow Haskell Compiler / GHC WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below. Deleted commits: f340f464 by Duncan Coutts at 2026-02-11T22:48:35+00:00 Make the IOManager API use CapIOManager rather than Capability This makes the API somewhat more self-contained and more consistent. Now the IOManager API and each of the backends takes just the I/O manager structure. Previously we had a bit of a mixture, depending on whether the function needed access to the Capability or just the CapIOManager. We still need access to the cap, so we introduce a back reference to reach the capability, via iomgr->cap. Convert all uses in select and poll backends, but not win32 ones. Convert callers in the scheduler and elsewhere. Also convert the three CMM primops that call IOManager APIs. They just need to use Capability_iomgr(MyCapability()). - - - - - 1136fd5b by Duncan Coutts at 2026-02-16T00:30:48+00:00 Split posix/MIO.c out of posix/Signals.c The MIO I/O manager was secretly living inside the Signals file. Now it gets its own file, like any other self-respecting I/O manager. - - - - - ec170e68 by Duncan Coutts at 2026-02-16T00:30:50+00:00 Rationalise some scheduler run queue utilities Move them all to the same place in the file. Make some static that were used only internally. Also remove a redundant assignment after calling truncateRunQueue that is already done within truncateRunQueue. - - - - - c4312136 by Duncan Coutts at 2026-02-16T00:30:50+00:00 Rename initIOManager{AfterFork} to {re}startIOManager These are more accurate names, since these actions happen after initialisation and are really about starting (or restarting) background threads. - - - - - 24a403ed by Duncan Coutts at 2026-02-16T00:30:50+00:00 Add a TODO to the MIO I/O manager The direction of travel is to make I/O managers per-capability and have all their state live in the struct CapIOManager. The MIO I/O manager however still has a number of global variables. It's not obvious how handle these globals however. - - - - - 20 changed files: - rts/Capability.c - rts/IOManager.c - rts/IOManager.h - rts/IOManagerInternals.h - rts/PrimOps.cmm - rts/RaiseAsync.c - rts/RtsStartup.c - rts/Schedule.c - rts/Schedule.h - + rts/posix/MIO.c - + rts/posix/MIO.h - rts/posix/Poll.c - rts/posix/Poll.h - rts/posix/Select.c - rts/posix/Select.h - rts/posix/Signals.c - rts/posix/Signals.h - rts/posix/Timeout.c - rts/posix/Timeout.h - rts/rts.cabal Changes: ===================================== rts/Capability.c ===================================== @@ -286,7 +286,8 @@ initCapability (Capability *cap, uint32_t i) #endif cap->total_allocated = 0; - initCapabilityIOManager(cap); /* initialises cap->iomgr */ + cap->iomgr = allocCapabilityIOManager(cap); + initCapabilityIOManager(cap->iomgr); cap->f.stgEagerBlackholeInfo = (W_)&__stg_EAGER_BLACKHOLE_info; cap->f.stgGCEnter1 = (StgFunPtr)__stg_gc_enter_1; @@ -1344,7 +1345,7 @@ markCapability (evac_fn evac, void *user, Capability *cap, } #endif - markCapabilityIOManager(evac, user, cap); + markCapabilityIOManager(evac, user, cap->iomgr); // Free STM structures for this Capability stmPreGCHook(cap); ===================================== rts/IOManager.c ===================================== @@ -39,7 +39,7 @@ #endif #if defined(IOMGR_ENABLED_MIO_POSIX) -#include "posix/Signals.h" +#include "posix/MIO.h" #include "Prelude.h" #endif @@ -316,22 +316,29 @@ char * showIOManager(void) } } +/* Allocate a CapIOManager for a given Capability. Having this helps us keep + * struct CapIOManager opaque from most of the rest of the RTS. + */ +CapIOManager *allocCapabilityIOManager(Capability *cap) +{ + CapIOManager *iomgr = stgMallocBytes(sizeof(CapIOManager), + "allocCapabilityIOManager"); + iomgr->cap = cap; /* link back */ + return iomgr; +} + -/* Allocate and initialise the per-capability CapIOManager that lives in each - * Capability. Called from initCapability(), which is done in the RTS startup - * in initCapabilities(), and later at runtime via setNumCapabilities(). +/* Initialise the per-capability CapIOManager that lives in each Capability. + * Called from initCapability(), which is done in the RTS startup in + * initCapabilities(), and later at runtime via setNumCapabilities(). * * Note that during RTS startup this is called _before_ the storage manager * is initialised, so this is not allowed to allocate on the GC heap. */ -void initCapabilityIOManager(Capability *cap) +void initCapabilityIOManager(CapIOManager *iomgr) { debugTrace(DEBUG_iomanager, "initialising I/O manager %s for cap %d", - showIOManager(), cap->no); - - CapIOManager *iomgr = - (CapIOManager *) stgMallocBytes(sizeof(CapIOManager), - "initCapabilityIOManager"); + showIOManager(), iomgr->cap->no); switch (iomgr_type) { #if defined(IOMGR_ENABLED_SELECT) @@ -363,16 +370,14 @@ void initCapabilityIOManager(Capability *cap) default: break; } - - cap->iomgr = iomgr; } /* Called late in the RTS initialisation */ -void initIOManager(void) +void startIOManager(void) { - debugTrace(DEBUG_iomanager, "initialising %s I/O manager", showIOManager()); + debugTrace(DEBUG_iomanager, "starting %s I/O manager", showIOManager()); switch (iomgr_type) { @@ -436,7 +441,7 @@ void initIOManager(void) /* Called from forkProcess in the child process on the surviving capability. */ void -initIOManagerAfterFork(Capability **pcap) +restartIOManager(CapIOManager *iomgr, Capability **pcap) { switch (iomgr_type) { @@ -467,7 +472,7 @@ initIOManagerAfterFork(Capability **pcap) /* Called from setNumCapabilities. */ -void notifyIOManagerCapabilitiesChanged(Capability **pcap) +void notifyIOManagerCapabilitiesChanged(CapIOManager *iomgr, Capability **pcap) { switch (iomgr_type) { #if defined(IOMGR_ENABLED_MIO_POSIX) @@ -572,38 +577,29 @@ void wakeupIOManager(void) } } -void markCapabilityIOManager(evac_fn evac, void *user, Capability *cap) +void markCapabilityIOManager(evac_fn evac, void *user, CapIOManager *iomgr) { switch (iomgr_type) { #if defined(IOMGR_ENABLED_SELECT) case IO_MANAGER_SELECT: - { - CapIOManager *iomgr = cap->iomgr; evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_hd); evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_tl); evac(user, (StgClosure **)(void *)&iomgr->sleeping_queue); break; - } #endif #if defined(IOMGR_ENABLED_POLL) case IO_MANAGER_POLL: - { - CapIOManager *iomgr = cap->iomgr; markClosureTable(evac, user, &iomgr->aiop_table); evac(user, (StgClosure **)(void *)&iomgr->timeout_queue); break; - } #endif #if defined(IOMGR_ENABLED_WIN32_LEGACY) case IO_MANAGER_WIN32_LEGACY: - { - CapIOManager *iomgr = cap->iomgr; evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_hd); evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_tl); break; - } #endif default: break; @@ -665,29 +661,23 @@ setIOManagerControlFd(uint32_t cap_no, int fd) { #endif -bool anyPendingTimeoutsOrIO(Capability *cap) +bool anyPendingTimeoutsOrIO(CapIOManager *iomgr) { switch (iomgr_type) { #if defined(IOMGR_ENABLED_SELECT) case IO_MANAGER_SELECT: - { - CapIOManager *iomgr = cap->iomgr; return (iomgr->blocked_queue_hd != END_TSO_QUEUE) || (iomgr->sleeping_queue != END_TSO_QUEUE); - } #endif #if defined(IOMGR_ENABLED_POLL) case IO_MANAGER_POLL: - return anyPendingTimeoutsOrIOPoll(cap->iomgr); + return anyPendingTimeoutsOrIOPoll(iomgr); #endif #if defined(IOMGR_ENABLED_WIN32_LEGACY) case IO_MANAGER_WIN32_LEGACY: - { - CapIOManager *iomgr = cap->iomgr; return (iomgr->blocked_queue_hd != END_TSO_QUEUE); - } #endif /* For the purpose of the scheduler, the threaded I/O managers never have @@ -729,19 +719,19 @@ bool anyPendingTimeoutsOrIO(Capability *cap) } -void pollCompletedTimeoutsOrIO(Capability *cap) +void pollCompletedTimeoutsOrIO(CapIOManager *iomgr) { debugTrace(DEBUG_iomanager, "polling for completed IO or timeouts"); switch (iomgr_type) { #if defined(IOMGR_ENABLED_SELECT) case IO_MANAGER_SELECT: - awaitCompletedTimeoutsOrIOSelect(cap, false); + awaitCompletedTimeoutsOrIOSelect(iomgr, false); break; #endif #if defined(IOMGR_ENABLED_POLL) case IO_MANAGER_POLL: - pollCompletedTimeoutsOrIOPoll(cap); + pollCompletedTimeoutsOrIOPoll(iomgr); break; #endif @@ -753,7 +743,7 @@ void pollCompletedTimeoutsOrIO(Capability *cap) #if defined(IOMGR_ENABLED_WINIO) case IO_MANAGER_WINIO: #endif - awaitCompletedTimeoutsOrIOWin32(cap, false); + awaitCompletedTimeoutsOrIOWin32(iomgr->cap, false); break; #endif default: @@ -762,19 +752,19 @@ void pollCompletedTimeoutsOrIO(Capability *cap) } -void awaitCompletedTimeoutsOrIO(Capability *cap) +void awaitCompletedTimeoutsOrIO(CapIOManager *iomgr) { debugTrace(DEBUG_iomanager, "waiting for completed IO or timeouts"); switch (iomgr_type) { #if defined(IOMGR_ENABLED_SELECT) case IO_MANAGER_SELECT: - awaitCompletedTimeoutsOrIOSelect(cap, true); + awaitCompletedTimeoutsOrIOSelect(iomgr, true); break; #endif #if defined(IOMGR_ENABLED_POLL) case IO_MANAGER_POLL: - awaitCompletedTimeoutsOrIOPoll(cap); + awaitCompletedTimeoutsOrIOPoll(iomgr); break; #endif @@ -786,17 +776,18 @@ void awaitCompletedTimeoutsOrIO(Capability *cap) #if defined(IOMGR_ENABLED_WINIO) case IO_MANAGER_WINIO: #endif - awaitCompletedTimeoutsOrIOWin32(cap, true); + awaitCompletedTimeoutsOrIOWin32(iomgr->cap, true); break; #endif default: barf("pollCompletedTimeoutsOrIO not implemented"); } - ASSERT(!emptyRunQueue(cap) || getSchedState() != SCHED_RUNNING); + ASSERT(!emptyRunQueue(iomgr->cap) || getSchedState() != SCHED_RUNNING); } -bool syncIOWaitReady(Capability *cap, +/* CMM primop. Result is true on success, or false on allocation failure. */ +bool syncIOWaitReady(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw, HsInt fd) @@ -812,14 +803,14 @@ bool syncIOWaitReady(Capability *cap, StgWord why_blocked = rw == IORead ? BlockedOnRead : BlockedOnWrite; tso->block_info.fd = fd; RELEASE_STORE(&tso->why_blocked, why_blocked); - appendToIOBlockedQueue(cap, tso); + appendToIOBlockedQueue(iomgr, tso); return true; } #endif #if defined(IOMGR_ENABLED_POLL) case IO_MANAGER_POLL: ASSERT(tso->why_blocked == NotBlocked); - return syncIOWaitReadyPoll(cap, tso, rw, fd); + return syncIOWaitReadyPoll(iomgr, tso, rw, fd); #endif default: barf("waitRead# / waitWrite# not available for current I/O manager"); @@ -827,25 +818,29 @@ bool syncIOWaitReady(Capability *cap, } -void syncIOCancel(Capability *cap, StgTSO *tso) +void syncIOCancel(CapIOManager *iomgr, StgTSO *tso) { debugTrace(DEBUG_iomanager, "cancelling I/O for thread %ld", (long) tso->id); switch (iomgr_type) { #if defined(IOMGR_ENABLED_SELECT) case IO_MANAGER_SELECT: - removeThreadFromDeQueue(cap, &cap->iomgr->blocked_queue_hd, - &cap->iomgr->blocked_queue_tl, tso); + removeThreadFromDeQueue(iomgr->cap, + &iomgr->blocked_queue_hd, + &iomgr->blocked_queue_tl, + tso); break; #endif #if defined(IOMGR_ENABLED_POLL) case IO_MANAGER_POLL: - syncIOCancelPoll(cap, tso); + syncIOCancelPoll(iomgr, tso); break; #endif #if defined(IOMGR_ENABLED_WIN32_LEGACY) case IO_MANAGER_WIN32_LEGACY: - removeThreadFromDeQueue(cap, &cap->iomgr->blocked_queue_hd, - &cap->iomgr->blocked_queue_tl, tso); + removeThreadFromDeQueue(iomgr->cap, + &iomgr->blocked_queue_hd, + &iomgr->blocked_queue_tl, + tso); abandonWorkRequest(tso->block_info.async_result->reqID); break; #endif @@ -856,11 +851,12 @@ void syncIOCancel(Capability *cap, StgTSO *tso) #if defined(IOMGR_ENABLED_SELECT) -static void insertIntoSleepingQueue(Capability *cap, StgTSO *tso, LowResTime target); +static void insertIntoSleepingQueue(CapIOManager *iomgr, StgTSO *tso, LowResTime target); #endif -bool syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay) +/* CMM primop. Result is true on success, or false on allocation failure. */ +bool syncDelay(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay) { debugTrace(DEBUG_iomanager, "thread %ld waiting for %lld us", tso->id, us_delay); ASSERT(tso->why_blocked == NotBlocked); @@ -871,13 +867,13 @@ bool syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay) LowResTime target = getDelayTarget(us_delay); tso->block_info.target = target; RELEASE_STORE(&tso->why_blocked, BlockedOnDelay); - insertIntoSleepingQueue(cap, tso, target); + insertIntoSleepingQueue(iomgr, tso, target); return true; } #endif #if defined(IOMGR_ENABLED_POLL) case IO_MANAGER_POLL: - return syncDelayTimeout(cap, tso, us_delay); + return syncDelayTimeout(iomgr, tso, us_delay); #endif #if defined(IOMGR_ENABLED_WIN32_LEGACY) case IO_MANAGER_WIN32_LEGACY: @@ -897,7 +893,7 @@ bool syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay) * delayed thread on the blocked_queue. */ RELEASE_STORE(&tso->why_blocked, BlockedOnDoProc); - appendToIOBlockedQueue(cap, tso); + appendToIOBlockedQueue(iomgr, tso); return true; } #endif @@ -907,18 +903,18 @@ bool syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay) } -void syncDelayCancel(Capability *cap, StgTSO *tso) +void syncDelayCancel(CapIOManager *iomgr, StgTSO *tso) { debugTrace(DEBUG_iomanager, "cancelling delay for thread %ld", (long) tso->id); switch (iomgr_type) { #if defined(IOMGR_ENABLED_SELECT) case IO_MANAGER_SELECT: - removeThreadFromQueue(cap, &cap->iomgr->sleeping_queue, tso); + removeThreadFromQueue(iomgr->cap, &iomgr->sleeping_queue, tso); break; #endif #if defined(IOMGR_ENABLED_POLL) case IO_MANAGER_POLL: - syncDelayCancelTimeout(cap, tso); + syncDelayCancelTimeout(iomgr, tso); break; #endif /* Note: no case for IO_MANAGER_WIN32_LEGACY despite it having a case @@ -935,14 +931,13 @@ void syncDelayCancel(Capability *cap, StgTSO *tso) #if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_WIN32_LEGACY) -void appendToIOBlockedQueue(Capability *cap, StgTSO *tso) +void appendToIOBlockedQueue(CapIOManager *iomgr, StgTSO *tso) { - CapIOManager *iomgr = cap->iomgr; ASSERT(tso->_link == END_TSO_QUEUE); if (iomgr->blocked_queue_hd == END_TSO_QUEUE) { iomgr->blocked_queue_hd = tso; } else { - setTSOLink(cap, iomgr->blocked_queue_tl, tso); + setTSOLink(iomgr->cap, iomgr->blocked_queue_tl, tso); } iomgr->blocked_queue_tl = tso; } @@ -957,9 +952,8 @@ void appendToIOBlockedQueue(Capability *cap, StgTSO *tso) * used. This is a wart that should be excised. */ // TODO: move to Select.c and rename -static void insertIntoSleepingQueue(Capability *cap, StgTSO *tso, LowResTime target) +static void insertIntoSleepingQueue(CapIOManager *iomgr, StgTSO *tso, LowResTime target) { - CapIOManager *iomgr = cap->iomgr; StgTSO *prev = NULL; StgTSO *t = iomgr->sleeping_queue; while (t != END_TSO_QUEUE && t->block_info.target < target) { @@ -971,7 +965,7 @@ static void insertIntoSleepingQueue(Capability *cap, StgTSO *tso, LowResTime tar if (prev == NULL) { iomgr->sleeping_queue = tso; } else { - setTSOLink(cap, prev, tso); + setTSOLink(iomgr->cap, prev, tso); } } #endif ===================================== rts/IOManager.h ===================================== @@ -19,6 +19,7 @@ #pragma once +#include "Capability.h" #include "sm/GC.h" // for evac_fn #include "BeginPrivate.h" @@ -227,26 +228,35 @@ enum IOOpOutcome { void selectIOManager(void); -/* Allocate and initialise the per-capability CapIOManager that lives in each - * Capability. Called from initCapability(), which is done in the RTS startup - * in initCapabilities(), and later at runtime via setNumCapabilities(). +/* Allocate a CapIOManager for a given Capability. Having this helps us keep + * struct CapIOManager opaque from most of the rest of the RTS. */ -void initCapabilityIOManager(Capability *cap); +CapIOManager *allocCapabilityIOManager(Capability *cap); + +/* Initialise the per-capability CapIOManager that lives in each Capability. + * Called from initCapability(), which is done in the RTS startup in + * initCapabilities(), and later at runtime via setNumCapabilities(). + * + * This is separate from allocCapabilityIOManager so that we can re-initialise + * I/O managers after forkProcess. + */ +void initCapabilityIOManager(CapIOManager *iomgr); /* Init hook: called from hs_init_ghc, very late in the startup after almost * everything else is done. */ -void initIOManager(void); +void startIOManager(void); /* Init hook: called from forkProcess in the child process on the surviving * capability. * - * Note that this is synchronous and can run Haskell code, so can change the - * given cap. + * This is synchronous and can run Haskell code, so can change the given cap. + * TODO: it would make for a cleaner API here if this were made asynchronous. */ -void initIOManagerAfterFork(/* inout */ Capability **pcap); +void restartIOManager(CapIOManager *iomgr, + /* inout */ Capability **pcap); /* TODO: rationalise initIOManager and initIOManagerAfterFork into a single per-capability init function. @@ -254,8 +264,12 @@ void initIOManagerAfterFork(/* inout */ Capability **pcap); /* Called from setNumCapabilities. + * + * This is synchronous and can run Haskell code, so can change the given cap. + * TODO: it would make for a cleaner API here if this were made asynchronous. */ -void notifyIOManagerCapabilitiesChanged(Capability **pcap); +void notifyIOManagerCapabilitiesChanged(CapIOManager *iomgr, + /* inout */ Capability **pcap); /* Shutdown hooks: called from hs_exit_ before and after the scheduler exits. @@ -288,7 +302,7 @@ void wakeupIOManager(void); /* GC hook: mark any per-capability GC roots the I/O manager uses. */ -void markCapabilityIOManager(evac_fn evac, void *user, Capability *cap); +void markCapabilityIOManager(evac_fn evac, void *user, CapIOManager *iomgr); /* GC hook: scavenge I/O related tso->block_info. Used by scavengeTSO. @@ -305,21 +319,20 @@ typedef enum { IORead, IOWrite } IOReadOrWrite; * necessarily operate on threads. The thread is suspended until the operation * completes. * - * These are called from CMM primops. The ones returing int can perform heap - * allocation, which might fail. They return 0 on success, or n > 0 on heap - * allocation failure, needing n words. The CMM primops should invoke the - * GC to free up at least n words and then retry the operation. + * Some of these are called from CMM primops. The primops returing bool can + * perform heap allocation, which might fail. They return true on success, or + * false on heap allocation failure. */ -/* Result is true on success, or false on allocation failure. */ -bool syncIOWaitReady(Capability *cap, StgTSO *tso, IOReadOrWrite rw, HsInt fd); +/* Called from CMM primop */ +bool syncIOWaitReady(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw, HsInt fd); -void syncIOCancel(Capability *cap, StgTSO *tso); +void syncIOCancel(CapIOManager *iomgr, StgTSO *tso); -/* Result is true on success, or false on allocation failure. */ -bool syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay); +/* Called from CMM primop */ +bool syncDelay(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay); -void syncDelayCancel(Capability *cap, StgTSO *tso); +void syncDelayCancel(CapIOManager *iomgr, StgTSO *tso); #if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_WIN32_LEGACY) /* Add a thread to the end of the queue of threads blocked on I/O. @@ -327,7 +340,7 @@ void syncDelayCancel(Capability *cap, StgTSO *tso); * This is used by the select() and the Windows MIO non-threaded I/O manager * implementation. Called from CMM code. */ -void appendToIOBlockedQueue(Capability *cap, StgTSO *tso); +void appendToIOBlockedQueue(CapIOManager *iomgr, StgTSO *tso); #endif /* Check to see if there are any pending timeouts or I/O operations @@ -336,7 +349,7 @@ void appendToIOBlockedQueue(Capability *cap, StgTSO *tso); * This is used by the scheduler as part of deadlock-detection, and the * "context switch as often as possible" test. */ -bool anyPendingTimeoutsOrIO(Capability *cap); +bool anyPendingTimeoutsOrIO(CapIOManager *iomgr); /* If there are any completed I/O operations or expired timers, process the * completions as appropriate (which will typically unblock some waiting @@ -344,7 +357,7 @@ bool anyPendingTimeoutsOrIO(Capability *cap); * * Called from schedule() both *before* and *after* scheduleDetectDeadlock(). */ -void pollCompletedTimeoutsOrIO(Capability *cap); +void pollCompletedTimeoutsOrIO(CapIOManager *iomgr); /* If there are any completed I/O operations or expired timers, process the * completions as appropriate. If there are none, wait until I/O or a timer @@ -360,6 +373,6 @@ void pollCompletedTimeoutsOrIO(Capability *cap); * * Called from schedule() both *before* and *after* scheduleDetectDeadlock(). */ -void awaitCompletedTimeoutsOrIO(Capability *cap); +void awaitCompletedTimeoutsOrIO(CapIOManager *iomgr); #include "EndPrivate.h" ===================================== rts/IOManagerInternals.h ===================================== @@ -24,7 +24,8 @@ /* The per-capability data structures belonging to the I/O manager. * - * It can be accessed as cap->iomgr. + * It can be accessed as cap->iomgr. Or given just the iomgr, you can access + * the owning cap as iomgr->cap. * * The content of the structure is defined conditionally so it is different for * each I/O manager implementation. @@ -33,6 +34,9 @@ */ struct _CapIOManager { + /* Back reference to the containing capability */ + Capability *cap; + #if defined(IOMGR_ENABLED_SELECT) /* Thread queue for threads blocked on I/O completion. */ StgTSO *blocked_queue_hd; ===================================== rts/PrimOps.cmm ===================================== @@ -2279,7 +2279,8 @@ stg_waitReadzh ( W_ fd ) { CBool ok; /* Ok, or heap alloc failure. */ - (ok) = ccall syncIOWaitReady(MyCapability() "ptr", CurrentTSO "ptr", + (ok) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr", + CurrentTSO "ptr", /* IORead */ 0::I32, fd); if (ok != 0::CBool) (likely: True) { jump stg_block_noregs(); @@ -2292,7 +2293,8 @@ stg_waitWritezh ( W_ fd ) { CBool ok; /* Ok, or heap alloc failure. */ - (ok) = ccall syncIOWaitReady(MyCapability() "ptr", CurrentTSO "ptr", + (ok) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr", + CurrentTSO "ptr", /* IOWrite */ 1::I32, fd); if (ok != 0::CBool) (likely: True) { jump stg_block_noregs(); @@ -2305,7 +2307,8 @@ stg_delayzh ( W_ us_delay ) { CBool ok; /* Ok, or heap alloc failure. */ - (ok) = ccall syncDelay(MyCapability() "ptr", CurrentTSO "ptr", us_delay); + (ok) = ccall syncDelay(Capability_iomgr(MyCapability()) "ptr", + CurrentTSO "ptr", us_delay); if (ok != 0::CBool) (likely: True) { /* Annoyingly, we cannot be consistent with how we wait and resume the ===================================== rts/RaiseAsync.c ===================================== @@ -708,12 +708,12 @@ removeFromQueues(Capability *cap, StgTSO *tso) case BlockedOnWrite: case BlockedOnDoProc: // These blocking reasons are only used by some I/O managers - syncIOCancel(cap, tso); + syncIOCancel(cap->iomgr, tso); goto done; case BlockedOnDelay: // This blocking reasons is only used by some I/O managers - syncDelayCancel(cap, tso); + syncDelayCancel(cap->iomgr, tso); goto done; default: ===================================== rts/RtsStartup.c ===================================== @@ -427,7 +427,7 @@ hs_init_ghc(int *argc, char **argv[], RtsConfig rts_config) } #endif - initIOManager(); + startIOManager(); x86_init_fpu(); ===================================== rts/Schedule.c ===================================== @@ -174,6 +174,11 @@ static void deleteAllThreads (void); static void deleteThread_(StgTSO *tso); #endif +#if defined(FORKPROCESS_PRIMOP_SUPPORTED) +static void truncateRunQueue(Capability *cap); +#endif +static StgTSO *popRunQueue (Capability *cap); + /* --------------------------------------------------------------------------- Main scheduling loop. @@ -409,7 +414,7 @@ schedule (Capability *initialCapability, Task *task) */ if (RtsFlags.ConcFlags.ctxtSwitchTicks == 0 && (!emptyRunQueue(cap) || - anyPendingTimeoutsOrIO(cap))) { + anyPendingTimeoutsOrIO(cap->iomgr))) { RELAXED_STORE(&cap->context_switch, 1); } @@ -591,38 +596,6 @@ run_thread: } /* end of while() */ } -/* ----------------------------------------------------------------------------- - * Run queue operations - * -------------------------------------------------------------------------- */ - -static void -removeFromRunQueue (Capability *cap, StgTSO *tso) -{ - if (tso->block_info.prev == END_TSO_QUEUE) { - ASSERT(cap->run_queue_hd == tso); - cap->run_queue_hd = tso->_link; - } else { - setTSOLink(cap, tso->block_info.prev, tso->_link); - } - if (tso->_link == END_TSO_QUEUE) { - ASSERT(cap->run_queue_tl == tso); - cap->run_queue_tl = tso->block_info.prev; - } else { - setTSOPrev(cap, tso->_link, tso->block_info.prev); - } - tso->_link = tso->block_info.prev = END_TSO_QUEUE; - cap->n_run_queue--; - - IF_DEBUG(sanity, checkRunQueue(cap)); -} - -void -promoteInRunQueue (Capability *cap, StgTSO *tso) -{ - removeFromRunQueue(cap, tso); - pushOnRunQueue(cap, tso); -} - /* ----------------------------------------------------------------------------- * scheduleFindWork() * @@ -923,14 +896,14 @@ scheduleCheckBlockedThreads(Capability *cap USED_IF_NOT_THREADS) * awaitCompletedTimeoutsOrIO below for the case of !defined(THREADED_RTS) * && defined(mingw32_HOST_OS). */ - if (anyPendingTimeoutsOrIO(cap)) + if (anyPendingTimeoutsOrIO(cap->iomgr)) { if (emptyRunQueue(cap)) { // block and wait - awaitCompletedTimeoutsOrIO(cap); + awaitCompletedTimeoutsOrIO(cap->iomgr); } else { // poll but do not wait - pollCompletedTimeoutsOrIO(cap); + pollCompletedTimeoutsOrIO(cap->iomgr); } } #endif @@ -950,7 +923,7 @@ scheduleDetectDeadlock (Capability **pcap, Task *task) * other tasks are waiting for work, we must have a deadlock of * some description. */ - if ( emptyRunQueue(cap) && !anyPendingTimeoutsOrIO(cap) ) + if ( emptyRunQueue(cap) && !anyPendingTimeoutsOrIO(cap->iomgr) ) { #if defined(THREADED_RTS) /* @@ -2191,7 +2164,6 @@ forkProcess(HsStablePtr *entry // bound threads for which the corresponding Task does not // exist. truncateRunQueue(cap); - cap->n_run_queue = 0; // Any suspended C-calling Tasks are no more, their OS threads // don't exist now: @@ -2208,7 +2180,7 @@ forkProcess(HsStablePtr *entry cap->n_returning_tasks = 0; #endif - // Release all caps except 0, we'll use that for starting + // Release all caps except 0, we'll use that for restarting // the IO manager and running the client action below. if (cap->no != 0) { task->cap = cap; @@ -2232,7 +2204,7 @@ forkProcess(HsStablePtr *entry // like startup event, capabilities, process info etc traceTaskCreate(task, cap); - initIOManagerAfterFork(&cap); + restartIOManager(cap->iomgr, &cap); // start timer after the IOManager is initialized // (the idle GC may wake up the IOManager) @@ -2392,7 +2364,7 @@ setNumCapabilities (uint32_t new_n_capabilities USED_IF_THREADS) } // Notify IO manager that the number of capabilities has changed. - notifyIOManagerCapabilitiesChanged(&cap); + notifyIOManagerCapabilitiesChanged(cap->iomgr, &cap); startTimer(); @@ -2997,7 +2969,7 @@ pushOnRunQueue (Capability *cap, StgTSO *tso) cap->n_run_queue++; } -StgTSO *popRunQueue (Capability *cap) +static StgTSO *popRunQueue (Capability *cap) { ASSERT(cap->n_run_queue > 0); StgTSO *t = cap->run_queue_hd; @@ -3017,6 +2989,45 @@ StgTSO *popRunQueue (Capability *cap) return t; } +#if defined(FORKPROCESS_PRIMOP_SUPPORTED) +static void truncateRunQueue(Capability *cap) +{ + // Can only be called by the task owning the capability. + TSAN_ANNOTATE_BENIGN_RACE(&cap->run_queue_hd, "truncateRunQueue"); + TSAN_ANNOTATE_BENIGN_RACE(&cap->run_queue_tl, "truncateRunQueue"); + TSAN_ANNOTATE_BENIGN_RACE(&cap->n_run_queue, "truncateRunQueue"); + cap->run_queue_hd = END_TSO_QUEUE; + cap->run_queue_tl = END_TSO_QUEUE; + cap->n_run_queue = 0; +} +#endif + +static void removeFromRunQueue (Capability *cap, StgTSO *tso) +{ + if (tso->block_info.prev == END_TSO_QUEUE) { + ASSERT(cap->run_queue_hd == tso); + cap->run_queue_hd = tso->_link; + } else { + setTSOLink(cap, tso->block_info.prev, tso->_link); + } + if (tso->_link == END_TSO_QUEUE) { + ASSERT(cap->run_queue_tl == tso); + cap->run_queue_tl = tso->block_info.prev; + } else { + setTSOPrev(cap, tso->_link, tso->block_info.prev); + } + tso->_link = tso->block_info.prev = END_TSO_QUEUE; + cap->n_run_queue--; + + IF_DEBUG(sanity, checkRunQueue(cap)); +} + +void promoteInRunQueue (Capability *cap, StgTSO *tso) +{ + removeFromRunQueue(cap, tso); + pushOnRunQueue(cap, tso); +} + /* ----------------------------------------------------------------------------- raiseExceptionHelper ===================================== rts/Schedule.h ===================================== @@ -164,10 +164,6 @@ void appendToRunQueue (Capability *cap, StgTSO *tso); */ void pushOnRunQueue (Capability *cap, StgTSO *tso); -/* Pop the first thread off the runnable queue. - */ -StgTSO *popRunQueue (Capability *cap); - INLINE_HEADER StgTSO * peekRunQueue (Capability *cap) { @@ -184,18 +180,6 @@ emptyRunQueue(Capability *cap) return cap->n_run_queue == 0; } -INLINE_HEADER void -truncateRunQueue(Capability *cap) -{ - // Can only be called by the task owning the capability. - TSAN_ANNOTATE_BENIGN_RACE(&cap->run_queue_hd, "truncateRunQueue"); - TSAN_ANNOTATE_BENIGN_RACE(&cap->run_queue_tl, "truncateRunQueue"); - TSAN_ANNOTATE_BENIGN_RACE(&cap->n_run_queue, "truncateRunQueue"); - cap->run_queue_hd = END_TSO_QUEUE; - cap->run_queue_tl = END_TSO_QUEUE; - cap->n_run_queue = 0; -} - #endif /* !IN_STG_CODE */ #include "EndPrivate.h" ===================================== rts/posix/MIO.c ===================================== @@ -0,0 +1,163 @@ +/* ----------------------------------------------------------------------------- + * + * (c) The GHC Team, 1998-2005 + * + * Signal processing / handling. + * + * ---------------------------------------------------------------------------*/ + +#include "rts/PosixSource.h" +#include "Rts.h" + +#include "Schedule.h" +#include "RtsUtils.h" +#include "Prelude.h" +#include "ThreadLabels.h" + +#include "MIO.h" +#include "IOManager.h" +#include "IOManagerInternals.h" + +#if defined(HAVE_ERRNO_H) +# include <errno.h> +#endif + +#include <stdlib.h> +#include <unistd.h> + +// Here's the pipe into which we will send our signals +static int io_manager_wakeup_fd = -1; +static int timer_manager_control_wr_fd = -1; +// TODO: Eliminate these globals. Put then into the CapIOManager, but the +// problem is these are shared across all caps, not per cap. + +#define IO_MANAGER_WAKEUP 0xff +#define IO_MANAGER_DIE 0xfe +#define IO_MANAGER_SYNC 0xfd + +void setTimerManagerControlFd(int fd) { + RELAXED_STORE(&timer_manager_control_wr_fd, fd); +} + +void +setIOManagerWakeupFd (int fd) +{ + // only called when THREADED_RTS, but unconditionally + // compiled here because GHC.Event.Control depends on it. + SEQ_CST_STORE(&io_manager_wakeup_fd, fd); +} + +#if defined(THREADED_RTS) +void timerManagerNotifySignal(int sig, siginfo_t *info) +{ + StgWord8 buf[sizeof(siginfo_t) + 1]; + int r; + + buf[0] = sig; + if (info == NULL) { + // info may be NULL on Solaris (see #3790) + memset(buf+1, 0, sizeof(siginfo_t)); + } else { + memcpy(buf+1, info, sizeof(siginfo_t)); + } + + int timer_control_fd = RELAXED_LOAD(&timer_manager_control_wr_fd); + if (0 <= timer_control_fd) + { + r = write(timer_control_fd, buf, sizeof(siginfo_t)+1); + if (r == -1 && errno == EAGAIN) { + errorBelch("lost signal due to full pipe: %d\n", sig); + } + } + + // If the IO manager hasn't told us what the FD of the write end + // of its pipe is, there's not much we can do here, so just ignore + // the signal.. +} +#endif + + +/* ----------------------------------------------------------------------------- + * Wake up at least one IO or timer manager HS thread. + * -------------------------------------------------------------------------- */ +void +ioManagerWakeup (void) +{ + int r; + const int wakeup_fd = SEQ_CST_LOAD(&io_manager_wakeup_fd); + // Wake up the IO Manager thread by sending a byte down its pipe + if (wakeup_fd >= 0) { +#if defined(HAVE_EVENTFD) + StgWord64 n = (StgWord64)IO_MANAGER_WAKEUP; + r = write(wakeup_fd, (char *) &n, 8); +#else + StgWord8 byte = (StgWord8)IO_MANAGER_WAKEUP; + r = write(wakeup_fd, &byte, 1); +#endif + /* N.B. If the TimerManager is shutting down as we run this + * then there is a possibility that our first read of + * io_manager_wakeup_fd is non-negative, but before we get to the + * write the file is closed. If this occurs, io_manager_wakeup_fd + * will be written into with -1 (GHC.Event.Control does this prior + * to closing), so checking this allows us to distinguish this case. + * To ensure we observe the correct ordering, we declare the + * io_manager_wakeup_fd as volatile. + * Since this is not an error condition, we do not print the error + * message in this case. + */ + if (r == -1 && SEQ_CST_LOAD(&io_manager_wakeup_fd) >= 0) { + sysErrorBelch("ioManagerWakeup: write"); + } + } +} + +#if defined(THREADED_RTS) +void +ioManagerDie (void) +{ + StgWord8 byte = (StgWord8)IO_MANAGER_DIE; + uint32_t i; + int r; + + { + // Shut down timer manager + const int fd = RELAXED_LOAD(&timer_manager_control_wr_fd); + if (0 <= fd) { + r = write(fd, &byte, 1); + if (r == -1) { sysErrorBelch("ioManagerDie: write"); } + RELAXED_STORE(&timer_manager_control_wr_fd, -1); + } + } + + { + // Shut down IO managers + for (i=0; i < getNumCapabilities(); i++) { + const int fd = RELAXED_LOAD(&getCapability(i)->iomgr->control_fd); + if (0 <= fd) { + r = write(fd, &byte, 1); + if (r == -1) { sysErrorBelch("ioManagerDie: write"); } + RELAXED_STORE(&getCapability(i)->iomgr->control_fd, -1); + } + } + } +} + +void +ioManagerStartCap (Capability **cap) +{ + rts_evalIO(cap,ensureIOManagerIsRunning_closure,NULL); +} + +void +ioManagerStart (void) +{ + // Make sure the IO manager thread is running + Capability *cap; + if (SEQ_CST_LOAD(&timer_manager_control_wr_fd) < 0 || SEQ_CST_LOAD(&io_manager_wakeup_fd) < 0) { + cap = rts_lock(); + ioManagerStartCap(&cap); + rts_unlock(cap); + } +} +#endif + ===================================== rts/posix/MIO.h ===================================== @@ -0,0 +1,30 @@ +/* ----------------------------------------------------------------------------- + * + * (c) The GHC Team, 1998-2005 + * + * Signal processing / handling. + * + * ---------------------------------------------------------------------------*/ + +#pragma once + +#include "IOManager.h" + +#if defined(HAVE_SIGNAL_H) +# include <signal.h> +#endif + +#include "BeginPrivate.h" + +/* Communicating with the IO manager thread (see GHC.Conc). + */ +void ioManagerWakeup (void); +#if defined(THREADED_RTS) +void ioManagerDie (void); +void ioManagerStart (void); +void ioManagerStartCap (/* inout */ Capability **cap); + +void timerManagerNotifySignal(int sig, siginfo_t *info); +#endif + +#include "EndPrivate.h" ===================================== rts/posix/Poll.c ===================================== @@ -120,9 +120,9 @@ also allows the signal mask to be adjusted, but we do not make use of this. ******************************************************************************/ /* Forward declarations */ -static bool enlargeTables(Capability *cap, CapIOManager *iomgr); -static void notifyIOCompletion(Capability *cap, StgAsyncIOOp *aiop); -static void ioCancel(Capability *cap, StgAsyncIOOp *aiop); +static bool enlargeTables(CapIOManager *iomgr); +static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop); +static void ioCancel(CapIOManager *iomgr, StgAsyncIOOp *aiop); static void reportPollError(int res, nfds_t nfds) STG_NORETURN; @@ -136,32 +136,31 @@ void initCapabilityIOManagerPoll(CapIOManager *iomgr) /* Used to implement syncIOWaitReady. * Result is true on success, or false on allocation failure. */ -bool syncIOWaitReadyPoll(Capability *cap, StgTSO *tso, +bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw, HsInt fd) { StgAsyncIOOp *aiop; - aiop = (StgAsyncIOOp *)allocateMightFail(cap, sizeofW(StgAsyncIOOp)); + aiop = (StgAsyncIOOp *)allocateMightFail(iomgr->cap, sizeofW(StgAsyncIOOp)); if (RTS_UNLIKELY(aiop == NULL)) return false; - SET_HDR(aiop, &stg_ASYNCIOOP_info, cap->r.rCCCS); + SET_HDR(aiop, &stg_ASYNCIOOP_info, iomgr->cap->r.rCCCS); aiop->notify.tso = tso; aiop->notify_type = NotifyTSO; aiop->live = &stg_ASYNCIO_LIVE0_closure; tso->why_blocked = rw == IORead ? BlockedOnRead : BlockedOnWrite; tso->block_info.aiop = aiop; - return asyncIOWaitReadyPoll(cap, aiop, rw, fd); + return asyncIOWaitReadyPoll(iomgr, aiop, rw, fd); } /* Result is true on success, or false on allocation failure. */ -bool asyncIOWaitReadyPoll(Capability *cap, StgAsyncIOOp *aiop, +bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop, IOReadOrWrite rw, int fd) { - CapIOManager *iomgr = cap->iomgr; if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) { - bool ok = enlargeTables(cap, iomgr); + bool ok = enlargeTables(iomgr); if (RTS_UNLIKELY(!ok)) return false; } - int ix = insertClosureTable(cap, &iomgr->aiop_table, aiop); + int ix = insertClosureTable(iomgr->cap, &iomgr->aiop_table, aiop); /* We use the aiop_table and aiop_poll_table densely. */ ASSERT(ix == sizeClosureTable(&iomgr->aiop_table) - 1); @@ -169,7 +168,7 @@ bool asyncIOWaitReadyPoll(Capability *cap, StgAsyncIOOp *aiop, /* The syncIO wrapper or CMM primop filled in the notify and live fields, * we fill the rest. */ - aiop->capno = cap->no; + aiop->capno = iomgr->cap->no; aiop->index = ix; aiop->outcome = IOOpOutcomeInFlight; @@ -183,12 +182,12 @@ bool asyncIOWaitReadyPoll(Capability *cap, StgAsyncIOOp *aiop, } -void syncIOCancelPoll(Capability *cap, StgTSO *tso) +void syncIOCancelPoll(CapIOManager *iomgr, StgTSO *tso) { StgAsyncIOOp *aiop = tso->block_info.aiop; ASSERT(aiop->notify_type == NotifyTSO); - ASSERT(indexClosureTable(&cap->iomgr->aiop_table, aiop->index) == aiop); - ioCancel(cap, aiop); + ASSERT(indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop); + ioCancel(iomgr, aiop); /* We cannot use the normal notifyIOCompletion here. We are in the context * of throwTo, interrupting a thread blocked on IO via an async exception. * We don't put the TSO back on the run queue or change the why_blocked @@ -198,7 +197,7 @@ void syncIOCancelPoll(Capability *cap, StgTSO *tso) } -void asyncIOCancelPoll(Capability *cap, StgAsyncIOOp *aiop) +void asyncIOCancelPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop) { /* We can reliably determine if the aiop is still in progress by checking * if the aiop_table still points to this aiop object. This is reliable @@ -206,20 +205,18 @@ void asyncIOCancelPoll(Capability *cap, StgAsyncIOOp *aiop) * is no longer retained by the application. */ ASSERT(aiop->notify_type != NotifyTSO); - if (indexClosureTable(&cap->iomgr->aiop_table, aiop->index) == aiop) { - ioCancel(cap, aiop); - notifyIOCompletion(cap, aiop); + if (indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop) { + ioCancel(iomgr, aiop); + notifyIOCompletion(iomgr, aiop); } } -static void ioCancel(Capability *cap, StgAsyncIOOp *aiop) +static void ioCancel(CapIOManager *iomgr, StgAsyncIOOp *aiop) { - CapIOManager *iomgr = cap->iomgr; - int ix = aiop->index; int ix_from; int ix_to; - removeCompactClosureTable(cap, &iomgr->aiop_table, ix, + removeCompactClosureTable(iomgr->cap, &iomgr->aiop_table, ix, &ix_from, &ix_to); if (ix_to != ix_from) { StgAsyncIOOp *aiop_to = indexClosureTable(&iomgr->aiop_table, ix_to); @@ -237,7 +234,7 @@ bool anyPendingTimeoutsOrIOPoll(CapIOManager *iomgr) } -static void notifyIOCompletion(Capability *cap, StgAsyncIOOp *aiop) +static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop) { ASSERT(aiop->outcome != IOOpOutcomeInFlight); switch (aiop->notify_type) { @@ -251,7 +248,8 @@ static void notifyIOCompletion(Capability *cap, StgAsyncIOOp *aiop) debugTrace(DEBUG_iomanager, "Raising exception in thread %" FMT_StgThreadID " blocked on an invalid fd", tso->id); - raiseAsync(cap, tso, (StgClosure *)blockedOnBadFD_closure, + raiseAsync(iomgr->cap, tso, + (StgClosure *)blockedOnBadFD_closure, false, NULL); break; } else { @@ -262,7 +260,7 @@ static void notifyIOCompletion(Capability *cap, StgAsyncIOOp *aiop) StgTSO *tso = aiop->notify.tso; tso->why_blocked = NotBlocked; tso->_link = END_TSO_QUEUE; - pushOnRunQueue(cap, tso); + pushOnRunQueue(iomgr->cap, tso); } break; } @@ -277,8 +275,7 @@ static void notifyIOCompletion(Capability *cap, StgAsyncIOOp *aiop) } -static void processIOCompletions(Capability *cap, CapIOManager *iomgr, - int ncompletions) +static void processIOCompletions(CapIOManager *iomgr, int ncompletions) { /* The scheme we use with poll is that we have a dense poll table, and a * corresponding table that maps to the closure table index. The poll @@ -320,7 +317,7 @@ static void processIOCompletions(Capability *cap, CapIOManager *iomgr, * apply the same compacting to the aiop_poll_table. */ int ix_from; int ix_to; - removeCompactClosureTable(cap, &iomgr->aiop_table, i, + removeCompactClosureTable(iomgr->cap, &iomgr->aiop_table, i, &ix_from, &ix_to); if (ix_to != ix_from) { StgAsyncIOOp *aiop_to; @@ -329,7 +326,7 @@ static void processIOCompletions(Capability *cap, CapIOManager *iomgr, aiop_poll_table[ix_to] = aiop_poll_table[ix_from]; } - notifyIOCompletion(cap, aiop); + notifyIOCompletion(iomgr, aiop); n--; } else { /* You'd expect incrementing the poll table index to be @@ -343,13 +340,11 @@ static void processIOCompletions(Capability *cap, CapIOManager *iomgr, } -void pollCompletedTimeoutsOrIOPoll(Capability *cap) +void pollCompletedTimeoutsOrIOPoll(CapIOManager *iomgr) { - CapIOManager *iomgr = cap->iomgr; - if (!isEmptyTimeoutQueue(iomgr->timeout_queue)) { Time now = getProcessElapsedTime(); - processTimeoutCompletions(cap, now); + processTimeoutCompletions(iomgr, now); } if (!isEmptyClosureTable(&iomgr->aiop_table)) { @@ -379,7 +374,7 @@ void pollCompletedTimeoutsOrIOPoll(Capability *cap) } else if (res > 0) { int ncompletions = res; ASSERT(ncompletions <= (int)nfds); - processIOCompletions(cap, iomgr, ncompletions); + processIOCompletions(iomgr, ncompletions); } else if (errno == EINTR) { /* We got interrupted by a signal. This is unlikely since we asked @@ -393,10 +388,8 @@ void pollCompletedTimeoutsOrIOPoll(Capability *cap) } -void awaitCompletedTimeoutsOrIOPoll(Capability *cap) +void awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr) { - CapIOManager *iomgr = cap->iomgr; - /* Loop until we've woken up some threads. This loop is needed because the * poll() timing isn't accurate, we sometimes sleep for a while but not * long enough to wake up a thread in a threadDelay. Or we may need to @@ -409,14 +402,14 @@ void awaitCompletedTimeoutsOrIOPoll(Capability *cap) !isEmptyClosureTable(&iomgr->aiop_table)); Time now = getProcessElapsedTime(); - processTimeoutCompletions(cap, now); + processTimeoutCompletions(iomgr, now); /* If we didn't wake any threads due to expiring timeouts, then we need * to wait on I/O. Or to put it another way, even if we did wake some * threads, we'll still poll (but not wait) for I/O. This is to ensure * we avoid starving threads blocked on I/O. */ - bool wait = emptyRunQueue(cap); + bool wait = emptyRunQueue(iomgr->cap); /* Decide if we are going to wait if no I/O is ready, either: * poll only, wait indefinitely, or wait until a timeout. @@ -461,7 +454,7 @@ void awaitCompletedTimeoutsOrIOPoll(Capability *cap) } else if (res > 0) { int ncompletions = res; ASSERT(ncompletions <= (int)nfds); - processIOCompletions(cap, iomgr, ncompletions); + processIOCompletions(iomgr, ncompletions); } else if (errno == EINTR) { /* We got interrupted by a signal. In the non-threaded RTS, if the @@ -471,7 +464,7 @@ void awaitCompletedTimeoutsOrIOPoll(Capability *cap) * signal is serviced. */ #if defined(RTS_USER_SIGNALS) - if (startPendingSignalHandlers(cap)) break; + if (startPendingSignalHandlers(iomgr->cap)) break; #endif /* We can also be interrupted by the shutdown signal handler, which @@ -485,7 +478,7 @@ void awaitCompletedTimeoutsOrIOPoll(Capability *cap) reportPollError(res, nfds); } - } while (emptyRunQueue(cap) + } while (emptyRunQueue(iomgr->cap) && (getSchedState() == SCHED_RUNNING)); } @@ -507,12 +500,12 @@ static void reportPollError(int res, nfds_t nfds) /* Helper function to double the size of the aiop_table and aiop_poll_table. */ -static bool enlargeTables(Capability *cap, CapIOManager *iomgr) +static bool enlargeTables(CapIOManager *iomgr) { int oldcapacity = capacityClosureTable(&iomgr->aiop_table); int newcapacity = (oldcapacity == 0) ? 1 : (oldcapacity * 2); - bool ok = enlargeClosureTable(cap, &iomgr->aiop_table, newcapacity); + bool ok = enlargeClosureTable(iomgr->cap, &iomgr->aiop_table, newcapacity); if (RTS_UNLIKELY(!ok)) return false; /* Update the auxiliary aiop_poll_table to match */ ===================================== rts/posix/Poll.h ===================================== @@ -19,19 +19,19 @@ void initCapabilityIOManagerPoll(CapIOManager *iomgr); /* Synchronous I/O and timer operations */ -bool syncIOWaitReadyPoll(Capability *cap, StgTSO *tso, +bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw, HsInt fd); -void syncIOCancelPoll(Capability *cap, StgTSO *tso); +void syncIOCancelPoll(CapIOManager *iomgr, StgTSO *tso); /* Asynchronous operations */ -bool asyncIOWaitReadyPoll(Capability *cap, StgAsyncIOOp *aiop, +bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop, IOReadOrWrite rw, int fd); -void asyncIOCancelPoll(Capability *cap, StgAsyncIOOp *aiop); +void asyncIOCancelPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop); /* Scheduler operations */ bool anyPendingTimeoutsOrIOPoll(CapIOManager *iomgr); -void pollCompletedTimeoutsOrIOPoll(Capability *cap); -void awaitCompletedTimeoutsOrIOPoll(Capability *cap); +void pollCompletedTimeoutsOrIOPoll(CapIOManager *iomgr); +void awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr); #endif /* IOMGR_ENABLED_POLL */ ===================================== rts/posix/Select.c ===================================== @@ -93,9 +93,8 @@ LowResTime getDelayTarget (HsInt us) * if this is true, then our time has expired. * (idea due to Andy Gill). */ -static bool wakeUpSleepingThreads (Capability *cap, LowResTime now) +static bool wakeUpSleepingThreads (CapIOManager *iomgr, LowResTime now) { - CapIOManager *iomgr = cap->iomgr; StgTSO *tso; bool flag = false; @@ -109,7 +108,7 @@ static bool wakeUpSleepingThreads (Capability *cap, LowResTime now) tso->_link = END_TSO_QUEUE; IF_DEBUG(scheduler, debugBelch("Waking up sleeping thread %" FMT_StgThreadID "\n", tso->id)); - pushOnRunQueue(cap,tso); + pushOnRunQueue(iomgr->cap,tso); flag = true; } return flag; @@ -217,9 +216,8 @@ static enum FdState fdPollWriteState (int fd) * */ void -awaitCompletedTimeoutsOrIOSelect(Capability *cap, bool wait) +awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait) { - CapIOManager *iomgr = cap->iomgr; StgTSO *tso, *prev, *next; fd_set rfd,wfd; int numFound; @@ -244,7 +242,7 @@ awaitCompletedTimeoutsOrIOSelect(Capability *cap, bool wait) do { now = getLowResTimeOfDay(); - if (wakeUpSleepingThreads(cap, now)) { + if (wakeUpSleepingThreads(iomgr, now)) { return; } @@ -355,7 +353,7 @@ awaitCompletedTimeoutsOrIOSelect(Capability *cap, bool wait) */ #if defined(RTS_USER_SIGNALS) if (RtsFlags.MiscFlags.install_signal_handlers && signals_pending()) { - startSignalHandlers(cap); + startSignalHandlers(iomgr->cap); return; /* still hold the lock */ } #endif @@ -368,12 +366,12 @@ awaitCompletedTimeoutsOrIOSelect(Capability *cap, bool wait) /* check for threads that need waking up */ - wakeUpSleepingThreads(cap, getLowResTimeOfDay()); + wakeUpSleepingThreads(iomgr, getLowResTimeOfDay()); /* If new runnable threads have arrived, stop waiting for * I/O and run them. */ - if (!emptyRunQueue(cap)) { + if (!emptyRunQueue(iomgr->cap)) { return; /* still hold the lock */ } } @@ -429,7 +427,7 @@ awaitCompletedTimeoutsOrIOSelect(Capability *cap, bool wait) IF_DEBUG(scheduler, debugBelch("Killing blocked thread %" FMT_StgThreadID " on bad fd=%i\n", tso->id, fd)); - raiseAsync(cap, tso, + raiseAsync(iomgr->cap, tso, (StgClosure *)blockedOnBadFD_closure, false, NULL); break; case RTS_FD_IS_READY: @@ -438,13 +436,13 @@ awaitCompletedTimeoutsOrIOSelect(Capability *cap, bool wait) tso->id)); tso->why_blocked = NotBlocked; tso->_link = END_TSO_QUEUE; - pushOnRunQueue(cap,tso); + pushOnRunQueue(iomgr->cap,tso); break; case RTS_FD_IS_BLOCKING: if (prev == NULL) iomgr->blocked_queue_hd = tso; else - setTSOLink(cap, prev, tso); + setTSOLink(iomgr->cap, prev, tso); prev = tso; break; } @@ -460,7 +458,7 @@ awaitCompletedTimeoutsOrIOSelect(Capability *cap, bool wait) } } while (wait && getSchedState() == SCHED_RUNNING - && emptyRunQueue(cap)); + && emptyRunQueue(iomgr->cap)); } #endif /* IOMGR_ENABLED_SELECT */ ===================================== rts/posix/Select.h ===================================== @@ -15,7 +15,7 @@ typedef StgWord LowResTime; LowResTime getDelayTarget (HsInt us); -void awaitCompletedTimeoutsOrIOSelect(Capability *cap, bool wait); +void awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait); #include "EndPrivate.h" ===================================== rts/posix/Signals.c ===================================== @@ -9,21 +9,12 @@ #include "rts/PosixSource.h" #include "Rts.h" -#include "Schedule.h" #include "RtsSignals.h" -#include "Signals.h" -#include "IOManager.h" #include "RtsUtils.h" +#include "Schedule.h" #include "Prelude.h" -#include "Ticker.h" #include "ThreadLabels.h" -#include "Libdw.h" - -/* TODO: eliminate this include. This file should be about signals, not be - * part of an I/O manager implementation. The code here that are really part - * of an I/O manager should be moved into an appropriate I/O manager impl. - */ -#include "IOManagerInternals.h" +#include "MIO.h" #if defined(alpha_HOST_ARCH) # if defined(linux_HOST_OS) @@ -45,10 +36,6 @@ # include <errno.h> #endif -#if defined(HAVE_EVENTFD_H) -# include <sys/eventfd.h> -#endif - #if defined(HAVE_TERMIOS_H) #include <termios.h> #endif @@ -134,110 +121,6 @@ more_handlers(int sig) nHandlers = sig + 1; } -// Here's the pipe into which we will send our signals -static int io_manager_wakeup_fd = -1; -static int timer_manager_control_wr_fd = -1; - -#define IO_MANAGER_WAKEUP 0xff -#define IO_MANAGER_DIE 0xfe -#define IO_MANAGER_SYNC 0xfd - -void setTimerManagerControlFd(int fd) { - RELAXED_STORE(&timer_manager_control_wr_fd, fd); -} - -void -setIOManagerWakeupFd (int fd) -{ - // only called when THREADED_RTS, but unconditionally - // compiled here because GHC.Event.Control depends on it. - SEQ_CST_STORE(&io_manager_wakeup_fd, fd); -} - -/* ----------------------------------------------------------------------------- - * Wake up at least one IO or timer manager HS thread. - * -------------------------------------------------------------------------- */ -void -ioManagerWakeup (void) -{ - int r; - const int wakeup_fd = SEQ_CST_LOAD(&io_manager_wakeup_fd); - // Wake up the IO Manager thread by sending a byte down its pipe - if (wakeup_fd >= 0) { -#if defined(HAVE_EVENTFD) - StgWord64 n = (StgWord64)IO_MANAGER_WAKEUP; - r = write(wakeup_fd, (char *) &n, 8); -#else - StgWord8 byte = (StgWord8)IO_MANAGER_WAKEUP; - r = write(wakeup_fd, &byte, 1); -#endif - /* N.B. If the TimerManager is shutting down as we run this - * then there is a possibility that our first read of - * io_manager_wakeup_fd is non-negative, but before we get to the - * write the file is closed. If this occurs, io_manager_wakeup_fd - * will be written into with -1 (GHC.Event.Control does this prior - * to closing), so checking this allows us to distinguish this case. - * To ensure we observe the correct ordering, we declare the - * io_manager_wakeup_fd as volatile. - * Since this is not an error condition, we do not print the error - * message in this case. - */ - if (r == -1 && SEQ_CST_LOAD(&io_manager_wakeup_fd) >= 0) { - sysErrorBelch("ioManagerWakeup: write"); - } - } -} - -#if defined(THREADED_RTS) -void -ioManagerDie (void) -{ - StgWord8 byte = (StgWord8)IO_MANAGER_DIE; - uint32_t i; - int r; - - { - // Shut down timer manager - const int fd = RELAXED_LOAD(&timer_manager_control_wr_fd); - if (0 <= fd) { - r = write(fd, &byte, 1); - if (r == -1) { sysErrorBelch("ioManagerDie: write"); } - RELAXED_STORE(&timer_manager_control_wr_fd, -1); - } - } - - { - // Shut down IO managers - for (i=0; i < getNumCapabilities(); i++) { - const int fd = RELAXED_LOAD(&getCapability(i)->iomgr->control_fd); - if (0 <= fd) { - r = write(fd, &byte, 1); - if (r == -1) { sysErrorBelch("ioManagerDie: write"); } - RELAXED_STORE(&getCapability(i)->iomgr->control_fd, -1); - } - } - } -} - -void -ioManagerStartCap (Capability **cap) -{ - rts_evalIO(cap,ensureIOManagerIsRunning_closure,NULL); -} - -void -ioManagerStart (void) -{ - // Make sure the IO manager thread is running - Capability *cap; - if (SEQ_CST_LOAD(&timer_manager_control_wr_fd) < 0 || SEQ_CST_LOAD(&io_manager_wakeup_fd) < 0) { - cap = rts_lock(); - ioManagerStartCap(&cap); - rts_unlock(cap); - } -} -#endif - #if !defined(THREADED_RTS) #define N_PENDING_HANDLERS 16 @@ -260,31 +143,9 @@ generic_handler(int sig USED_IF_THREADS, void *p STG_UNUSED) { #if defined(THREADED_RTS) - - StgWord8 buf[sizeof(siginfo_t) + 1]; - int r; - - buf[0] = sig; - if (info == NULL) { - // info may be NULL on Solaris (see #3790) - memset(buf+1, 0, sizeof(siginfo_t)); - } else { - memcpy(buf+1, info, sizeof(siginfo_t)); - } - - int timer_control_fd = RELAXED_LOAD(&timer_manager_control_wr_fd); - if (0 <= timer_control_fd) - { - r = write(timer_control_fd, buf, sizeof(siginfo_t)+1); - if (r == -1 && errno == EAGAIN) { - errorBelch("lost signal due to full pipe: %d\n", sig); - } - } - - // If the IO manager hasn't told us what the FD of the write end - // of its pipe is, there's not much we can do here, so just ignore - // the signal.. - + //TODO: This calls MIO directly. We should go via IOManager API. + // The IOManager API should be extended to cover signals. + timerManagerNotifySignal(sig, info); #else /* not THREADED_RTS */ /* Can't call allocate from here. Probably can't call malloc ===================================== rts/posix/Signals.h ===================================== @@ -27,18 +27,6 @@ void startSignalHandlers(Capability *cap); void install_vtalrm_handler(int sig, TickProc handle_tick); -/* Communicating with the IO manager thread (see GHC.Conc). - * - * TODO: these I/O manager things are not related to signals and ought to live - * elsewhere, e.g. in a module specifically for the I/O manager. - */ -void ioManagerWakeup (void); -#if defined(THREADED_RTS) -void ioManagerDie (void); -void ioManagerStart (void); -void ioManagerStartCap (/* inout */ Capability **cap); -#endif - extern StgInt *signal_handlers; #include "EndPrivate.h" ===================================== rts/posix/Timeout.c ===================================== @@ -26,7 +26,7 @@ */ #if defined(IOMGR_ENABLED_POLL) -bool syncDelayTimeout(Capability *cap, StgTSO *tso, HsInt us_delay) +bool syncDelayTimeout(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay) { Time now = getProcessElapsedTime(); Time target; @@ -42,16 +42,16 @@ bool syncDelayTimeout(Capability *cap, StgTSO *tso, HsInt us_delay) /* fill in a new timeout queue entry */ StgTimeout *timeout; - timeout = (StgTimeout *)allocateMightFail(cap, sizeofW(StgTimeout)); + timeout = (StgTimeout *)allocateMightFail(iomgr->cap, sizeofW(StgTimeout)); if (RTS_UNLIKELY(timeout == NULL)) { return false; } union NotifyCompletion notify = { .tso = tso }; - initElemTimeoutQueue(timeout, notify, NotifyTSO, cap->r.rCCCS); + initElemTimeoutQueue(timeout, notify, NotifyTSO, iomgr->cap->r.rCCCS); ASSERT(tso->why_blocked == NotBlocked); tso->why_blocked = BlockedOnDelay; tso->block_info.timeout = timeout; - insertTimeoutQueue(&cap->iomgr->timeout_queue, timeout, target); + insertTimeoutQueue(&iomgr->timeout_queue, timeout, target); debugTrace(DEBUG_iomanager, "timer for delay of %lld usec installed at time %lld ns", @@ -60,18 +60,18 @@ bool syncDelayTimeout(Capability *cap, StgTSO *tso, HsInt us_delay) } -void syncDelayCancelTimeout(Capability *cap, StgTSO *tso) +void syncDelayCancelTimeout(CapIOManager *iomgr, StgTSO *tso) { ASSERT(tso->why_blocked == BlockedOnDelay); StgTimeoutQueue *timeout = tso->block_info.timeout; - deleteTimeoutQueue(&cap->iomgr->timeout_queue, timeout); + deleteTimeoutQueue(&iomgr->timeout_queue, timeout); tso->block_info.closure = (StgClosure *)END_TSO_QUEUE; /* the timeout is no longer accessible from anywhere (except here) */ IF_NONMOVING_WRITE_BARRIER_ENABLED { - updateRemembSetPushClosure(cap, (StgClosure *)timeout); + updateRemembSetPushClosure(iomgr->cap, (StgClosure *)timeout); } /* We don't put the TSO back on the run queue or change the why_blocked @@ -79,7 +79,7 @@ void syncDelayCancelTimeout(Capability *cap, StgTSO *tso) */ } -static void notifyTimeoutCompletion(Capability *cap, StgTimeout *timeout); +static void notifyTimeoutCompletion(CapIOManager *iomgr, StgTimeout *timeout); /* We use the 64bit Time type from rts/Time.h so our max time (in nanosecond * precision) is over 290 years from the epoch of the monotonic clock. @@ -90,10 +90,8 @@ static void notifyTimeoutCompletion(Capability *cap, StgTimeout *timeout); * With 64bit Time we do not need to worry about clock wraparound and can just * use the simple formula. */ -void processTimeoutCompletions(Capability *cap, Time now) +void processTimeoutCompletions(CapIOManager *iomgr, Time now) { - CapIOManager *iomgr = cap->iomgr; - /* Pop entries from the front of the sleeping queue that are past their * wake time, and unblock the corresponding MVars. */ @@ -105,17 +103,17 @@ void processTimeoutCompletions(Capability *cap, Time now) debugTrace(DEBUG_iomanager,"timer expired at %lld ns", waketime); StgTimeout *timeout; deleteMinTimeoutQueue(&iomgr->timeout_queue, &timeout); - notifyTimeoutCompletion(cap, timeout); + notifyTimeoutCompletion(iomgr, timeout); /* the timeout is no longer accessible from anywhere (except here) */ IF_NONMOVING_WRITE_BARRIER_ENABLED { - updateRemembSetPushClosure(cap, (StgClosure *)timeout); + updateRemembSetPushClosure(iomgr->cap, (StgClosure *)timeout); } } } -static void notifyTimeoutCompletion(Capability *cap, StgTimeout *timeout) +static void notifyTimeoutCompletion(CapIOManager *iomgr, StgTimeout *timeout) { switch (timeout->notify_type) { case NotifyTSO: @@ -123,11 +121,11 @@ static void notifyTimeoutCompletion(Capability *cap, StgTimeout *timeout) StgTSO *tso = timeout->notify.tso; tso->why_blocked = NotBlocked; tso->_link = END_TSO_QUEUE; - pushOnRunQueue(cap, tso); + pushOnRunQueue(iomgr->cap, tso); break; } case NotifyMVar: - performTryPutMVar(cap, timeout->notify.mvar, Unit_closure); + performTryPutMVar(iomgr->cap, timeout->notify.mvar, Unit_closure); break; case NotifyTVar: ===================================== rts/posix/Timeout.h ===================================== @@ -12,9 +12,9 @@ #include "BeginPrivate.h" -bool syncDelayTimeout(Capability *cap, StgTSO *tso, HsInt us_delay); +bool syncDelayTimeout(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay); -void syncDelayCancelTimeout(Capability *cap, StgTSO *tso); +void syncDelayCancelTimeout(CapIOManager *iomgr, StgTSO *tso); /* Process the completion of any timeouts that have expired: this means * notifying whatever is waiting on the timeout, a thread, an MVar or TVar. @@ -24,7 +24,7 @@ void syncDelayCancelTimeout(Capability *cap, StgTSO *tso); * No result is returned: callers can check if there are now any runnable * threads by consulting the scheduler's run queue. */ -void processTimeoutCompletions(Capability *cap, Time now); +void processTimeoutCompletions(CapIOManager *iomgr, Time now); /* Utility to compute the timeout wait time (in milliseconds) between now and * the next timer expiry (if any), or no waiting (if !wait). ===================================== rts/rts.cabal ===================================== @@ -581,6 +581,7 @@ library posix/Ticker.c posix/OSMem.c posix/OSThreads.c + posix/MIO.c posix/Poll.c posix/Select.c posix/Signals.c View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/694defb0348b886d4733d663a6f96b8... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/694defb0348b886d4733d663a6f96b8... You're receiving this email because of your account on gitlab.haskell.org.
participants (1)
-
Duncan Coutts (@dcoutts)