2929import com .google .devtools .build .lib .profiler .SilentCloseable ;
3030import com .google .devtools .build .lib .vfs .OutputService .RewoundActionSynchronizer ;
3131import java .util .concurrent .ConcurrentHashMap ;
32- import java .util .concurrent .locks .Condition ;
3332import java .util .concurrent .locks .Lock ;
3433import java .util .concurrent .locks .ReadWriteLock ;
3534import java .util .concurrent .locks .ReentrantReadWriteLock ;
35+ import java .util .concurrent .locks .StampedLock ;
3636import 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