Want reasonable reference counting? Disable automatic sharing of immutable

Steven Schveighoffer schveiguy at gmail.com
Sat Nov 13 21:55:21 UTC 2021


On 11/13/21 3:01 PM, Stanislav Blinov wrote:
> On Saturday, 13 November 2021 at 14:34:43 UTC, Steven Schveighoffer wrote:
> 
>> Whether it does this or pushes it off to another thread is incidental. 
>> The running of finalizers is a function of the GC, not the caller.
> 
> :)
> 
>> The part you are not getting is that this is not something being 
>> called by the pure code, it's being run by the GC.
> 
> ```d
> import someLibrary;
> // someLibrary defines a module-global
> // int threadLocal;
> 
> void main()
> {
>      someLibrary.threadLocal = () pure nothrow {
>          return someLibrary.blah(42);
>      } ();
>      auto old = someLibrary.threadLocal;
>      auto someInts = () pure { return new int[1000]; } ();
>      assert(someLibrary.threadLocal == old);
> }
> ```
> 
> That assert may fail. Or you may even crash before getting to it, and 
> not with an `OutOfMemoryError`, but with a `FinalizeError`, depending to 
> the value of `threadLocal`. Or it can be totally fine if the GC doesn't 
> collect. What am I not getting?..

You are not getting that the GC collecting has nothing to do with the 
pure function's executation. The GC hijacks the current thread to do its 
business, and then passes back control to the caller.

> 
>> Imagine it like a context switch to another thread that runs the GC 
>> code, and then switches back to the pure code.
> 
> If I imagine that, the assert above should always hold. Because there 
> should be no way that imaginary "another thread" would access main 
> thread's `threadLocal`. Somehow, reality contradicts imagination.

Actually, the GC can run finalizers from ANY thread. So accessing thread 
locals in a GC finalizer is risky behavior anyway.

>> It's similar to running some kernel code, or signal code -- it's 
>> initiated by a separate entity, in this case the GC.
>>
>>> Unless that changed and GC isn't doing that anymore, that's a bug 
>>> that's been open for some years now.
>>
>> It should be closed as invalid. Which bug is that?
> 
> https://issues.dlang.org/show_bug.cgi?id=19316

Thanks! I closed it.

> Feel free to close it as invalid, if the code above either:
> - dies with an OutOfMemoryError
> - passes the assert

The assert is incorrectly written, as the thread local can change at any 
time if the GC happens to run on the current thread, and happens to be 
finalizing a `Bad` object (even if it was allocated via a different 
thread). This is how you set it up.

Honestly, I think accessing thread locals in a GC destructor should be 
in the spec as implementation-defined behavior.

-Steve


More information about the Digitalmars-d mailing list