diff options
author | Ben Gamari <ben@smart-cactus.org> | 2022-05-03 15:29:04 -0400 |
---|---|---|
committer | Marge Bot <ben+marge-bot@smart-cactus.org> | 2022-05-19 04:57:51 -0400 |
commit | 372cf7305157df7bbe41d2d1f45211d9b999ff52 (patch) | |
tree | 51b84bf3f4770b30a44ca86c30530b6af7d4e3e7 /libraries | |
parent | cfc8e2e2e3c9d9044f8f4d100c102b005695905f (diff) | |
download | haskell-372cf7305157df7bbe41d2d1f45211d9b999ff52.tar.gz |
base: Throw exceptions raised while closing finalized Handles
Fixes #21336.
Diffstat (limited to 'libraries')
-rw-r--r-- | libraries/base/GHC/IO/Handle/Internals.hs | 34 | ||||
-rw-r--r-- | libraries/base/GHC/TopHandler.hs | 31 | ||||
-rw-r--r-- | libraries/base/GHC/Weak/Finalize.hs | 2 |
3 files changed, 60 insertions, 7 deletions
diff --git a/libraries/base/GHC/IO/Handle/Internals.hs b/libraries/base/GHC/IO/Handle/Internals.hs index cbd43c1666..cd1f6a35ef 100644 --- a/libraries/base/GHC/IO/Handle/Internals.hs +++ b/libraries/base/GHC/IO/Handle/Internals.hs @@ -440,9 +440,39 @@ recoveringEncode codec from to = go from to handleFinalizer :: FilePath -> MVar Handle__ -> IO () handleFinalizer fp m = do handle_ <- takeMVar m - (handle_', _) <- hClose_help handle_ + (handle_', mb_exc) <- hClose_help handle_ putMVar m handle_' - return () + case mb_exc of + Just exc -> throwIO exc + Nothing -> return () + +{- + Note [Handling exceptions during Handle finalization] + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Handles which become unreachable are flushed closed automatically by the + garbage collector, which calls 'GHC.IO.Handle.Internals.handleFinalizer'. + However, numerous things can go wrong during this process. For instance, + while we are flushing we may find that the handle's device is full. What to + do in this case? + + For a long time we would simply ignore the failure. However, silently + ignoring failures is rarely a good option. For this reason the + 'GHC.Weak.Finalizer.runFinalizerBatch' now catches exceptions and + dispatches them to a notification action (which can be set via + 'GHC.Weak.Finalize.setFinalizerExceptionHandler'). + + This then poses the question of what happens if the exception notification + action itself throws an exception. We currently ignore such second-order + exceptions. + + Note that stdout/stderr are handled a bit differently, since they are never + finalized by the GC. Instead, 'GHC.TopHandler.flushStdHandles' explicitly + catches exceptions from hFlush and dispatches them to the usual Weak + finalization exception notifier. + + See #21336. + + -} -- --------------------------------------------------------------------------- -- Allocating buffers diff --git a/libraries/base/GHC/TopHandler.hs b/libraries/base/GHC/TopHandler.hs index 6a4e0325a6..704e521e18 100644 --- a/libraries/base/GHC/TopHandler.hs +++ b/libraries/base/GHC/TopHandler.hs @@ -81,6 +81,7 @@ runMainIO main = do main_thread_id <- myThreadId weak_tid <- mkWeakThreadId main_thread_id + setFinalizerExceptionHandler handleFinalizerException case weak_tid of (Weak w) -> setMainThread w install_interrupt_handler $ do m <- deRefWeak weak_tid @@ -226,13 +227,33 @@ calling the RTS, without iconv at all. -} --- try to flush stdout/stderr, but don't worry if we fail --- (these handles might have errors, and we don't want to go into --- an infinite loop). +-- try to flush stdout/stderr. flushStdHandles :: IO () flushStdHandles = do - hFlush stdout `catchAny` \_ -> return () - hFlush stderr `catchAny` \_ -> return () + hFlush stdout `catchException` handleExc + -- In the event that we fail to flush stderr the default finalizer exception + -- handler (which prints to stderr) will also likely fail. However, we call it + -- anyways since the user may have set their own handler. + hFlush stderr `catchException` handleExc + where + -- We dispatch exceptions thrown by hFlush to the same action used to + -- handle Weak finalizers since this is where "normal" Handles (e.g. not + -- stderr/stdout) would be flushed. + -- + -- See Note [Handling exceptions during Handle finalization] in + -- GHC.IO.Handle.Internals + handleExc se = do + handleFinalizerExc <- getFinalizerExceptionHandler + -- Swallow any exceptions thrown by the finalizer exception handler + handleFinalizerExc se `catchException` (\(SomeException _) -> return ()) + +-- | See Note [Handling exceptions during Handle finalization] in +-- GHC.IO.Handle.Internals +handleFinalizerException :: SomeException -> IO () +handleFinalizerException se = + hPutStr stderr msg `catchException` (\(SomeException _) -> return ()) + where + msg = "Exception during Weak# finalization (ignored): " ++ displayException se ++ "\n" safeExit, fastExit :: Int -> IO a safeExit = exitHelper useSafeExit diff --git a/libraries/base/GHC/Weak/Finalize.hs b/libraries/base/GHC/Weak/Finalize.hs index 09308fb3d3..d16277248b 100644 --- a/libraries/base/GHC/Weak/Finalize.hs +++ b/libraries/base/GHC/Weak/Finalize.hs @@ -49,6 +49,8 @@ runFinalizerBatch (I# n) arr = handleFinalizerExc <- getFinalizerExceptionHandler handleFinalizerExc se `catchException` (\(SomeException _) -> return ()) +-- See Note [Handling exceptions during Handle finalization] for the +-- motivation for this mechanism. finalizerExceptionHandler :: IORef (SomeException -> IO ()) finalizerExceptionHandler = unsafePerformIO $ newIORef (const $ return ()) {-# NOINLINE finalizerExceptionHandler #-} |