Skip to content

Commit c8acced

Browse files
committed
Replace custom ReentrantReadWriteLock with StampedLock
1 parent dcbfe39 commit c8acced

File tree

1 file changed

+12
-61
lines changed

1 file changed

+12
-61
lines changed

src/main/java/com/google/devtools/build/lib/remote/RemoteRewoundActionSynchronizer.java

Lines changed: 12 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@
2929
import com.google.devtools.build.lib.profiler.SilentCloseable;
3030
import com.google.devtools.build.lib.vfs.OutputService.RewoundActionSynchronizer;
3131
import java.util.concurrent.ConcurrentHashMap;
32-
import java.util.concurrent.locks.Condition;
3332
import java.util.concurrent.locks.Lock;
3433
import java.util.concurrent.locks.ReadWriteLock;
3534
import java.util.concurrent.locks.ReentrantReadWriteLock;
35+
import java.util.concurrent.locks.StampedLock;
3636
import javax.annotation.Nullable;
3737

3838
/**
@@ -55,7 +55,8 @@ public interface Cancellable {
5555
// execution.
5656
// This ensures high throughput and low memory footprint for the common case of no rewound
5757
// actions. In this case, there won't be any writers and the performance characteristics of a
58-
// ReentrantReadWriteLock are comparable to that of an atomic counter.
58+
// ReentrantReadWriteLock are comparable to that of an atomic counter. A StampedLock would not be
59+
// a good fit as its performance regresses with 127 or more concurrent readers.
5960
// Note that it wouldn't be correct to only start using this lock once an action is rewound,
6061
// because a non-rewound action consuming its non-lost outputs could have already started
6162
// executing.
@@ -156,8 +157,15 @@ private SilentCloseable enterActionPreparationForRewinding(Action action)
156157
fineLocks =
157158
Caffeine.newBuilder()
158159
.weakValues()
159-
// TODO: Investigate whether fair locks would be beneficial.
160-
.build((ActionLookupData unused) -> new WeakSafeReentrantReadWriteLock());
160+
// ReentrantReadWriteLock would not work here as its individual read and write
161+
// locks do not strongly reference the parent lock, which would lead to locks
162+
// being cleaned up while they are still held
163+
// (https://bugs.openjdk.org/browse/JDK-8189598). This can be worked around by
164+
// using a construction similar to Guava's Striped helpers. StampedLock is both
165+
// more memory-efficient and its views do strongly reference the parent lock
166+
// (https://github.com/openjdk/jdk/blob/b349f661ea5f14b258191134714a7e712c90ef3e/src/java.base/share/classes/java/util/concurrent/locks/StampedLock.java#L1039),
167+
// TODO: Investigate the effect of fair locks on build wall time.
168+
.build((ActionLookupData unused) -> new StampedLock().asReadWriteLock());
161169
coarseLock = null;
162170
}
163171
} finally {
@@ -284,61 +292,4 @@ private static Iterable<ActionLookupData> inputKeysFor(
284292
private static ActionLookupData outputKeyFor(Action action) {
285293
return ((DerivedArtifact) action.getPrimaryOutput()).getGeneratingActionKey();
286294
}
287-
288-
// Classes below are based on Guava's Striped class, but optimized for memory usage by using
289-
// extension rather than delegation:
290-
// https://github.com/google/guava/blob/d25d62fc843ece1c3866859bc8639b815093eac8/guava/src/com/google/common/util/concurrent/Striped.java#L282-L326
291-
292-
/**
293-
* ReadWriteLock implementation whose read and write locks retain a reference back to this lock.
294-
* Otherwise, a reference to just the read lock or just the write lock would not suffice to ensure
295-
* the {@code ReadWriteLock} is retained.
296-
*
297-
* <p>{@see https://bugs.openjdk.org/browse/JDK-8189598}
298-
*/
299-
private static final class WeakSafeReentrantReadWriteLock extends ReentrantReadWriteLock {
300-
private final WeakSafeReadLock readLock = new WeakSafeReadLock(this);
301-
private final WeakSafeWriteLock writeLock = new WeakSafeWriteLock(this);
302-
303-
@Override
304-
public WeakSafeReadLock readLock() {
305-
return readLock;
306-
}
307-
308-
@Override
309-
public WeakSafeWriteLock writeLock() {
310-
return writeLock;
311-
}
312-
}
313-
314-
/**
315-
* A read lock that ensures a strong reference is retained to the owning {@link ReadWriteLock}.
316-
*/
317-
private static final class WeakSafeReadLock extends ReentrantReadWriteLock.ReadLock {
318-
@SuppressWarnings({"unused", "FieldCanBeLocal"})
319-
private final WeakSafeReentrantReadWriteLock strongReference;
320-
321-
WeakSafeReadLock(WeakSafeReentrantReadWriteLock readWriteLock) {
322-
super(readWriteLock);
323-
this.strongReference = readWriteLock;
324-
}
325-
}
326-
327-
/**
328-
* A write lock that ensures a strong reference is retained to the owning {@link ReadWriteLock}.
329-
*/
330-
private static final class WeakSafeWriteLock extends ReentrantReadWriteLock.WriteLock {
331-
@SuppressWarnings({"unused", "FieldCanBeLocal"})
332-
private final WeakSafeReentrantReadWriteLock strongReference;
333-
334-
WeakSafeWriteLock(WeakSafeReentrantReadWriteLock readWriteLock) {
335-
super(readWriteLock);
336-
this.strongReference = readWriteLock;
337-
}
338-
339-
@Override
340-
public Condition newCondition() {
341-
throw new UnsupportedOperationException();
342-
}
343-
}
344295
}

0 commit comments

Comments
 (0)