www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Throwing InvalidMemoryOperationError

reply "Etienne" <etcimon gmail.com> writes:
What's up with throwing this error?

It's completely unrecoverable when the called through the GC 
finalization process, the GC mutex doesn't unlock and all you get 
is a deadlock.

Why not just let it fail with an access violation of some sort? 
At least you get a clean stack trace from a debugger of choice, 
and you're not stuck figuring out which completely random 
operation occurred.
Jun 04 2015
next sibling parent reply "Etienne Cimon" <etcimon gmail.com> writes:
On another note, considering the unimaginable amount of bugs that 
can stem from throwing in a constructor or destructor, I don't 
see why D shouldn't just enforce a nothrow on them.
Jun 04 2015
parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Thursday, 4 June 2015 at 16:12:54 UTC, Etienne Cimon wrote:
 On another note, considering the unimaginable amount of bugs 
 that can stem from throwing in a constructor
Throwing from a constructor is kinda important as it is the only way to signal failure on its input...
Jun 04 2015
parent reply "Etienne Cimon" <etcimon gmail.com> writes:
On Thursday, 4 June 2015 at 16:20:28 UTC, Adam D. Ruppe wrote:
 On Thursday, 4 June 2015 at 16:12:54 UTC, Etienne Cimon wrote:
 On another note, considering the unimaginable amount of bugs 
 that can stem from throwing in a constructor
Throwing from a constructor is kinda important as it is the only way to signal failure on its input...
Wouldn't that be with `this() in { assert() }` ? My concern is the fact that the destructor won't be called. Or will it?
Jun 04 2015
parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Thursday, 4 June 2015 at 16:32:39 UTC, Etienne Cimon wrote:
 Wouldn't that be with `this() in { assert() }` ?
Not necessarily. Consider something like a file wrapper, if fopen is null inside the ctor, you'd generally throw on that.
 My concern is the fact that the destructor won't be called. Or 
 will it?
It won't be for deterministic objects (structs on the stack) but is for GC'd objects (eventually). I don't think it should be called since if the constructor fails, the object doesn't really exist and there's nothing to destroy. You can reliably clean up intermediate things in a constructor using scope(failure): struct Foo { FILE* file, file2; this(something somename) { file = fopen(somename); if(file is null) throw FileException(somename); scope(failure) { fclose(file); file = null; } file2 = fopen(somename2); if(file2 is null) throw FileException(somename2); } ~this() { if(file !is null) fclose(file); if(file2 !is null) fclose(file2); } } That'd work whether the destructor is called automatically or not and isn't too hard to write since scope(failure) is pretty convenient.
Jun 04 2015
parent "Etienne Cimon" <etcimon gmail.com> writes:
On Thursday, 4 June 2015 at 16:49:07 UTC, Adam D. Ruppe wrote:
 On Thursday, 4 June 2015 at 16:32:39 UTC, Etienne Cimon wrote:
 Wouldn't that be with `this() in { assert() }` ?
Not necessarily. Consider something like a file wrapper, if fopen is null inside the ctor, you'd generally throw on that.
 My concern is the fact that the destructor won't be called. Or 
 will it?
It won't be for deterministic objects (structs on the stack) but is for GC'd objects (eventually). I don't think it should be called since if the constructor fails, the object doesn't really exist and there's nothing to destroy. You can reliably clean up intermediate things in a constructor using scope(failure): struct Foo { FILE* file, file2; this(something somename) { file = fopen(somename); if(file is null) throw FileException(somename); scope(failure) { fclose(file); file = null; } file2 = fopen(somename2); if(file2 is null) throw FileException(somename2); } ~this() { if(file !is null) fclose(file); if(file2 !is null) fclose(file2); } } That'd work whether the destructor is called automatically or not and isn't too hard to write since scope(failure) is pretty convenient.
Nice, I'll try and use that once I find the reason I get this error: https://travis-ci.org/etcimon/botan/jobs/65410185#L426 Somewhere random in a 100k line code base, a deadlock is triggered in the GC by some object's destructor. :/
Jun 04 2015
prev sibling parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Thursday, 4 June 2015 at 16:05:37 UTC, Etienne wrote:
 Why not just let it fail with an access violation of some sort?
