question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Thread safety concerns due to "memory barrier" pattern in value classes

See original GitHub issue

Many of the Value classes contain instances of a pattern that I think is an incorrect attempt at creating a “memory barrier”. This pattern is known to be incorrect in Java.

The essence of the incorrect memory barrier pattern is

  • Writers acquire a lock, initialize an object, release the lock, re-acquire the lock, then write the initialized object to a place where readers can see it. Finally, they release the lock again.
  • Readers do not use any synchronization.

This pattern is seductive because it does constrain the order in which writes reach main memory. There is also no issue if all reads and writes happen in the same thread. However, it is incorrect when there are concurrent writers and readers because the readers do not have to observe those writes in order. Readers can observe writes out-of-order if they page-in the reference to the initialized object before they page-in the writes that initialize the fields of the object.

Here is an example from FcnRcdValue. The “writer” pathway is in createIndex():

int[] tbl = new int[len];
Arrays.fill(tbl, -1);

synchronized(this) {
  for (int i = 0; i < this.domain.length; i++) {
    int loc = (this.domain[i].hashCode() & 0x7FFFFFFF) % len;
    while (tbl[loc] != -1) {
      loc = (loc + 1) % len;
    }
    tbl[loc] = i;
  }
}

synchronized(this) { this.indexTbl = tbl; }

The first synchronized block initializes the cells of the local array tbl, and the second one writes tbl to this.indexTbl where it can be picked up by readers.

The reader pathway is in select(arg):

if (this.indexTbl != null) {
  return this.values[this.lookupIndex(arg)];
}
if (this.isNorm) this.createIndex();

if (this.indexTbl != null) {
  return this.values[this.lookupIndex(arg)];
}

In this example, the reader’s first check (this.indexTbl != null) can be true even when the cells of the indexTbl array are uninitialized (i.e. all zero). Even if the cells are initialized in main memory, those writes may not be visible to the reader yet. This could lead to readers crashing or returning incorrect values for function applications.

These classes contain instances of this pattern:

  • FcnRcdValue
  • SetCapValue
  • SetCupValue
  • SetDiffValue
  • SetOfFcnsValue
  • SetOfRcdsValue
  • SetOfTuplesValue
  • SubsetValue
  • UnionValue

Either this is a very subtle concurrency bug, or the synchronization is actually unnecessary and it can be removed to improve TLC’s performance by one iota.

If this is a concurrency bug, I believe it can be fixed with careful use of the volatile keyword and removal (or merging) of some synchronized blocks.

I have never seen problems due to this code. However, I believe it is worth reporting and fixing.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:21 (17 by maintainers)

github_iconTop GitHub Comments

2reactions
lemmycommented, Dec 2, 2021

Thanks, @craft095 and @Calvin-L for the reviews!

1reaction
craft095commented, Nov 18, 2021

Sure I will do.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Should thread-safe class have a memory barrier at the end of ...
Thread -safe classes have properties and methods that can be safely invoked by multiple threads concurrently, but their constructors are not ...
Read more >
Thread-safe singletons and their usage in Swift
A barrier: A barrier is used on a concurrent queue in order to synchronise writes. By indicating the barrier flag, it allows to...
Read more >
Thread-Safe Class Design - objc.io
This article will focus on practical tips, design patterns, and anti-patterns with regard to writing thread-safe classes and using Grand Central Dispatch ...
Read more >
Concurrency Hazards: Solving Problems In Your ...
A data race—or race condition—occurs when data is accessed concurrently from multiple threads. Specifically, it happens when one or more threads are writing...
Read more >
Reading 20: Thread Safety - MIT
Recall race conditions: multiple threads sharing the same mutable variable without coordinating what they're doing. This is unsafe, because the correctness of ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found