I hate class destructors with a burning passion

Mathias LANG geod24 at gmail.com
Sun Jun 6 11:54:40 UTC 2021


Over the past few months, more than half of the critical bug 
we've encountered which were not due to our own code are related 
to destructors. Specifically, destructors being invoked by the GC.

In case you need a refresher, allocating from a destructor that 
is called from the GC leads to an `InvalidMemoryOperationError`. 
Which is pretty bad, because you don't even know *WHERE* the 
allocation happen, because `InvalidMemoryOperation` [does not 
give you a stack 
trace](https://github.com/dlang/druntime/blob/5c1873c7d95510f7e922f49ab46d815203f25471/src/core/exception.d#L279). Why ? Because generating a stack trace allocates. This also mean you can't debug *other* failures in destructors, such as when an `assert` fails (see below).

(Stack trace allocating can be fixed BTW, but requires a breaking 
change, because the `interface` we use provides you with 
already-formatted `const(char)[]` instead of structured data, but 
that's another discussion).

When such an `InvalidMemoryOperation` happens on your computer 
and is easy to debug, great. But when it happens once every blue 
moon on the production server... Not so great.

So what are the critical bugs we've seen over the last 6 months ?

### `AA.remove` allocate(d)

We use Vibe.d extensively in our server app, and Vibe.d tries to 
clean after itself.
I think it's a reasonable assumption that, if you have an 
associative array, you should be able to call `remove` on it from 
a destructor, provided you can ensure it hasn't been collected.
Except, before v2.095.0, you couldn't, because `remove` might 
have called `shrink`, which allocates. [This bug has been fixed 
now](https://issues.dlang.org/show_bug.cgi?id=21442).

### `assert` failure allocate(d)

I'm not talking about message formatting here, but pure `assert(a 
!= a)` allocating.
This bug has been known for [almost a 
decade](https://issues.dlang.org/show_bug.cgi?id=7349) and was 
finally fixed in v2.097.0 ([it wasn't that 
hard](https://github.com/dlang/druntime/pull/3476))

### `throw`ing from the constructor of GC-allocated `class` is 
broken since 2.096.0

Why did we have an assertion failure in our destructor ?
Well turns out there was [an `assert` checking a 
magic](https://github.com/vibe-d/vibe-core/issues/283), and it 
was being triggered. Why ? Because of a [regression introduced in 
v2.096.0](https://issues.dlang.org/show_bug.cgi?id=21989).


This is the top 3 that we could track down. We currently have a 
random crash which seems to trigger when a C++ object is 
destructed by the GC (to be precise: the GC finalizes a `class` 
that holds an `std::vector`), but that might just be our 
bindings...


More information about the Digitalmars-d mailing list