
So I looked at this some more, and determined a few things: - Ctrl-C did not do anything in the original program because the main thread was blocked on a socket accept() call, which seemingly could not be interrupted for an asynchronous exception. - I fixed this by having the main thread initialize everything, then running the main body of code in an async and waiting on that. Apparently this wait is interruptible, so Ctrl-C works, bracket nicely cleans up, and the application exits. Good! - Then I added a console handler to capture 'close' events. Here is the code for the handler: f mainThreadID event = case event of ControlC -> putStrLn "HANDLER" >> throwTo mainThreadID UserInterrupt Break -> putStrLn "HANDLER" >> throwTo mainThreadID UserInterrupt Close -> putStrLn "HANDLER" >> throwTo mainThreadID ThreadKilled >> threadDelay maxBound _ -> pure () Where the 'threadDelay' call serves simply to give the main thread time to exit. This works fine for ControlC and Break, but fails for Close: the program exists immediately, and no cleanup is run. So I still haven't solved how to clean up when the window close button is hit. Any thoughts? Thanks Peter