-
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