You can do this at least with a filthy hack. Add this to your file with main(): extern(C) void onInvalidMemoryOperationError(void*) { asm { int 3; } } Then recompile your program. class Fail { ~this() { auto a = new int; } } /* extern(C) void onInvalidMemoryOperationError(void*) { asm { int 3; } } */ void main() { auto fail = new Fail(); } Before: core.exception.InvalidMemoryOperationError (0) After: Trace/breakpoint trap `int 3;` is x86/x86_64 talk for "please invoke the debugger" and the extern(C) onInvalidMemoryOperationError is what the GC calls to throw that exception. Since the function is in the druntime.lib file though.... you can easily override it by simply providing a replacement file in your main application. The linker prefers versions of functions from .o files over .lib files. (This also works on Windows btw, I'm just using the linux terms cuz that's what I'm on right now) So now the magic comes if we run the program in gdb: $ dmd b.d -g $ gdb b (gdb) r Program received signal SIGTRAP, Trace/breakpoint trap. 0x000000000041d0c5 in onInvalidMemoryOperationError () => 0x000000000041d0c5 <onInvalidMemoryOperationError+13>: c9 leave (gdb) where _param_0=0x6446a0 <gc.gc.GC.mutexStorage()+16>) at b.d:7 at b.d:2 allocation :)
Jun 04 2015
parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
If int 3 doesn't work for some reason btw, you could always just 
deliberately write to a null pointer and trigger a segfault in 
the overridden function, would have the same result in the 
debugger.

I feel like this onError thing is meant to be overridable by 
importing core.exception too, but I don't see that in the source. 
The linker-based override definately works today though!
Jun 04 2015
parent reply "Etienne Cimon" <etcimon gmail.com> writes:
On Thursday, 4 June 2015 at 17:43:35 UTC, Adam D. Ruppe wrote:
 If int 3 doesn't work for some reason btw, you could always 
 just deliberately write to a null pointer and trigger a 
 segfault in the overridden function, would have the same result 
 in the debugger.

 I feel like this onError thing is meant to be overridable by 
 importing core.exception too, but I don't see that in the 
 source. The linker-based override definately works today though!
So far I've tried the null pointer to get a segmentation fault. It failed. I'm trying to rebuild gdb because this error is what I got: Message: Process 61701 (gdb) of user 0 dumped core. Stack trace of thread 61701: (gdb) find_pc_sect_symtab_from_partial (g (gdb) (gdb) (gdb) (gdb) (gdb) (gdb) handle_inferior_event.part.32 (gdb) (gdb) (libc.so.6)
Jun 04 2015
parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Thursday, 4 June 2015 at 17:51:31 UTC, Etienne Cimon wrote:
 I'm trying to rebuild gdb because this error is what I got:
wow that's messed up. Did you try it with dmd -gc too? Or a non-debug version of the program entirely? Maybe your version of gdb has a bug in reading D debugging info. With a non-debug, you won't get line numbers in the stack trace, but the mangled function name should still really narrow down your search. (there's a ddemangle program that comes with dmd that can translate it or reading by eyeball isn't bad either, should see your class name in there)
Jun 04 2015
next sibling parent "Etienne Cimon" <etcimon gmail.com> writes:
On Thursday, 4 June 2015 at 17:58:34 UTC, Adam D. Ruppe wrote:
 On Thursday, 4 June 2015 at 17:51:31 UTC, Etienne Cimon wrote:
 I'm trying to rebuild gdb because this error is what I got:
wow that's messed up. Did you try it with dmd -gc too? Or a non-debug version of the program entirely? Maybe your version of gdb has a bug in reading D debugging info. With a non-debug, you won't get line numbers in the stack trace, but the mangled function name should still really narrow down your search. (there's a ddemangle program that comes with dmd that can translate it or reading by eyeball isn't bad either, should see your class name in there)
Yeah, obviously I had to use exec-file to avoid symbols because dub test compiles with symbols. It took some time to remember but this is basically how I proceeded to debug the whole botan library a few months ago. Using addr2line or a backtrace library. It's nice not having to do this for all my projects, but having to follow all these steps is obviously unfriendly for a state-of-the-art language like D. Here's what I get from the `asm { int 3; }` type of breakpoint: Program received signal SIGUSR1, User defined signal 1. 0x0000000000ccd8f7 in ?? () (gdb) bt (gdb) c Continuing. Program received signal SIGUSR2, User defined signal 2. 0x00007ffff7122cc7 in sigsuspend () from /lib64/libc.so.6 (gdb) Continuing. D: Error: Invalid memory operation [Thread 0x7ffff6c6c700 (LWP 67283) exited] ^C Program received signal SIGINT, Interrupt. 0x00007ffff79c9f1d in __lll_lock_wait () from /lib64/libpthread.so.0 (gdb) bt /lib64/libpthread.so.0 /lib64/libpthread.so.0 (gdb) q A debugging session is active. /home/devpriv/botan/source/botan/math/numbertheory/numthry.d:744
Jun 04 2015
prev sibling parent reply "Etienne Cimon" <etcimon gmail.com> writes:
Well, I think the error is that the GC is not using the TLS 
matching the corresponding object's destructors. Could this be 
possible?
Jun 04 2015
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 6/4/15 3:02 PM, Etienne Cimon wrote:
 Well, I think the error is that the GC is not using the TLS matching the
 corresponding object's destructors. Could this be possible?
Possible and likely :) GC destruction can run in any thread, there is no guarantee they are run in the same thread. -Steve
Jun 04 2015