在 Haskell 中处理 UserInterrupt 异常
我正在Haskell中为Scheme解释器实现REPL,我想处理一些异步事件,如UserInterrupt、StackOverflow、HeapOverflow等...基本上,我想在UserInterrupt发生时停止当前计算并打印当发生 StackOverflow 和 HeapOverflow 等时,会显示合适的消息。我的实现如下:
repl evaluator = forever $ (do
putStr ">>> " >> hFlush stdout
out <- getLine >>= evaluator
if null out
then return ()
else putStrLn out)
`catch`
onUserInterrupt
onUserInterrupt UserInterrupt = putStrLn "\nUserInterruption"
onUserInterrupt e = throw e
main = do
interpreter <- getMyLispInterpreter
handle onAbort (repl $ interpreter "stdin")
putStrLn "Exiting..."
onAbort e = do
let x = show (e :: SomeException)
putStrLn $ "\nAborted: " ++ x
它按预期工作,但有一个例外。如果我启动解释器并按 Ctrl-Z + Enter,我会得到:
>>> ^Z
Aborted: <stdin>: hGetLine: end of file
Exiting...
这是正确的。但是,如果我启动解释器并按 Ctrl-C,然后按 Ctrl-Z + Enter,我会得到:
>>>
UserInterruption
>>> ^Z
它挂起,我无法再使用解释器。但是,如果我再次按 Ctrl-C,REPL 就会解除阻止。我搜索了很多,但无法弄清楚其中的原因。谁能给我解释一下吗?
非常感谢!
I'm implementing a REPL for a Scheme interpreter in Haskell and I'd like to handle some async events like UserInterrupt, StackOverflow, HeapOverflow, etc... Basically, I'd like to stop the current computation when UserInterrupt occurs and print a suitable message when StackOverflow and HeapOverflow occur, etc. I implemented this as follows:
repl evaluator = forever $ (do
putStr ">>> " >> hFlush stdout
out <- getLine >>= evaluator
if null out
then return ()
else putStrLn out)
`catch`
onUserInterrupt
onUserInterrupt UserInterrupt = putStrLn "\nUserInterruption"
onUserInterrupt e = throw e
main = do
interpreter <- getMyLispInterpreter
handle onAbort (repl $ interpreter "stdin")
putStrLn "Exiting..."
onAbort e = do
let x = show (e :: SomeException)
putStrLn $ "\nAborted: " ++ x
It works as expected with one exception. If I start the interpreter and press Ctrl-Z + Enter, I get:
>>> ^Z
Aborted: <stdin>: hGetLine: end of file
Exiting...
That's correct. But if I start the interpreter and press Ctrl-C followed by Ctrl-Z + Enter I get:
>>>
UserInterruption
>>> ^Z
And it hangs and I can't use the interpreter anymore. However, if I press Ctrl-C again, the REPL unblocks. I searched a lot and I can't figure out the reason of it. Can anyone explain me?
Many thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
Control-C 处理不适用于
catch
:可能与 GHC #2301:正确处理 SIGINT/SIGQUIT这是一个工作测试用例,删除了
评估器
:在 Linux 上,Control-Z 不会像 Sjoerd 提到的那样被捕获。也许您使用的是 Windows,其中 Control-Z 用于 EOF。我们可以使用 Control-D 在 Linux 上发出 EOF 信号,这会复制您所看到的行为:
EOF 由您的
handle/onAbort
函数处理,Control-C 由catch/onUserInterrupt
处理代码>.这里的问题是您的repl
函数只会捕获第一个 Control-C - 可以通过删除handle/onAbort
函数来简化测试用例。如上所述,Control-C 处理不适用于catch
可能与 GHC 有关#2301:正确处理 SIGINT/SIGQUIT。以下版本使用 Posix API 为 Control-C 安装持久信号处理程序:
它可以处理多次按下 Control-C:
如果不使用 Posix API,在 Windows 上安装持久信号处理程序需要重新引发异常每次捕获时,如 http://suacommunity.com/dictionary/signals.php 中所述
Control-C handling does not work with
catch
: may be related to GHC #2301: Proper handling of SIGINT/SIGQUITHere is a working testcase, with the
evaluator
removed:On Linux, Control-Z is not caught as Sjoerd mentioned. Perhaps you are on Windows, where Control-Z is used for EOF. We can signal EOF on Linux with Control-D, which replicates the behavior you saw:
EOF is handled by your
handle/onAbort
function, and Control-C is handled bycatch/onUserInterrupt
. The issue here is that yourrepl
function will only catch the first Control-C -- the testcase can be simplified by removing thehandle/onAbort
function. As noted above, that Control-C handling does not work withcatch
may be related to GHC #2301: Proper handling of SIGINT/SIGQUIT.The following version instead uses the Posix API to install a persistent signal handler for Control-C:
which can handle Control-Cs being pressed multiple times:
If not using the Posix API, installing a persistent signal handler on Windows requires re-raising the exception each time it is caught, as described in http://suacommunity.com/dictionary/signals.php