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. 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.
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 and was finally fixed in v2.097.0 (it wasn't that hard)
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, and it was being triggered. Why ? Because of a regression introduced in v2.096.0.
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...