synchronized class bugs?

Steven Schveighoffer schveiguy at gmail.com
Wed Apr 8 13:43:08 UTC 2020


On 4/7/20 11:18 AM, Gregor Mückl wrote:
> I've been playing around with synchronized class. My example is the 
> following dummy class:
> 
>      synchronized class Shared {
>      public:
>          void increment(int value) {
>              //(cast(Shared) this).x += value;
>              x += value;
>          }
> 
>          void decrement(int value) {
>              //(cast(Shared) this).x -= value;
>              x -= value;
>          }
> 
>          shared int get() { return x; }
> 
>      private:
>          int x;
>      }
> 
> Then I'm calling increment and decrement many times in parallel threads 
> so that they *should* cancel out in the end and x should be back to 0 at 
> the end. With the implicit synchronization using d monitors, this should 
> work.
> 
> Bug number 1: dmd doesn't translate this without casting away shared:
> 
> sharedtest.d(14): Error: read-modify-write operations are not allowed 
> for shared variables. Use core.atomic.atomicOp!"+="(this.x, value) instead.
> 
> The += and -= operators are safe as they are inside locked monitors. The 
> emitted code contains calls to d_montiorenter and d_monitorexit. The 
> compiler should understand this.

The requirement for atomicOp is separate from the synchronization. The 
synchronization is only sound because you made it sound.

In fact, if you did:

auto getPtr() { return &x; }

Then the synchronization all of a sudden does not apply to that variable.

This is why we are moving to REQUIRING casting away shared to read/write 
data -- you have to do it anyway.

> Bug number 2: even when casting away shared, the end result is wrong 
> when using multiple threads. Thus, the monitor locking code in druntime 
> cannot be correct. It does have an effect, though. Omitting synchronized 
> on Shared results in values that are wide off the mark. Including it 
> results in small differences. This suggests that a memory fence might be 
> missing somewhere.
> 
> The observed reality contradicts the documentation in many ways and is 
> inconsistent with itself. What is going on here?

synchronized classes call _d_monitorenter and _d_monitorexit, which lock 
OS primitives (and initialize the monitor if necessary). These have not 
been touched in years, and depend on the OS mutex which should be 
rock-solid. Looking at the history, it looks like the last significant 
change was 
https://github.com/dlang/druntime/commit/70b3536ddd06e8e1afb269b88ed3298ec03b8798#diff-d609308f6ce1b06db16e214b30db6f74 
in 2015.

I've definitely depended on synchronized functions in the past, and 
haven't had issues, but it was in the far past.

The only place I can reasonably assume that there might be an issue is 
for the initialization.

To test this, try initializing the lock FIRST before spawning multiple 
threads (just call one of the functions), and see if it helps. If so, it 
might be a bug in the initialization routines.

Also if you find an issue, it may be specific to your OS.

-Steve


More information about the Digitalmars-d mailing list