Description
Tracking ticket: #164
When GHC's extensible exception mechanism was introduced, it did not provide the Exception
class with a method to render exceptions to a string. Instead, GHC's implementation opted to use the show
function. This introduced an uneasy tension:
- We want error messages being displayed to the user to be easy-to-understand
- We want the
show
to produce valid Haskell syntax (being the inverse ofread
)
In 2008 this tension was noticed and addressed with the introduction of the displayException
method into Exception
:
class (Show a, Typeable a) => Exception a where
...
displayException :: a -> String
displayException = show
However, at the time the CLC stopped short of changing the top-level exception handler (which handles exceptions not caught by the user) to use this function to display messages to the user.
Here I propose that we do precisely this. While this is a change in semantics, I argue that it is worthwhile as:
- it only affects exceptions not otherwise caught by the user
- it only affects output of
stderr
, which is already fragile at best - all exceptions defined by
base
use the defaultshow
implementation ofdisplayException
and therefore will not change in behavior
Furthermore, this change will facilitate showing of useful annotation information introduced by the exception backtrace proposal (section 2.10).
Implementation
This change will require changing GHC.Conc.Sync.uncaughtExceptionHandler
as follows:
diff --git a/libraries/base/GHC/Conc/Sync.hs b/libraries/base/GHC/Conc/Sync.hs
index 7177c87dbf2..39b45c20a5a 100644
--- a/libraries/base/GHC/Conc/Sync.hs
+++ b/libraries/base/GHC/Conc/Sync.hs
@@ -950,9 +950,7 @@ uncaughtExceptionHandler = unsafePerformIO (newIORef defaultHandler)
defaultHandler :: SomeException -> IO ()
defaultHandler se@(SomeException ex) = do
(hFlush stdout) `catchAny` (\ _ -> return ())
- let msg = case cast ex of
- Just Deadlock -> "no threads to run: infinite loop or deadlock?"
- _ -> showsPrec 0 se ""
+ let msg = displayException se
withCString "%s" $ \cfmt ->
withCString msg $ \cmsg ->
errorBelch cfmt cmsg
With a corresponding change to the Exception
instance of GHC.IO.Exception.Deadlock
to preserve existing behavior:
diff --git a/libraries/base/GHC/IO/Exception.hs b/libraries/base/GHC/IO/Exception.hs
index 5f8a1ca024a..c23c65680c1 100644
--- a/libraries/base/GHC/IO/Exception.hs
+++ b/libraries/base/GHC/IO/Exception.hs
@@ -100,7 +100,8 @@ blockedIndefinitelyOnSTM = toException BlockedIndefinitelyOnSTM
data Deadlock = Deadlock
-- | @since 4.1.0.0
-instance Exception Deadlock
+instance Exception Deadlock where
+ displayException _ = "no threads to run: infinite loop or deadlock?"
-- | @since 4.1.0.0
instance Show Deadlock where
Migration
This change will result in different output on stderr
in the case of uncaught exceptions. In general, users who want predictable output on stderr
are urged to provide their own exception handler (either via setUncaughtExceptionHandler
or a well-placed catch
in their program). The current show
-based output can be retained via:
setUncaughtExceptionHandler $ \(SomeException exc) -> do
To ease mitigation, we propose adding the following function exposing the current uncaught exception handler's behavior from GHC.Conc.Sync
:
oldDefaultHandler :: SomeException -> IO ()
oldDefaultHandler = mkTopExceptionHandler $ \ se@(SomeException ex) ->
case cast ex of
Just Deadlock -> "no threads to run: infinite loop or deadlock?"
_ -> show se