Duncan Coutts pushed to branch wip/dcoutts/signals-cleanup at Glasgow Haskell Compiler / GHC Commits: cc533b66 by Sasha Bogicevic at 2026-10-31T09:18:15-04:00 Don't report -XStrict-generated bangs under -Wredundant-bang-patterns The -XStrict flag causes the desugarer to insert bang patterns. These are not user-written, so -Wredundant-bang-patterns must not report them. To achieve this, the desugarer now tracks whether it is desugaring compiler-generated code (similar to the typechecker's 'tcl_in_gen_code'). Bangs inserted by -XStrict are given generated spans, which suppresses the "redundant bang" warnings without otherwise affecting pattern-match warnings. Fixes #27323 - - - - - 13d38047 by Luite Stegeman at 2026-10-31T09:19:11-04:00 rts: tombstone Weak# copies that lose an evacuation race (#23160) Mark the losing copy of a Weak# during an evacuation race in parallel GC with stg_DEAD_WEAK_info. This ensures that the nonmoving GC doesn't follow dangling pointers, leading to crashes. Fixes #23160 - - - - - 63347328 by Zubin Duggal at 2026-10-31T09:19:52-04:00 ghci: Drop GHCi's exception context when rethrowing in ghc -e Fixes #27847. - - - - - a85fef3f by Zubin Duggal at 2026-10-31T09:19:52-04:00 base: Preserve exception context in modifyIOError Fixes #27851. CLC proposal: https://github.com/haskell/core-libraries-committee/issues/446 - - - - - 1d60b591 by Zubin Duggal at 2026-10-31T09:19:52-04:00 base: Preserve exception context in mapException Fixes #27851. CLC proposal: https://github.com/haskell/core-libraries-committee/issues/446 - - - - - 3c029a1b by Zubin Duggal at 2026-10-31T09:19:52-04:00 base: Preserve exception context when rethrowing exceptions in Handle reads Fixes #27875. CLC proposal: https://github.com/haskell/core-libraries-committee/issues/447 - - - - - ab2b317a by Zubin Duggal at 2026-10-31T09:19:52-04:00 base: Preserve exception context when rethrowing exceptions in hClose Fixes #27875. CLC proposal: https://github.com/haskell/core-libraries-committee/issues/447 - - - - - f5abf277 by Zubin Duggal at 2026-10-31T09:19:52-04:00 base: Preserve exception context when rethrowing exceptions in Windows openFile Fixes #27875. CLC proposal: https://github.com/haskell/core-libraries-committee/issues/447 - - - - - d842c672 by Zubin Duggal at 2026-10-31T09:19:52-04:00 base: Preserve exception context when rethrowing exceptions in executablePath Fixes #27875. CLC proposal: https://github.com/haskell/core-libraries-committee/issues/447 - - - - - c5a8bb9f by Simon Peyton Jones at 2026-10-31T09:20:34-04:00 Simplify a tc-trace which could crash the compiler The trace formatted an error message, which prematurely read the ExpType in a FunResCtxt. Solution: don't put call the error message formatting when tc-tracing. - - - - - ab49b1d2 by Simon Peyton Jones at 2026-10-31T09:20:34-04:00 Tiny comment change to uniqAway - - - - - 6efe4ba3 by Simon Peyton Jones at 2026-10-31T09:20:34-04:00 Make niFixSubst idempotent I discovered (when fixing #27994) that `niFixSubst` didn't actually generate an idempotent subsitution despite its claims. It could return a substitution that mentions domain variables on in its range, when those variables are hidden inside the kind of a type variable occurrence. As a result it can generate ill-kinded types. Boo! The fix is simple, and uses deep substitution. See Note [Finding the substitution fixpoint] I don't think it actually /caused/ any bugs, so there is no regression test. - - - - - 3b60ae7e by Simon Peyton Jones at 2026-10-31T09:20:34-04:00 Fix a nasty bug in fundeps This MR fixes #27994, which turned out to be very tricky. See Note [Exploiting closed type famililes], especially (CF7). I was ending up with TyVars (not TcTyVars) in the solver, because the substitution computed in `getRelevantBranches` mixed the two. The code was just too clever. I ended up simplifying it quite a bit. For /closed/ families we no longer call `trimQtvs` (was `trim_qtvs`). Open families still do use `trimQtvs`. See Note [trimQTvs] This patch also fixes one other buglet in the same code. * Over-saturated type families were being mis-handled. See the new use of `saturatedFamApp_maybe`. - - - - - e4610f2d by Facundo Domínguez at 2026-10-31T09:21:24-04:00 Have haddock --incremental look for the module in -this-unit-id - - - - - bc6c0db6 by sheaf at 2026-10-31T09:22:20-04:00 mightBeFunTy: hotfix for #27979 ce01ccb6 refactored 'GHC.Types.RepType.mightBeFunTy' to look through unary classes. That change introduced the helper function 'unwrap_type' which was supposed to always return an unwrapped type, but its fallback equation mistakenly returned the original type. This caused the subsequent 'tyConAppTyCon_maybe' to fail in cases where it should succeed (e.g. looking through foralls). In this commit, we move all the unary class unwrapping logic into 'unwrapType' where it belonged from the start, and update the documentation of 'unwrapType' to better explain what it should be used for (#26695). Fixes #27979 #26695 ------------------------- Metric Increase: T5642 ------------------------- - - - - - 233cce46 by Alan Zimmerman at 2026-10-31T09:23:01-04:00 EPA: Tidy up ExactPrint.markAnnListA' It takes an action which can modify the AnnList, but this is never used. So remove it. - - - - - 3594a908 by Sylvain Henry at 2026-10-31T09:23:51-04:00 gitmodules: ignore dirty libffi-clib submodule (#26785) `./boot` runs autoreconf in libraries/libffi-clib, which leaves `configure~` behind and rewrites the tracked `aclocal.m4` whenever the local automake version differs from the one that generated it. Every checkout then looks dirty. Ignore working-tree changes in the submodule (`ignore = dirty`); a changed submodule commit is still reported. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> - - - - - 341223c0 by Rodrigo Mesquita at 2026-10-31T09:24:33-04:00 refactor: Keep the interactive Ids directly rather than Name Removes things like names <- map idName ids map (\i -> let id = find (\x -> i==idName x) names) idso and is more straightforward because there's less state going around (no more updating HscEnv as an output) It also kills `printTypeOfNames` completely, since now we just have the Ids. No longer `lookupName` names to get back the ids... ``` printTypeOfName :: GHC.GhcMonad m => Name -> m () printTypeOfName n = do maybe_tything <- GHC.lookupName n case maybe_tything of Nothing -> return () Just thing -> printTyThing thing ``` now is just `printTyThing . AnId` Also: ```diff - tythings <- catMaybes `liftM` mapM GHC.lookupName namesSorted - docs <- mapM pprTypeAndContents [i | AnId i <- tythings] + docs <- mapM pprTypeAndContents idsSorted ``` (and yes, in ExecBreaks there are only Ids, no TyThings, bc they are only constructed by the interpreter execution which can only bind ids. for TyThings use runDecls which doesn't return execBreaks.) There's some care to be had to make sure we use the "most refined" Ids, whereas when using `Name`s we'd always fetch the most refined Id from scratch. Fixes #28052 - - - - - b8545418 by Duncan Coutts at 2026-11-03T12:00:33+00:00 Move idle GC tracking to its own file, step 1 of 2 Add a new IdleGC.{c,h} module. Move the RecentActivity type, recent_activity variable and {get,set}RecentActivity wrappers. - - - - - 581a2b71 by Duncan Coutts at 2026-11-03T12:00:34+00:00 Move idle GC functionality to its own file, step 2 of 2 We can now fully encapsulate the recent_activity state, exposing just three hooks, and one query used in the scheduler and timer tick. This is a prelude to making changes to the implementation of tracking when to perform an idle gc. - - - - - d4fe0fbf by Duncan Coutts at 2026-11-03T12:00:34+00:00 Correct comments in idle GC code The comments still referred to stopTimer/startTimer. These were replaced recently by pauseTimer/unpauseTimer, but the comment was not updated. - - - - - 35ca056d by Duncan Coutts at 2026-11-03T12:00:34+00:00 Make signal handling be a responsibility of the I/O manager(s) Previously it was scattered between I/O managers and the scheduler, and especially the scheduler's deadlock detection. Previously the scheduler would poll for pending signals each iteration of the scheduler loop. The scheduler also had some hairy signal functionality in the deadlock detection: in the non-threaded RTS (only) if there were still no threads running after deadlock detection then it would block waiting for signals. But signals can and (in my opinion) should be thought of as just a funny kind of I/O, and thus should be a responsibility of the I/O manager. So now we have the I/O managers poll for signals when they are polling for I/O completion (and removing the separate poll in the scheduler). And when I/O managers block waiting for I/O then they now also start signal handlers if they get interrupted by a signal. Crucially, if there is no pending I/O or timers, the awaitCompletedTimeoutsOrIO will still block waiting for signals. This patch puts us into an intermediate state: it temporarily breaks deadlock detection in the non-threaded RTS. The waiting on I/O currently happens before deadlock detection. This means we'll now wait forever on signals before doing deadlock detection. We need to move waiting after deadlock detection. We'll do that in a later patch. - - - - - 7baed3f5 by Duncan Coutts at 2026-11-03T12:00:34+00:00 Clean up the RTS internal signal handling API Now that the I/O manager is responsible for signals, we can simplify the API we present for signal handling. We now just need startPendingSignalHandlers, which is called from the I/O managers. We can get rid of awaitUserSignals. We also don't need RtsSignals.h to re-export the platform-specific posix/Signals.h or win32/ConsoleHandler.h We can also hide more of the implementation of signals. Less has to be exposed in posix/Signals.h or win32/ConsoleHandler.h. Indeed, posix/Signals.h becomes empty and we remove it. Partly this is because we don't need inline functions (or macros) in the interface. Also remove signal_handlers from RTS ABI exported symbols list. It does not appear to have any users in the core libs, and its really an internal implementation detail. It should not be exposed unless it's really necessary. - - - - - 0c79180b by Duncan Coutts at 2026-11-03T12:00:34+00:00 Narrow the window in which signals might be missed before poll/select Any signals delivered just before poll/select block will be missed until they are woken later by unrelated events. This arbitrarily delays the delivery of signals. This is not a complete solution. It is a cheap way to narrow but not close the window. A full solution will have to come later. This issue existed already, and is documented in issue #27540. - - - - - 069cb35b by Duncan Coutts at 2026-11-03T12:00:34+00:00 In the scheduler, move I/O blocking after deadlock detection To make deadlock detection effective in the non-threaded RTS when there are deadlocked threads and other unrelated threads waiting on I/O, we need to arrange to do deadlock detection before we block in scheduler to wait on I/O. The solution is to: 1. adjust scheduleFindWork, which runs before deadlock detection, to only poll for I/O and not block; and 2. add a step after deadlock detection to wait on I/O if there are still no threads to run (and there's any I/O or timeouts outstanding) The scheduleCheckBlockedThreads is now so simple that it made more sense to inline it into scheduleFindWork. - - - - - 7330009a by Duncan Coutts at 2026-11-03T12:00:34+00:00 Remove bogus anyPendingTimeoutsOrIO guard from scheduleDetectDeadlock The deadlock detection was only invoked if both of these conditions hold: 1. the run queue is empty 2. there is no pending I/O or timeouts The second condition is unnecessary. The deadlock detection mechanism can find deadlocks even if there are other threads waiting on I/O or timers. Having this extra condition means that we fail to detect blocked threads if there are any threads waiting on I/O or timers. Part of fixing issue #26408 - - - - - 5fa3cc8f by Duncan Coutts at 2026-11-03T12:00:34+00:00 Don't consider pending I/O for early context switch optimisation Context switches are normally initiated by the timer signal. If however the user specifies "context switch as often as possible", with +RTS -C0 then the scheduler arranges for an early context switch (when it's just about to run a Haskell thread). Context switching very often is expensive, so as an optimisation there cases where we do not arrange an early context switch: 1. if there's no other threads to run 2. if there is no pending I/O or timers This patch eliminates case 2, leaving only case 1. The rationale is as follows. The use of this was inconsistent across platforms and threaded/non-threaded RTS ways. It only worked on the non-threaded RTS and on Windows only worked for the win32-legacy I/O manager. On all other combinations anyPendingTimeoutsOrIO would always return false. The fact that nobody noticed and complained about this inconsistency suggests that the feature is not relied upon. If however it turns out that applications do rely on this, then the proper thing to do is not to restore this check, but to add a new I/O manager hint function that returns if there is any pending events that are likely to happen *soon*: for example timeouts expiring within one timeslice, or I/O waits on things likely to complete soon like disk I/O, but not for example socket/pipe I/O. The motivation to avoid this use of anyPendingTimeoutsOrIO is to allow us to eliminate anyPendingTimeoutsOrIO entirely. All other uses of this are just guards on {await,poll}CompletedTimeoutsOrIO and the guards can safely be folded into those functions. This will better cope with some I/O managers having no proper implementation of anyPendingTimeoutsOrIO. Ultimately this will let us simplify the scheduler which currently has to have special #ifdef mingw32_HOST_OS cases to cope with the lack of a working anyPendingTimeoutsOrIO for some Windows I/O managers - - - - - 92cfe0a2 by Duncan Coutts at 2026-11-03T12:00:34+00:00 Remove anyPendingTimeoutsOrIO guarding {poll,await}CompletedTimeoutsOrIO Previously the API of the I/O manager used a two step process: check anyPendingTimeoutsOrIO and then call {poll,await}CompletedTimeoutsOrIO. This was primarily there as a performance thing, to cheaply check if we need to do anything. And then because anyPendingTimeoutsOrIO existed, it was used for other things too. We have now eliminated the other uses, and are just left with the performance pattern. But this was problematic because not all I/O managers correctly implement anyPendingTimeoutsOrIO (specifically the win32 ones), and now that we also make I/O managers responsible for signals then we need to poll/await even if there is no pending I/O or timeouts. If there is no pending I/O or timeouts then await needs to degenerate to just waiting forever for any signals. - - - - - 40038625 by Duncan Coutts at 2026-11-03T12:00:34+00:00 Remove anyPendingTimeoutsOrIO, it is no longer used And this avoids the problems arising from the win32 I/O managers having had a bogus implementation. - - - - - b8bff072 by Duncan Coutts at 2026-11-03T12:00:34+00:00 Remove second scheduler call to awaitCompletedTimeoutsOrIO Previously awaitCompletedTimeoutsOrIO was called both before and after deadlock detection in the scheduler. The reason for that was that the win32 I/O managers had a bogus implementation of anyPendingTimeoutsOrIO and this was used to guard the call of awaitCompletedTimeoutsOrIO prior to deadlock detection. This meant the first call site was never actually called when using the win32 I/O managers. This was the reason for the second call: the first one was never used. What a mess. So now we have a simple design in the scheduler: 1. poll for completed I/O, timers or signals 2. if no runnable threads: do deadlock detection 3. if still no runnable threads: block waiting for I/O, timers or signals. - - - - - 89a0f625 by Duncan Coutts at 2026-11-03T12:00:34+00:00 Lift emptyRunQueue guard out of scheduleDetectDeadlock this improved the clarity of the logic when reading the scheduler code. And improve the comment about scheduleDoGC that was rather unclear. - - - - - de035e24 by Duncan Coutts at 2026-11-03T12:00:34+00:00 Make interruptIOManager work for WinIO (non-threaded) Fixes #27403 The idle GC scheme now fundamentally relies on interruptIOManager working, so it's no longer acceptable for it to be missing for WinIO. Not having it would cause lock ups. - - - - - 6a5a4089 by Duncan Coutts at 2026-11-03T12:00:34+00:00 Make non-threaded deadlock detection also rely on idle GC Only do deadlock detection GC when idle GC kicks in. This also relies on using wakeUpRts, so now do this unconditionally. Previously wakeUpRts was for the threaded rts only. - - - - - d1c9794a by Duncan Coutts at 2026-11-03T12:00:34+00:00 Enable idle GC by default on non-threaded RTS The behaviour is now uniform between the threaded and non-threaded RTS ways. The deadlock detection now relies on idle GC for both threaded and non-threaded ways. Previously deadlock detection did not rely on idle GC for the non-threaded way. Also tweak test T7275 to account for idle GC. This test's output is sensitive to the number of major GCs run. Since this commit enables idle GC for the non-threaded RTS, for this test that increases the number of major GCs, since the test program is frequently idle for more than 300ms. - - - - - 8a0c1e5f by Duncan Coutts at 2026-11-03T12:00:34+00:00 Fix state of idle GC control vars with +RTS -V0 Currently when the user uses +RTS -I0, then doIdleGC is set to false. But if the master tick interval -V is set to 0 then the idleGCDelayTime was being set to 0 but doIdleGC was not being set to false, which is inconsistent, and almost certainly buggy. - - - - - cf5b8a97 by Duncan Coutts at 2026-11-03T12:00:34+00:00 Add a long Note [Deadlock detection] It describes the historical and modern designs and their trade-offs. The point is we've now unified the code for deadlock detection between the threaded and non-threaded ways, by changing the non-threaded to follow the same design as the threaded. - - - - - d7509ba8 by Duncan Coutts at 2026-11-03T12:00:34+00:00 Refactor the utils providing the I/O timeout for different system APIs We have to provide a timeout in three different time formats (int milliseconds, struct timeval *, and struct timespec *). These are for platform APIs including poll, select, ppoll, pselect, epoll_wait, epoll_wait2 and in future kevent and io_uring. Previously we had one util for each format that both worked out what the timeout should be and converted it into the particular format. This duplicates some logic and it also makes it awkward to modify the timeout value (since there's no common format). Modifying the timeout value is exactly what we want to do, in a subsequent commit, where we want to adjust the timeout to take account of idle GC. - - - - - 450365f7 by Duncan Coutts at 2026-11-03T12:00:34+00:00 Update the old select I/O manager to use the new timeout utils This will make it much easier to adjust the timeout to take account of idle GC. - - - - - b42b3f4e by Duncan Coutts at 2026-11-03T12:00:34+00:00 Add opportunistic deadlock detection without idle GC Our general design is now that comprehensive deadlock detection relies on idle GC. This allows us to detect partial deadlocks that are not system deadlocks. But it's not prompt. It's a nice to have feature to be able to promptly detect the cases that are system deadlocks. There's no hope to do this in the threaded RTS with -N > 1, but it can be done for the non-threaded RTS and _could_ be done for the threaded RTS with -N1. We can integrate this feature into the general design without too many contortions. We just need the I/O managers to check before sleeping if they would be about to sleep indefinately with no I/O and no registered signal handlers. If so, they can notify the idle GC that we're deadlocked and we schedule and idle GC. - - - - - 019b6cd5 by Duncan Coutts at 2026-11-03T12:00:34+00:00 Add idle GC mode for !HAVE_PREEMPTION We have to do idle GC (and thus deadlock detection) differently when we do not have the ticker and the ability to interrupt the I/O manager when it's blocking. - - - - - 6ac1bc81 by Duncan Coutts at 2026-11-03T12:00:34+00:00 Add and use USED_IF_PREEMPTION to avoid warnings - - - - - 80046f4e by Duncan Coutts at 2026-11-03T12:00:34+00:00 Add performDeadlockDetection and export via ghc-experimental When documenting the idle GC and deadlock detection behaviour in the user guide, it becomes obiovus that in some cases one would want manual control over deadlock detection. In particular, for applications that disable idle GC, for debugging or robustness, they may want to invoke deadlock detecting GC manually. Bind it in ghc-internal GHC.Internal.System.Mem and re-export via ghc-experimental in System.Mem.Experimental. It is clearly implementation specific behaviour, especially since in GHC the deadlock detection is tied to doing a major GC. - - - - - 1c91bd2a by Duncan Coutts at 2026-11-03T12:00:34+00:00 Add tests for deadlock detection, issue #26408 We have three tests: T26408a: make sure that we can detect a /partial deadlock/ that is not also a /system deadlock/ T26408b: make sure that we can detect a /system deadlock/ promptly without relying on idle GC T26408c: make sure that we do not falsely detect a deadlock when there is nothing to do except wait on signals T26408d: make sure that we can detect a system deadlock with idle GC disabled, after the idle GC timeout has elapsed. - - - - - f9a7e192 by Duncan Coutts at 2026-11-03T12:00:34+00:00 Update the user guide with the revised idle GC behaviour It's now not just for the threaded RTS, but general. Add a new section in the RTS chapter: Idle GC and deadlock detection. Provide general documentation on both topics. In particular, document the behaviour of deadlock detection and how it depends on idle GC. Move the existing entries for -I and -Iw to this section. Add a changelog entry. - - - - - 7ccc867d by Duncan Coutts at 2026-11-03T12:00:34+00:00 Remove variable n_haskell_handlers which has become write-only - - - - - c7e969a3 by Duncan Coutts at 2026-11-03T12:07:38+00:00 Add hadrian build flavour modifier +no_preemptive_rts This builds the rts (non-threaded) way without -DHAVE_PREEMPTION, which is otherwise passed by default. The build does not yet work with this modifier. That's next. Having this modifier is very useful for doing any RTS work that affects the wasm !HAVE_PREEMPTION mode, since it's otherwise pretty hard to test that configuration. - - - - - 3847d189 by Duncan Coutts at 2026-11-03T12:07:38+00:00 Other misc changes to get +no_preemptive_rts working The wasm build doesn't fully rely on !HAVE_PREEMPTION, it also just avoids building entire files. So some fixes are needed for the !HAVE_PREEMPTION in a normal (e.g. Linux) configuration. - - - - - 9bae7190 by Duncan Coutts at 2026-11-03T12:07:38+00:00 Remove use of (un)blockUserSignals in GC Once upon a time, prior to 7ed3f7556f46b957f7efb97ed7ebdc92db2f8ab7 in 2009, the signal handlers were stored within the RTS as a table of StablePtrs. This meant that the table could be accessed concurrently by the C signal handler (picking out a Haskell signal handler when the signal was delivered) and by the GC when it looks at the stable pointers. To avoid problems, the relevant signals were masked around this critical section in the GC. The use of blockUserSignals/unblockUserSignals in the GC was missed when the scheme was rewritten. We can safely remove it now. The GC no longer has anything to do with the data structures within the RTS used for signals. This probably fixes #10686, by avoiding using blockUserSignals in the GC, but the proper fix is later. - - - - - 8d1a28d4 by Duncan Coutts at 2026-11-03T12:07:38+00:00 Change sigprocmask to pthread_sigmask in a couple places in the rts This is part of a series of changes to fix or remove uses of sigprocmask. See issue #27968. According to Posix:
The use of the sigprocmask() function is unspecified in a multi-threaded process.
and indeed it does different things on different platforms. In particular on macOS the behaviour is to change the signal mask for every thread in the process (there is no per-process mask, it changes the per-thread mask for every thread). This obviously does not mix well with other concurrent changes to signal masks in other threads. The use of sigprocmask in exitBySignal just needs to unmask a signal in the current OS thread so that it can send a signal to itself. This purpose only requires changing the signal mask of the calling thread, which is exactly what pthread_sigmask is specified to do. In resetTerminalSettings, sigprocmask (now pthread_sigmask) is used in an attempt to mask a signal. It is unclear if this is effective. It depends on whether there are any remaining threads running by the time the function is called during shutdown. Add a Note [pthread_sigmask and the ineffectiveness of masking signals] and refer to it. - - - - - 09b118db by Duncan Coutts at 2026-11-03T12:07:38+00:00 Change (un)blockUserSignals to use pthread_sigmask, not sigprocmask These functions are exported from the RTS (on posix platforms). They are used in the process library to temporarily mask signals during a fork(). This is only needed to ensure the new process has the signals masked initially. So in particular for this use case we want it to only affect the signal mask of the calling thread. Thus pthread_sigmask is appropriate. This probably fixes #10686. It should mean that there's no interaction between threads that create new processes (which manipulate the signal mask) and GC (which no longer uses blockUserSignals), or multiple OS threads that create processes concurrently, because now they only manipulate their local thread's signal mask. - - - - - e375ca90 by Duncan Coutts at 2026-11-03T12:07:38+00:00 Make (un)blockUserSignals local functions in win32 console handling These functions were defined for all platforms in the RTS public headers but only exported from the RTS on non-win32 platforms. They are only used externally (by the process library) on posix platforms. So for win32, break the link with the exported names and use local only C static functions. They do serve a local purpose, but are now unrelated to the posix ones. - - - - - 6f7f8b8a by Duncan Coutts at 2026-11-03T12:07:38+00:00 In System.Posix.Internals, change import of sigprocmask to pthread_sigmask and refer to the Note. It's still pretty dubious! - - - - - 6f3a5122 by Duncan Coutts at 2026-11-03T12:07:38+00:00 Drop use of sigprocmask in stg_sig_install It is somewhat unclear what the signal masking was trying to protect in stg_sig_install. The comment said // Block the signal until we figure out what to do There are no shared data structured between stg_sig_install and the generic_handler that it installs. In any case, masking signals does not in general work, so whatever it was doing was a false sense of security. - - - - - cb3dc8ef by Duncan Coutts at 2026-11-03T12:07:38+00:00 Make startPendingSignalHandlers a per-platform, not common, API The direction of travel is that user-signal handling should be managed by the I/O manager(s). This is because signals (& win32 console events) are (a funny kind of) I/O, and also because they have to be involved in noticing signals and starting signal handlers. And how we communicate between the low level signal handler and starting the Haskell signal handler threads is also speific to the I/O manager implementation. There is no need for a common API here. These are just utilities shared between some I/O manager implementations. - - - - - 3e19b19c by Duncan Coutts at 2026-11-03T12:07:38+00:00 Unify freeSignalHandlers and finiUserSignals in RTS shutdown They were called at different stages of shutdown. Historically this might have made sense when the handler mechanism used stable pointers. Now there appears to be no good reason. On win32, freeSignalHandlers did nothing. On posix, finiUserSignals did nothing. There appears to be no dependency on the stage of shutdown. It is all local resources. - - - - - ba9d4318 by Duncan Coutts at 2026-11-03T12:07:38+00:00 Push checks on install_signal_handlers flag into signal functions Also, change the logic slightly. The docs for --install-signal-handlers say that it installs handlers for ctl-c etc. It does _not_ say that it disables the API for Haskell code to install signal handerls. Previously however the RTS startup code would guard initUserSignals behind the flag install_signal_handlers. All that code does is initalise variables properly. This meant that when user code installs a handler, it would be working with ill-defined userSignals value and an un-initialised mutex! Similarly, in the win32 console handling, it would skip initialising the hConsoleEvent, which certainly cannot lead to good outcomes. So now we only guard initDefaultHandlers and resetDefaultHandlers behind the install_signal_handlers flag. - - - - - c40c234c by Duncan Coutts at 2026-11-03T12:07:38+00:00 Fix user guide reference to --install-signal-handlers While reviewing --install-signal-handlers docs, noticed a typo in the --generate-crash-dumps docs that referred to --install-signal-handlers when it means --install-seh-handlers. Yes I double-checked the code. - - - - - fef0062a by Duncan Coutts at 2026-11-03T12:07:38+00:00 Split user signal functions out of posix/Signals into new file The direction of travel is that user-signal handling should be managed by the I/O manager(s). The user signal functions (i.e. to allow Haskell code to set signal handlers) should stop being first class functions called by the rest of the RTS and become utilities used by the I/O managers. Start here by splitting them out. - - - - - ae41baea by Duncan Coutts at 2026-11-03T12:07:38+00:00 Make stg_sig_install part of the IOManager API This is the external entry point for user code to install signal handlers. So it becomes part of the I/O manager API. Eventually it will dispatch to the I/O manager implementations (which will do different things), but for now it dispatches to the same old implementation, under a new name. - - - - - 93809a84 by Duncan Coutts at 2026-11-03T12:07:38+00:00 Document that nocldstop is a totally mad implicit parameter and it's part of our public API! - - - - - 7ae773ac by Duncan Coutts at 2026-11-03T12:07:38+00:00 Move user signal init and fini into the I/O manager Initially just moved into start/stopIOManager, but a later step will be to make that be part of the initialisation of each I/O manager. Why? Because they cannot have a common init if they have different data structures that need initialisation. In fact they already do have different code and data (threaded/non-threaded, windows/other), but this is fudged using CPP. - - - - - a1d406da by Duncan Coutts at 2026-11-03T12:07:38+00:00 Push {init,fini}{UserSignals,ConsoleEvents} down into I/O managers So they become part of individual I/O managers, which will allow us to start specialising them more sanely. Rename the Win32 ones to {init,fini}ConsoleEvents. - - - - - dbb9b781 by Duncan Coutts at 2026-11-03T12:07:38+00:00 Push installUserSignalHandler down into individual I/O managers This will allow us to specialise them. Initally they all use the same (existing) generic_handler. - - - - - fd8557f4 by Duncan Coutts at 2026-11-03T12:07:38+00:00 Specialise MIO's installing of signals It already used a separate implementation, but using cpp via the signal "generic_handler". Now it is much more direct and local. MIO writes signals into a pipe and reads and dispatches them at the other end (in Haskell). - - - - - 3de0d155 by Duncan Coutts at 2026-11-03T12:07:39+00:00 Introduce an I/O manager data structure shared between all caps Accessed via iomgr->shared, but it points to the same struct for all capabilities. We initialise and free this with cap 0. We will use it initially for signal handling, since only one capability needs to dispatch signals. This is a fairly general mechanism, so I/O managers may be able to make use of it for other purposes, especially once in-RTS ones are adapted to work in the threaded RTS with multiple capabilities. - - - - - 4a9226e3 by Duncan Coutts at 2026-11-03T12:07:39+00:00 Use the shared I/O manager data structure for MIO for signals Move the MIO I/O manager's timer manager control fd from a global variable into the shared I/O manager struct. Soon we will use the same mechanism for the same purpose of signal handling for the other posix I/O managers. - - - - - d8a89122 by Duncan Coutts at 2026-11-04T23:34:02+00:00 Extract and add more utility functions for the signal self-pipe trick Extract a utility function writeSignalInfoToPipe from the MIO I/O manager so we can use it in other I/O managers too. Also add related utility functions readSignalInfoFromPipe, newNonBlockingPipe and startSignalHandler. We will use these in the poll and selectbis I/O managers. - - - - - 0744a481 by Duncan Coutts at 2026-11-04T23:34:02+00:00 Redo the scheme for special fds in the poll and selectbis I/O managers At the moment we only have one special fd for these I/O managers: the pipe (or eventfd) for interrupting them when they're sleeping. We're about to gain a second for signal handling. The existing scheme was rather ad-hoc and hard to extend. It was already complicated by the fact that the interrupt fd is optional. The signal fd will also be optional in the sense that cap 0 will have it but cap >0 will not. So the new scheme is that we add special aiop entries into the aiop_table, and for the most part we can treat these uniformly with other aiops. But the action we take for them if/when they are ready is handled specially. This seems much cleaner and will be easy to extend for signals. - - - - - 7e6b6d65 by Duncan Coutts at 2026-11-04T23:34:02+00:00 Use the signal self-pipe trick for the poll and selectbis I/O managers So now the low level signal handler delivers signals into a pipe that is monitored by the I/O manager (on cap 0). The I/O manager then dispatches the handler (starting a Haskell thread). There are two important reasons for this change: 1. This method does not have any windows of time where signals can be missed for arbitrary periods of time. The previous method has a race where a signal delivered just before the I/O manager calls poll/select will not notice the signal until some unrelated event causes poll/select to return. See issue #27540. The new method ensures that a fd is ready and so poll/select will notice immediately (if the signal was delivered prior to the call to poll/select) or later. 2. The existing method relies on temporarily masking signals for concurrency safety which cannot work reliably in a multi-threaded process. Fixing this is a necessary pre-requisite to enable these in-RTS I/O managers to work in the threaded RTS. The issue is that startPendingSignalHandlers and the low level signal handler share the pending_handler_buf data structure, and so startPendingSignalHandlers has to mask delivery of signals to avoid the shared data being mutated concurrently. But masking signals in a multi-threaded process is not generally effective because the signal can still be delivered in any thread Document the self-pipe trick in a Note. This fixes issue #27540. - - - - - 7991398a by Duncan Coutts at 2026-11-04T23:34:02+00:00 Move the legacy user signal handler into legacy select I/O manager We do not convert the legacy select I/O manager to use the self-pipe trick. It keeps the existing code, but since it is now the only user of it, then that code can move from being a shared utility to live with the select I/O manager. There are some minor modifications to the code moved so that it is not entangled with other remaining shared signal utils. - - - - - a29a447d by Duncan Coutts at 2026-11-04T23:34:02+00:00 Deprecate (un)blockUserSignals for external RTS users Encourage external users to stop using these actions. - - - - - 8784a85f by Duncan Coutts at 2026-11-04T23:34:02+00:00 Split interruptStgRts and remove wakeUpRtsViaTicker. Split the two uses of interruptStgRts into their posix and win32 cases, and move them to their use sites. Make the posix case not rely on wakeUpRts or wakeUpRtsViaTicker. The use of wakeUpRtsViaTicker while it seemed to cleverly solve overly-close coupling in one area, causes overly-close coupling in another area: ticker(s). And we want to start improving the ticker(s) but the existence of wakeUpRtsViaTicker holds that back. Delete wakeUpRtsViaTicker and its related implementation pieces. In the posix case, we add interruptIOManagerFromSignal, which is specific for the circumstance of use within a signal handler. The implementation of this for the in-RTS I/O managers is just interruptIOManager but for MIO it has to wake up the timer manager thread. All of this seems pretty unnecessary since it is all to support a ctl-c handler that is almost never used since it is duplicated and overridden by a ctl-c handler installed by runMainIO. See issue #28066. - - - - - b6d0b8bd by Duncan Coutts at 2026-11-04T23:34:02+00:00 Simplify execution of signal handlers We make a few related changes: 1. Don't use malloc/free for passing siginfo_t from C to Haskell, use heap allocated byte arrays indead. The Haskell side already uses ForeignPtr which can use byte array directly. 2. Don't redundantly fork IO threads for signal handlers, when invoked from in-RTS I/O managers. The I/O manager already creates a new IO thread, but the existing runHandlers would then redundantly forkIO again. This is a waste of resources but also means signal handlers end up getting scheduled last. 3. Schedule signal handler threads first. This should fix #28067. Introduce new runHandler/runHandlerPtr to replace the existing runHandlers/runHandlersPtr. Conveniently the old ones have a weird name (they only invoke one handler!) so we can use a sensible name, and leave the old ones (which are exported via base!) alone. Add deprecation warnings though, since nobody should be using them via base. Update the caller in ghc-internal: the MIO timer manager. This one does need to use forkIO, but it's clearer if that forkIO is done there. - - - - - c98a6ece by Duncan Coutts at 2026-11-04T23:34:02+00:00 Stop using signal-unsafe functions in signal handlers Unfortunately this does mean that the SIGQUIT functionality of dumping a stack trace has to be disabled for now. There's no short term practical way to allow that to work. The stack trace functions all use dynamic memory allocation using malloc/free, which is verboten in signal handlers. See #28071 about re-enabling the stack trace info. - - - - - 013f46bb by Duncan Coutts at 2026-11-04T23:34:02+00:00 Add a changelog entry for the signals changes - - - - - 162 changed files: - .gitmodules - + changelog.d/T27323 - + changelog.d/T27847 - + changelog.d/T27979 - + changelog.d/T27994 - + changelog.d/clc-446 - + changelog.d/clc-447 - + changelog.d/fix-nonmoving-weak-evacuation-race-23160 - + changelog.d/ghc-api-interactive-ids - + changelog.d/idle-gc-and-deadlock-detection - + changelog.d/signals-cleanup - compiler/GHC/Core/Coercion/Axiom.hs - compiler/GHC/Core/Predicate.hs - compiler/GHC/Core/TyCo/FVs.hs - compiler/GHC/Core/TyCo/Subst.hs - compiler/GHC/Core/Type.hs - compiler/GHC/Core/Unify.hs - compiler/GHC/HsToCore/Match.hs - compiler/GHC/HsToCore/Match/Literal.hs - compiler/GHC/HsToCore/Monad.hs - compiler/GHC/HsToCore/Pmc.hs - compiler/GHC/HsToCore/Pmc/Desugar.hs - compiler/GHC/HsToCore/Types.hs - compiler/GHC/HsToCore/Utils.hs - compiler/GHC/Runtime/Context.hs - compiler/GHC/Runtime/Debugger.hs - compiler/GHC/Runtime/Eval.hs - compiler/GHC/Runtime/Eval/Types.hs - compiler/GHC/Runtime/Heap/Inspect.hs - compiler/GHC/Tc/Instance/Family.hs - compiler/GHC/Tc/Solver/FunDeps.hs - compiler/GHC/Tc/Utils/Unify.hs - compiler/GHC/Types/RepType.hs - compiler/GHC/Types/Var/Env.hs - docs/users_guide/profiling.rst - docs/users_guide/runtime_control.rst - ghc/GHCi/UI.hs - ghc/GHCi/UI/Monad.hs - hadrian/src/Flavour.hs - hadrian/src/Flavour/Type.hs - hadrian/src/Settings/Builders/Common.hs - hadrian/src/Settings/Default.hs - libraries/base/changelog.md - libraries/ghc-experimental/src/System/Mem/Experimental.hs - libraries/ghc-internal/include/RtsIfaceSymbols.h - libraries/ghc-internal/src/GHC/Internal/Conc/Signal.hs - libraries/ghc-internal/src/GHC/Internal/Control/Exception/Base.hs - libraries/ghc-internal/src/GHC/Internal/Event/Control.hs - libraries/ghc-internal/src/GHC/Internal/Event/TimerManager.hs - libraries/ghc-internal/src/GHC/Internal/IO/Handle/Internals.hs - libraries/ghc-internal/src/GHC/Internal/IO/Handle/Text.hs - libraries/ghc-internal/src/GHC/Internal/IO/Handle/Windows.hs - libraries/ghc-internal/src/GHC/Internal/System/Environment/ExecutablePath.hsc - libraries/ghc-internal/src/GHC/Internal/System/IO/Error.hs - libraries/ghc-internal/src/GHC/Internal/System/Mem.hs - libraries/ghc-internal/src/GHC/Internal/System/Posix/Internals.hs - rts/Capability.c - rts/IOManager.c - rts/IOManager.h - rts/IOManagerInternals.h - + rts/IdleGC.c - + rts/IdleGC.h - rts/Linker.c - rts/Prelude.h - rts/RtsFlags.c - rts/RtsSignals.h - rts/RtsStartup.c - rts/RtsSymbols.c - rts/Schedule.c - rts/Schedule.h - rts/Ticker.h - rts/Timer.c - rts/configure.ac - rts/include/Rts.h - rts/include/rts/BlockSignals.h - rts/include/rts/Config.h - rts/include/rts/IOInterface.h - rts/include/rts/OSThreads.h - rts/include/rts/RtsToHsIface.h - rts/include/rts/storage/Closures.h - rts/include/rts/storage/GC.h - rts/posix/FdWakeup.c - rts/posix/MIO.c - rts/posix/MIO.h - rts/posix/OSThreads.c - rts/posix/Poll.c - rts/posix/Poll.h - rts/posix/Select.c - rts/posix/Select.h - rts/posix/SelectBis.c - rts/posix/SelectBis.h - rts/posix/Signals.c - − rts/posix/Signals.h - rts/posix/TTY.c - rts/posix/Ticker.c - rts/posix/Timeout.c - rts/posix/Timeout.h - + rts/posix/UserSignals.c - + rts/posix/UserSignals.h - rts/rts.cabal - rts/sm/Evac.c - rts/sm/GC.c - rts/win32/AsyncMIO.c - rts/win32/AsyncMIO.h - rts/win32/AsyncWinIO.c - rts/win32/AsyncWinIO.h - rts/win32/AwaitEvent.c - rts/win32/ConsoleHandler.c - rts/win32/ConsoleHandler.h - + testsuite/tests/codeGen/should_compile/T27979.hs - + testsuite/tests/codeGen/should_compile/T27979.stderr - testsuite/tests/codeGen/should_compile/all.T - testsuite/tests/count-deps/CountDepsParser.stdout - + testsuite/tests/exceptions/T27851.hs - + testsuite/tests/exceptions/T27851.stderr - + testsuite/tests/exceptions/T27851b.hs - + testsuite/tests/exceptions/T27851b.stderr - + testsuite/tests/exceptions/T27851c.hs - + testsuite/tests/exceptions/T27851c.stdout - + testsuite/tests/exceptions/T27851d.hs - + testsuite/tests/exceptions/T27851d.stdout - + testsuite/tests/exceptions/T27851d.stdout-javascript-unknown-ghcjs - testsuite/tests/exceptions/all.T - testsuite/tests/ghc-e/should_fail/T18441fail7.stderr - testsuite/tests/ghc-e/should_run/ghc-e005.stderr - + testsuite/tests/ghci.debugger/scripts/RttiSharedTyVar.hs - + testsuite/tests/ghci.debugger/scripts/RttiSharedTyVar.script - + testsuite/tests/ghci.debugger/scripts/RttiSharedTyVar.stdout - testsuite/tests/ghci.debugger/scripts/all.T - + testsuite/tests/haddock/haddock_testsuite/IncrementalUnitId.hs - testsuite/tests/haddock/haddock_testsuite/Makefile - testsuite/tests/haddock/haddock_testsuite/all.T - + testsuite/tests/haddock/haddock_testsuite/haddockIncrementalUnitId.stdout - testsuite/tests/interface-stability/ghc-experimental-exports.stdout - testsuite/tests/interface-stability/ghc-experimental-exports.stdout-mingw32 - + testsuite/tests/pmcheck/should_compile/T27323.hs - + testsuite/tests/pmcheck/should_compile/T27323b.hs - testsuite/tests/pmcheck/should_compile/all.T - testsuite/tests/profiling/should_run/Makefile - testsuite/tests/rts/Makefile - + testsuite/tests/rts/T23160.hs - + testsuite/tests/rts/T23160.stdout - + testsuite/tests/rts/T26408a.hs - + testsuite/tests/rts/T26408a.stderr - + testsuite/tests/rts/T26408b.hs - + testsuite/tests/rts/T26408b.stderr - + testsuite/tests/rts/T26408c.hs - + testsuite/tests/rts/T26408c.stdout - + testsuite/tests/rts/T26408d.hs - + testsuite/tests/rts/T26408d.stderr - testsuite/tests/rts/all.T - + testsuite/tests/typecheck/should_compile/T27994.hs - + testsuite/tests/typecheck/should_compile/T27994a.hs - + testsuite/tests/typecheck/should_compile/T27994b.hs - testsuite/tests/typecheck/should_compile/all.T - testsuite/tests/warnings/should_compile/Overflow.stderr-ws-32 - testsuite/tests/warnings/should_compile/Overflow.stderr-ws-64 - testsuite/tests/warnings/should_compile/T13256.stderr-ws-32 - testsuite/tests/warnings/should_compile/T13256.stderr-ws-64 - utils/check-exact/ExactPrint.hs - utils/haddock/CHANGES.md - utils/haddock/haddock-api/src/Haddock/Interface.hs The diff was not included because it is too large. View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/a50e2a4afd04c9d93a4ace07980501a... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/a50e2a4afd04c9d93a4ace07980501a... You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help