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 #0 onInvalidMemoryOperationError ( _param_0=0x6446a0 <gc.gc.GC.mutexStorage()+16>) at b.d:7 #1 0x0000000000425dfc in gc.gc.GC.malloc() () #2 0x0000000000421525 in gc_qalloc () #3 0x000000000041e8e1 in _d_newitemT () #4 0x000000000041d0b3 in b.Fail.__dtor() (this=0x7ffff7eb8000) at b.d:2 #5 0x0000000000435878 in rt_finalize2 () #6 0x000000000042d016 in rt_finalizeFromGC () #7 0x0000000000429930 in gc.gc.Gcx.sweep() () #8 0x000000000042a04b in gc.gc.Gcx.fullcollect() () #9 0x000000000042bed9 in gc_term () #10 0x0000000000421a9b in rt_term () #11 0x000000000041e73e in rt.dmain2._d_run_main() () #12 0x000000000041e6ca in rt.dmain2._d_run_main() () #13 0x000000000041e644 in _d_run_main () #14 0x000000000041d115 in main () Line #4 there is the winner! b.d, line 2, my destructor 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: #0 0x0000000000629bdf make_vector_type (gdb) #1 0x0000000000670a18 read_type_die (gdb) #2 0x000000000066ee87 lookup_die_type (gdb) #3 0x000000000067002a read_type_die (gdb) #4 0x000000000066ee87 lookup_die_type (gdb) #5 0x0000000000672aeb new_symbol_full (gdb) #6 0x0000000000674e1f process_die (gdb) #7 0x0000000000674a2b process_die (gdb) #8 0x0000000000675031 process_die (gdb) #9 0x0000000000678e57 dw2_do_instantiate_symtab (gdb) #10 0x0000000000679f38 dwarf2_read_symtab (gdb) #11 0x00000000005e4451 psymtab_to_symtab (gdb) #12 0x00000000005e54d3 find_pc_sect_symtab_from_partial (g #13 0x00000000005e0143 find_pc_sect_symtab (gdb) #14 0x00000000005dc3dd blockvector_for_pc_sect (gdb) #15 0x00000000005dc57d block_for_pc (gdb) #16 0x00000000006e70bb inline_frame_sniffer (gdb) #17 0x00000000006e52c6 frame_unwind_try_unwinder (gdb) #18 0x00000000006e567f frame_unwind_find_by_frame (gdb) #19 0x00000000006e1d2b get_prev_frame_if_no_cycle (gdb) #20 0x00000000006e4029 get_prev_frame_always (gdb) #21 0x00000000006e4761 get_prev_frame (gdb) #22 0x00000000006e4a3c unwind_to_current_frame (gdb) #23 0x0000000000610b71 catch_exceptions_with_msg (gdb) #24 0x00000000006e1e40 get_current_frame (gdb) #25 0x0000000000603909 handle_inferior_event.part.32 (gdb) #26 0x00000000006055ee fetch_inferior_event (gdb) #27 0x000000000061c7f2 inferior_event_handler (gdb) #28 0x000000000061a7d1 process_event (gdb) #29 0x000000000061abca gdb_do_one_event (gdb) #30 0x000000000061ae3e start_event_loop (gdb) #31 0x0000000000613c13 captured_command_loop (gdb) #32 0x0000000000610d3a catch_errors (gdb) #33 0x0000000000615526 captured_main (gdb) #34 0x0000000000610d3a catch_errors (gdb) #35 0x000000000061568b gdb_main (gdb) #36 0x00000000004604a5 main (gdb) #37 0x00007f3d3893ffe0 __libc_start_main (libc.so.6) #38 0x00000000004604e8 _start (gdb)
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 #0 0x0000000000ccd8f7 in ?? () #1 0x0000000000000000 in ?? () (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 #0 0x00007ffff79c9f1d in __lll_lock_wait () from /lib64/libpthread.so.0 #1 0x00007ffff79c4906 in pthread_mutex_lock () from /lib64/libpthread.so.0 #2 0x0000000000dbccb1 in ?? () #3 0x000000000119c1b0 in ?? () #4 0x00007fffffffc470 in ?? () #5 0x0000000000000000 in ?? () (gdb) q A debugging session is active. [root localhost build]# addr2line -e __test__full__ 0x0ccd8f7 /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