Bug 23555 – Throwing an Error in a destructor hangs on a collection
Status
RESOLVED
Resolution
DUPLICATE
Severity
enhancement
Priority
P4
Component
druntime
Product
D
Version
D2
Platform
x86
OS
Mac OS X
Creation time
2022-12-14T06:01:04Z
Last change time
2022-12-18T01:17:16Z
Assigned to
No Owner
Creator
Steven Schveighoffer
Comments
Comment #0 by schveiguy — 2022-12-14T06:01:04Z
If you throw an error in a destructor, the GC will hang if it tries to run that destructor.
```d
class C
{
~this()
{
assert(false, "bad");
}
}
void main()
{
new C;
}
```
If compiled in non-release mode, this will hang.
What happens is the assert is thrown and *not* caught by the code in druntime that prevents exceptions from getting to the GC runFinalizers routine. If you change the assert to a throw of a staticaally-allocated exception, you instead get a nice error message and exit of the program.
The "offending" PR is here: https://github.com/dlang/druntime/pull/1447
The issue is that there is code such as: https://github.com/dlang/druntime/pull/1447/files#diff-ded8b221f7392e8cfea15564dc107aa3007a832a7debadc3755559c4121f799eR2446-R2452
What happens? The destructor throws the assert error.
Upon throwing, the defaultTraceHandler function here: https://github.com/dlang/dmd/blob/fabd06214ef699279bdbb83a13bd8d1be34e2e34/druntime/src/core/runtime.d#L676
will avoid allocating because it's in a finalizer. Great, this avoids the deadlock.
But once it reaches the `scope(failure)` clause, it's caught, and rethrown *after the inFinalizer flag is turned off*. This means that it now will attempt to allocate a traceinfo, which tries to take the lock a second time, and then enters a deadlock state.
The ultimate solution is that defaultTraceinfoHandler should not use the GC. In all honesty, the allocated data for the traceinfo can easily be obtained using C malloc calls. This is the real solution.
But the scope failure clause should be remedied as well. If you are going to unset the flag, you should unlock the lock as well. If you aren't going to unlock the lock, the unsetting of the flag should probably come later (or not at all, this particular scope(failure) can't possibly happen with an Exception, as the function is nothrow. I haven't thought through the correct remedy.
The code is currently here: https://github.com/dlang/dmd/blob/020685c85b4fde7d50511716dc98dfc5dc97ef2b/druntime/src/core/internal/gc/impl/conservative/gc.d#L3137
There are other scope(failure) calls in that file that might need attention.
Comment #1 by b2.temp — 2022-12-14T15:55:15Z
would it be possible to fix that in DMD ? e.g always use a trap instruction (like checkaction C) if `assert()` is located in a dtor ?
Comment #2 by schveiguy — 2022-12-18T01:17:16Z
*** This issue has been marked as a duplicate of issue 22616 ***