Default Exception Handlers
What happens to exceptions when you don’t explicitly handle them? This week we review Taylor’s blog post about default exception handlers.
Episode 43 was published on 2021-04-19.
>> Hello and welcome to the Haskell Weekly podcast. This is a show about Haskell, a purely functional programming language. I'm your host Taylor Fausak. I'm an engineer at ITProTV. With me today is Cameron Gera, one of the engineers on my team. Thanks for joining me today, Cam.
>> Thanks for having me, Taylor, I know we've missed a couple of weeks here, so I'm excited for us to be back here, recording some Haskell Weekly podcasts. And, uh, You know, I think today is just a really special day. You know, I think last episode, we kind of talked about asynchronous exceptions and today we're going to actually dive into the long lost brother slash brother from another mother, um, exception handling in Haskell. And we're going to be talking about exception handlers. We're actually going to specifically focus on a post by our near and dear friend Taylor Fausak called default exception handler in Haskell. So really looking forward to diving in today. Thanks for having me on again, Taylor. uh, what do you say? We just kind of jump in and we'll give a high level overview of what kind of prompted this.
>> Sure it sounds good. And I think this might be the first time we're, uh, talking about one of my own posts. So this feels a little weird, but let's do it. Um, so as you mentioned previously, we were talking about async exceptions and this time around, we're talking about exception handlers and in particular, the default exception handler. And I decided to write a post about this because. I knew about this concept from other programming languages, where you can put in an exception handler that will get called if nothing else handles that exception. And for the longest time, I thought that Haskell didn't have this, but it turns out it does. And it's had it for a long time, but it's in, it's like squirreled away in an internal GHC module and it's not documented well. So that's why I wrote this post. And, uh, the motivation actually was that we spawn threads on our application at work. And I was curious, are any of those threads failing? And are we like, do we not know about that? So if we had a default exception handler, we could find out. Um, so yeah, that's the motivation and that's a big picture of what we'll be talking about.
>> Nice. Yeah, I think, um, you know, it's, uh, interesting that this is a very, not highly talked about subject. Because in most programming languages, they have that default handler for these exceptions. And it's generally a first class thing. Um, because you need to know, oh, if something breaks in my code, how do I handle it? Where for Haskell. Most of the time people rely on the compiler to say, oh yeah, this is going to quote unquote work as far as types lined up. And then you have, you know, the runtime environment, which gets kinda messy. Because of like the unknown around exceptions and the, more or less, mystery for easily handling stuff with Haskell. Um, and I, I think this brings great light and could honestly be, be, uh, what's the word I'm looking for here. It could be the documentation for it.
>> Oh, there we go.
>> You know, this amazing function that, uh, you gotta tell us about what's this.
>> So, uh, yeah, to, to what you said, I should probably open a pull request against GHC to add documentation to this function, or even export it from somewhere more typical, like control dot exception or system dot IO. Um, but yeah, the, uh, the function we're talking about is called set uncaught exception handler. And it's very well-named. It does exactly what it says based on the name. Um, uh, but to the point you were making about, um, you know, this doesn't seem to come up that often in Haskell, I was trying to puzzle through why that may be the case. And I think part of it is that often you just like wrap your entire main function in an exception handler, and that will catch most things. But if you spawn a thread and don't, uh, wait for that thread to finish or re throw its exceptions on your main thread, then you're going to miss those. And fortunately, we have the async library that manages that for you. And if you're using the async library and using the helpers that it provides, like race or concurrently, then this probably isn't a problem for you, but there's always a chance that you're, depending on something that uses fork IO and doesn't install an exception handler. So maybe this just doesn't come up that much. Um, and for us, I said, I suspected that this could be the case that we had some uncaught exceptions. Uh, and after installing this in our application, it's not like we had a deluge of new exception reports. Um, so it seems like by and large, we were handling this correctly in the first place.
>> Right. So this is kind of a thing you wouldn't really know you're missing until you'd installed it and found out, Oh, look what I've been missing.
>> Right. It's like, uh, if you just decided to look through the logs one day, or you're looking through for a different reason and you see a bunch of exceptions in there and you're like, wait, wait, hold on. I'm not seeing these in our exception reporting service. Like we use Sentry, other people use, you know, like Honeybadger or Rollbar or whatever. Um, You, you want all of your exceptions to end up there because if nothing is in there, then you assume everything's going well. But if you're spawning a thread and then crashing, then, well, bad things are happening and you don't know about it.
>> Right, right. I feel like, you know, obviously we've, we've had our fun with the async exception stuff that we previously touched on here at ITProTV. The other thing I was thinking that, you know, we don't. Losing my train of thought. That's what happens when I decided to podcast and I'm a little tired. yeah, no, I'm really, I really lost it. I guess it's energy drink really didn't do much for me.
>> Not enough energy in that drink. Um, I'll, I'll try to connect the dots. So on our last episode of the podcast, which, uh, we're, we're trying to live up to our name of doing weekly shows, but you know, every other week, every other other week, it's good enough. Um, Last time around, we were talking about async exceptions and those are exceptions that another thread throws into your thread or are thrown from pure code that doesn't look like it should throw exceptions. And you still can use an exception handler to deal with those, but you need to make sure that the exception handler is installed in a place where it is actually wrapping around, uh, where that exception will be thrown to. Um, and so that's tricky and that's what the async library takes care of for you. So there is a little bit overlap with what we're talking about this week and what we talked about last time, but they're kind of orthogonal concerns.
>> Like I said, brother from another mother.
>> Exactly. Yeah, well said. Um, so yeah, I guess, uh, I should dig into what is set uncaught exception handler and how do you use it? Um, and for that. So like I said, it's very well named, uh, even though it doesn't have documentation, it says exactly what it's going to do. If there is an exception that is uncaught, this is the thing that'll be called, regardless of which thread, um, it was thrown from. And the exception handler you install, uh, takes as its argument, some exception, which is or wrapper, huh? Oh, no way. Yeah. Who knew? Some exception, which is a wrapper around any exception. Uh, and then you just return IO. So the default is to print out the exception using the show instance, which is a little surprising. And in fact, it's the only reason I found out about this. Was, uh, someone from Serokell wrote a blog post about changing the default exception handler to use the display exception method rather than show, because a lot of exception types use a derived show instance, but then have a fancy display exception. So why not use that? And it turns out you can use the same mechanism, uh, to like send the exception report to Sentry, like I mentioned, or if you want to like, you know, throw it into a database or, you know, literally do anything with it.
>> You've got options.
>> Cause we're in the IO monad, which is pretty helpful.
>> Yeah, so yeah, IO, you can do anything. And I had some questions when I was first looking at this, because if you're handling an uncaught exception, clearly something has gone wrong and your program is probably about to crash anyway. So I was curious, like, What happens if an exception gets thrown from within your exception handler? Or if you take 30 minutes to handle it? Or you, you know, go into an infinite loop? Something like that. Um, and the answer to basically all the, all of those questions is: it behaves pretty much like normal code. Um, if you throw an exception from your uncaught exception handler, it will then be caught by your uncaught exception handler. So it's pretty easy to get in a loop there. And I would strongly suggest not throwing exceptions from your exception handler,
>> Seems like a reasonable thing. I mean, if you throw it from the exception handler, who's going to handle it. Right? I mean, that's where it's supposed to end. It's not supposed to
>> continually cycle and loop.
>> Um, so that being said, it's not always practical to avoid throwing exceptions from your exception handler, especially if you want to report to an external service. What if that service is down, you might throw an exception. Um, so there's actually a different function called get uncaught exception handler. And so you can use that to grab the original handler or the one that was there before you want to change it. And then you install yours, but you wrap yours in the original handler. So there's something goes wrong with your custom one. You fall way back to the original default one and just use that. So that's what I would suggest doing. Um, if you're, if you're looking at doing this, uh, I have that suggestion in the post. You want to like grab the original handler and then set the new one and handle exceptions in the new one with the old one?
>> Yep, awesome. Yeah. Well, I think, is there any, like, you've already kind of mentioned, don't fail within your exception handler. It could be bad. We have some thunder here in beautiful Florida. So if you hear that in the rumble of the background, um, yeah, it's what it is. It's not uh Taylor's belly, even though some may say it is. Uh, all right guys. Well, I think really Taylor that's a good synopsis of what is covered here. Is there anything else that you want to touch on?
>> I think that's it. Uh, I feel a little weird devoting a whole podcast to this because it's such a short topic. Really, it gets down to there's this function that exists. And if you're writing an application, you should probably call it, uh, so that if you do have an uncaught exception, you'll know about it. Um, but again, I felt motivated to write a blog post for it because I hadn't heard of it before. And it's not very well-documented. So I just wanted to get the word out there. So if you're listening to this and you write a Haskell application, Check out the uncaught exception handler and install it into your app.
>> Yep. And who knows? It could help you figure out, Oh, there's some, some sort of issue you haven't seen yet you haven't been aware of. So, um, yeah, I think it's a great little just, Hey, here it is, tada, kind of thing.
>> And for those who are interested. Dive in deeper and see how that could help you in your daily work or if your side projects, whatever you, however you use Haskell. Um, or if you have a desire to use Haskell, you can just create a little simple app that installs a default exception handler. Why not,
>> Yeah. Uh, and please, uh, don't complain to me if you find out that your code was actually failing all along and you didn't know about it. That's, uh, that's your problem? Not mine.
>> A little harsh, but okay. Okay.
>> No, I.
>> have the, you forgot to catch the exception. So.
>> true. That's true. Um, but yeah, I think, uh, that will do it for us this week. Uh, thank you for listening to the Haskell Weekly podcast. I have been your host Taylor Fausak, and with me today was Cameron Gera. If you want to find out more about us, please check out our website, HaskellWeekly.News. If you liked the show. Please rate and review us wherever you found us. And if you have any feedback for us, you can tweet us @HaskellWeekly.
>> And we are brought to you by our employer. ITProTV an ACI learning company. They would love to extend an offer of 30% off the lifetime of your subscription on our platform. By using the promo code HaskellWeekly30 at checkout, all one word, it is cap sensitive. So a capital H capital W and everything else, lowercase. And I think that about does it for us. Thank you all for joining and we'll see you next week on the Haskell Weekly podcast