[GHC] #12096: Attach stacktrace information to SomeException
GHC
ghc-devs at haskell.org
Tue May 24 10:59:18 UTC 2016
#12096: Attach stacktrace information to SomeException
-------------------------------------+-------------------------------------
Reporter: ndtimofeev | Owner:
Type: feature request | Status: new
Priority: normal | Milestone:
Component: Core Libraries | Version: 8.0.1
Resolution: | Keywords:
Operating System: Unknown/Multiple | Architecture:
| Unknown/Multiple
Type of failure: None/Unknown | Test Case:
Blocked By: | Blocking:
Related Tickets: | Differential Rev(s):
Wiki Page: |
-------------------------------------+-------------------------------------
Comment (by ndtimofeev):
1. I just copy `ErrorCall` API and implementation. Also I would like to
see both stacktrace (`CallStack` and `CostCentreStack`, not only
`CallStack`).
2. Yep, stacktrace information is lost when we catch unwraped exception.
It looks like a problem. First:
{{{#!hs
f = throw (CustomException False)
g = f `catch` \err@(CustomException fixable) ->
if fixable
then makeGood
else throw err
}}}
We only rethrow exception and… change it stacktrace. Now it start from
`throw` in `g`, not in `f`.
Second:
{{{#!hs
onException eval handler = eval `catch` (\e@(SomeException _) -> handler
>> throw e)
f = throw (CustomException False)
g = f `onException` makeGood
}}}
Now exception has to stacktrace.
Third:
{{{#!hs
loop = forever $ threadDelay maxBound
main = do
tid <- forkIO $ loop `onException` putStrLn "Bang!"
threadDelay 1000000
throwTo tid UserException
}}}
Now `UserException` has absolutely irrelevant stacktrace.
I don't know how fix first problem. The second can be fixed something like
that:
{{{#!hs
throw :: (HasCallStack, Exception e) => e -> a
throw e
| Just (SomeException _) <- cast e = raise# e
| otherwise = unsafeDupablePerformIO $ do
stack <- currentCallStack
raise# (CallStackException e $ if stack /= []
then prettyCallStack ?callStack ++ "\n" ++ renderStack stack
else prettyCallStack ?callStack)
}}}
The third is more complicated. For example we can skip stacktrace
information for asynchronous exceptions. But in general, we can't
determine this exception synchronous or asynchronous. Perhaps `throwTo`
can add to exception extra information. But I do not understand how.
{{{#!hs
throwTo' :: Exception e => ThreadId -> e -> IO a
throwTo' tid = throwTo tid . SomeAsyncException
catch' :: Exception e => IO a -> (e -> IO a) -> IO a
catch' eval handler = eval `catch` \err@(SomeException _) -> go err
handler err
where
go :: (Exception e, Exception a) => e -> (a -> IO b) ->
SomeException -> IO b
go ex f origErr
| Just v <- cast ex = f v
| Just (SomeException inner) <- cast ex = go inner f
origErr
| Just (SomeAsyncException inner) <- cast ex = go inner f
origErr
| otherwise = throw origErr
}}}
Also it will be useful in situation like that:
{{{#!hs
processCmd = timeout 20000 . postDataAndWaitResponce
main = do
tasks <- newChan :: IO (Chan (String, MVar (Either SomeEception (Maybe
String))))
tid <- forkIO $ forever $ do
(cmd, ret) <- readChan tasks
try (processCmd cmd) >>= putMVar ret
threadDelay 1000000
throwTo tid UserException
}}}
`UserException` can't kill forked thread because it try catch all
(synchronous) exception. If `try (processCmd cmd) >>= putMVar ret` will be
masked `timeout` will be broken.
--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/12096#comment:3>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the ghc-tickets
mailing list