Handling of Uncaught Exceptions in Haskell
When your Haskell application’s thread throws an exception that does not get caught, the Haskell runtime system will handle it and print it based on the
This is the default behavior that can be customized using the
Personally, I was quite surprised when I noticed that the
Show type class is used for rendering.
At the early days of my Haskell development experience, I noticed that the
Exception type class had the
displayException method and assumed that it’s used for printing of uncaught exceptions.
A few years later, I realized that my assumption was wrong and was quite surprised.
The default uncaught exception handler uses
showsPrec from the
Show type class, not
When I shared this information with my colleagues, some of them were surprised as well.
I was inclined to think that
displayException is a better default, and it turned out that I was not the only one thinking this way.
Since people were supporting this idea, I decided to do two things:
- Raise this topic in the GHC issue tracker.
- Create a small library with a function that would carefully modify uncaught exception handling to use
displayException. We already had such a helper in our code base and wanted to extract it into a library. This function is not entirely trivial because, for example, the
ExitCodeexception should be handled specially (the program should exit with the appropriate exit code in this case).
I started with researching this topic and discovered that it already had been discussed back in 2014.
The relevant GHC issue is #9822. It refers to the e-mail thread where two things were proposed:
- The addition of
- The modification of the default uncaught exception handler to use
The first thing was accepted and implemented, and the
displayException is a part of
Exception for many years already.
The second thing, however, appeared to be debatable and there was no consensus regarding it.
You can read the whole thread for details since it’s not so big.
Two essential points from it are:
- There is a good reason to use
showfor uncaught exceptions. Uncaught exceptions are usually encountered by programmers, so it’s better to format them in “programmer-friendly” way, i. e. using
show. So that should be the default. However, sometimes one may prefer
displayException, so it should be easy to use it instead.
- Even though there is
setUncaughtExceptionHandler, modifying the uncaught exception handler to use
displayExceptionis not easy because:
- stdout has to be flushed, ignoring any exceptions while doing so;
- output has to go to stderr, not stdout;
ExitCodeexception should be handled correctly, and the program should exit with correct exit code.
After that, there was a discussion about adding
displayExceptionHandler that would do “the right thing”, but this discussion quickly moved into another direction and no such function was added.
In 2020, I couldn’t find a function that does “the right thing” on Hackage.
When I first implemented this helper (before reading this thread), I didn’t take
ExitCode into account and had a subtle minor bug.
My program was exiting with non-zero code when launched with
optparse-applicative library throws (probably indirectly)
ExitSuccess in this case, but I didn’t consider that.
The intended difference between
show is that the former should return a “human-friendly” string, while the latter does not say anything about “human-friendliness” and is usually auto-generated to produce “programmer-friendly” strings that reflect the internal structure of the Haskell type.
A good analogy from the aforementioned e-mail thread is
repr() functions in Python.
Based on my experience, I admit that
displayException is usually implemented as
show, which is the default implementation.
I think there are 3 possible reasons for that:
Showis auto-generated and the string produced by it sufficiently human-friendly and custom formatting is not needed. I believe it’s a rare case because auto-generated
showusually looks quite verbose.
- A programmer just didn’t bother implementing
displayException. Maybe they forgot to do it (the compiler doesn’t check it), maybe they were lazy, maybe they didn’t even know about this method.
- There is a custom
Showinstance with human-readable pretty formatting. I think it’s a bad idea because then there is no way to render an exception in “programmer-friendly” way. “Programmer-friendly” way usually contains Haskell identifiers, so that you can quickly find documentation for the relevant data type. Also, you should be able to see all values that constitute the exception. “Human-friendly” way may return a string where some fields are omitted and figuring out which type of exception was thrown can be complicated.
So currently, in many cases it does not really matter which function is called to print an uncaught exception because
displayException is often implemented as
However, I think that in the ideal world, most of the types with
Exception instance should provide a custom definition for
displayException, and the topic brought up in this article will matter.
It’s indeed debatable which function is more appropriate. I think there is no clear winner, so it should be easy to use either of them for uncaught exceptions.
One thought that I have is that
displayException is more appropriate for console apps because uncaught exceptions are visible to end users, while
show is more appropriate for UI apps because errors in UI apps are hidden from end users.
Anyway, it’s just one thought, and I don’t intend to recommend using
displayException for uncaught exceptions, I just want to make it easy to do so if someone wants that.
Since there was a discussion about providing a function that would change uncaught exception handling to use
displayException but no actions were taken, I went ahead and implemented a tiny library for this use case.
The library is called uncaught-exception.
You can view its source code on GitHub.
The default exception handler is implemented in GHC as follows:
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 "" withCString "%s" $ \cfmt -> withCString msg $ \cmsg -> errorBelch cfmt cmsg -- don't use errorBelch() directly, because we cannot call varargs functions -- using the FFI. foreign import ccall unsafe "HsBase.h errorBelch2" errorBelch :: CString -> CString -> IO ()
I could have implemented a similar handler myself, but decided not to do it because I don’t entirely understand this definition. Specifically:
- How is
ExitCodehandled and how does a Haskell program determine which code it should exit with? Is it handled by
errorBelch? Probably no because
errorBelchonly takes two strings. So perhaps it’s handled outside of this helper.
- What does
errorBelchdo, and why is it implemented in a foreign language (most likely C/C++)? Can’t it be implemented in Haskell? Should I use FFI in my handler as well?
I guess I could find out answers to these questions, but I picked a simpler way, actually two ways. If this default handler is ever updated in GHC, I will not have to do anything in my library. I define the following wrapper:
-- | Helper data type used by 'displayUncaughtException'. -- It causes @show@ to call @displayException@. -- When an exception of this type is caught, it will be @show@n and -- that will call @displayException@ of the wrapped exception. newtype DisplayExceptionInShow = DisplayExceptionInShow SomeException instance Show DisplayExceptionInShow where show (DisplayExceptionInShow se) = displayException se instance Exception DisplayExceptionInShow
along with the
wrapException :: SomeException -> SomeException function that wraps exceptions into this wrapper.
And define two functions:
displayUncaughtException :: IO a -> IO a displayUncaughtException = handle (throwIO . wrapException) withDisplayExceptionHandler :: IO a -> IO a withDisplayExceptionHandler action = do handler <- getUncaughtExceptionHandler setUncaughtExceptionHandler (handler . wrapException) action <* setUncaughtExceptionHandler handler
They serve the same purpose, but I am not sure which one is better, hence I provide both. If you have a good argument in favor of one of these functions, please let me know.
In the first version, we don’t touch the default handler at all.
In the second version, we only update it to wrap its argument into
DisplayExceptionInShow and then apply as is.
Also, there is
setDisplayExceptionHandler, which updates the handler the same way as
withDisplayExceptionHandler but doesn’t restore it in the end.
In all cases, all concerns should be handled.
All but one: if we wrap
DisplayExceptionInShow, we will break functions like
exitSuccess because they work by throwing an
ExitCode exception and we change its type.
So we implement
wrapException carefully to ignore
wrapException :: SomeException -> SomeException wrapException e | isSyncException e , Nothing <- fromException @Deadlock e , Nothing <- fromException @ExitCode e = toException $ DisplayExceptionInShow e | otherwise = e where isSyncException :: SomeException -> Bool isSyncException = isNothing . fromException @SomeAsyncException
Notice that we also ignore exceptions that can be cast to
The reason is twofold:
- There are not many such exceptions, and their
showrepresentation should be more or less human-friendly.
- I am afraid that by wrapping them into
DisplayExceptionInShow, we may break something the same way as we break when we wrap
We also ignore
Deadlock because it’s specifically handled by
Notice that there is an essential difference in case an uncaught exception occurs in a thread other than the one where one of these functions was used.
displayUncaughtException only affects the thread where it’s called, all other threads are unaffected (and use
Show for printing by default).
withDisplayExceptionHandler updates the global variable holding the uncaught exception handler and thus affects all threads in the application.
The same is true for
Since the uncaught exception handler is stored in a global variable, there can be race conditions when multiple threads are involved.
For example, if an action inside
withDisplayExceptionHandler forks a thread and this thread ends with an exception, error printing may be unpredictable (depends on what action finishes first).
A good practice is to use functions from the
async package, such as
withAsyncensures that the child thread always stops before the parent thread;
concurrentlyadditionally ensures that exceptions thrown by children threads are propagated to their parent.
Further discussions about the
async packages are out of the scope of this article.
Several years ago, a new method was added to the
Exception type class:
It was supposed to be used for printing uncaught exceptions, but this idea appeared to be controversial and didn’t get enough support.
As an alternative, it was proposed to add helpers that one would use to change the default uncaught exception handler to use
However, to the best of my knowledge, no such helpers exist nowadays, neither in
base nor in a dedicated library.
In this article, I’ve presented a new library with such helpers. I am not 100% sure my functions handle all cases correctly, maybe there are other exceptions that need special treatment. If you have any suggestions regarding the implementation of this library, please don’t hesitate to open an issue/PR in the repo.
Serokell specializes in Haskell app development. If you need reliable custom software designed for the specific purposes of your business, contact our team to discuss your situation.