Mercury Bugs - mercury
View Issue Details
0000418mercuryBugpublic2016-09-07 13:572016-10-07 14:34
Reporterwangp 
Assigned Towangp 
PrioritynormalSeverityminorReproducibilityalways
StatusresolvedResolutionfixed 
PlatformOSOS Version
Product Version 
Target VersionFixed in Version 
Summary0000418: io error exceptions not working
DescriptionThere is some problem with exceptions not being thrown or caught properly in low-level C grades, as in the attached test case:

./test|no_such_cmd
zsh: command not found: no_such_cmd
Errno = 32: Broken pipe
Mercury runtime: reached not_reached
TagsNo tags attached.
Attached Files? test.m (633) 2016-09-07 13:57
https://bugs.mercurylang.org/file_download.php?file_id=263&type=bug
? throw_from_foreign.m (778) 2016-09-19 17:53
https://bugs.mercurylang.org/file_download.php?file_id=265&type=bug

Notes
(0000912)
wangp   
2016-09-19 17:53   
Nothing to do with io.m.

Note (to self) about throw_from_foreign.m in low-level C grades:

The Mercury procedure dothrow/1 is foreign_exported, so we create a C wrapper function dothrow() which uses MR_call_engine() to enter the Mercury code. MR_call_engine() itself uses setjmp(). When dothrow/1 throws an exception it calls longjmp, but that sends us back to the setjmp point in the MR_call_engine call for the dothrow() wrapper function instead of the exception handler.
(0000913)
wangp   
2016-09-20 11:57   
The previous comment was wrong.

try_io/builtin_catch creates a Mercury exception handler entry on the nondet stack.
Then we enter the foreign proc `chucker',
which calls the C wrapper `dothrow',
which calls the C function `MR_call_engine' (with catch_exceptions=FALSE),
which runs the Mercury proc `dothrow',
which calls `throw'.

throw/builtin_throw finds the Mercury exception handler on the nondet stack. It does NOT longjmp back to the MR_call_engine, but calls the Mercury exception handler. The C stack is not unwound, so some time later we end up returning into MR_call_engine.

Anyone have suggestions for how builtin_throw should unwind the C stack?

One way might be for builtin_catch to call setjmp so that builtin_throw always calls longjmp to unwind the C stack (like high-level C grades).

Another way might for MR_call_engine to always intercept exceptions and rethrow them. e.g. if catch_exceptions=FALSE, it still needs to create an exception handler entry of type MR_C_LONGJMP_HANDLER on the nondet stack, so it can catch any exception and rethrow it.
(0000914)
juliensf   
2016-09-20 12:19   
With regard to throw_from_foreign.m, throwing exceptions across the C interface like that is currently documented as being undefined behaviour, it's not surprising that it doesn't work. Are you suggesting that it needs to work? (I could reproduce the issue with test.m on my machine; which grade are you using?)
(0000915)
wangp   
2016-09-20 12:37   
I couldn't find where it is documented.

io.m does throw exceptions across the C interface. ML_throw_io_error is C wrapper for Mercury proc throw_io_error, and is called indirectly from many foreign_procs.

I think I would not mind if throwing exceptions across the C interface is not supported (with better documentation, and perhaps runtime checks). I think a better way for io.m or any other module to handle errors is to return error codes from foreign_procs up to Mercury code, then for the Mercury code to throw exceptions.

I'm testing with asm_fast.gc and none.gc.
(0000916)
juliensf   
2016-09-20 13:51   
It's documented in the comment at the head of library/exception.m.

The ODBC library does it as well. I think ML_throw_io_error (probably) worked well enough once upon a time, but I suspect that various runtime changes may have contributed to it not working. it would be preferable if we didn't throw exceptions over the C interface (certainly in the standard library at least).

We could insert runtime checks in the foreign_export wrappers, but there are probably performance implications with doings so (such checks could obviously be omitted in the case where we know the Mercury code is not going to throw an exception).

I tried with none.gc.debug.trseg.stseg.
(0000917)
wangp   
2016-09-20 15:58   
It's very easy to write a Mercury predicate that may throw exceptions without meaning to. It would be preferable to support throwing exceptions across the C interface, perhaps even if there is some performance impact. The majority of foreign_exported predicates in extras might fall afoul of the rule.

I will try to fix io.m.
(0000918)
juliensf   
2016-09-20 18:20   
If there are any proposals in this area, then I suggest we discuss them on the developers list.
(0000935)
wangp   
2016-10-07 14:34   
Fixed, io.m does not throw past the C interface any more.

Issue History
2016-09-07 13:57wangpNew Issue
2016-09-07 13:57wangpStatusnew => assigned
2016-09-07 13:57wangpAssigned To => wangp
2016-09-07 13:57wangpFile Added: test.m
2016-09-19 17:53wangpFile Added: throw_from_foreign.m
2016-09-19 17:53wangpNote Added: 0000912
2016-09-20 11:57wangpNote Added: 0000913
2016-09-20 12:19juliensfNote Added: 0000914
2016-09-20 12:37wangpNote Added: 0000915
2016-09-20 13:51juliensfNote Added: 0000916
2016-09-20 15:58wangpNote Added: 0000917
2016-09-20 18:20juliensfNote Added: 0000918
2016-10-07 14:34wangpStatusassigned => resolved
2016-10-07 14:34wangpResolutionopen => fixed
2016-10-07 14:34wangpNote Added: 0000935