| // Copyright 2026 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.lib.remote; | |
| import java.lang.invoke.MethodHandles; | |
| import java.lang.invoke.VarHandle; | |
| /** | |
| * A non-reentrant read-write lock that admits new readers even while a writer is waiting. | |
| * | |
| * <p>A reader only ever waits for a writer that holds the lock, never for other readers or for a | |
| * waiting writer. A writer waits until the lock is free and may thus be starved by a steady stream | |
| * of readers. Writers exclude each other. | |
| * | |
| * <p>This class is optimized for a low memory footprint and for not inflating the object's monitor | |
| * under contention between readers. The monitor is only entered by writers and by the last reader | |
| * releasing the lock while a writer is waiting for it. It is thus only inflated while a writer | |
| * holds or waits for the lock. | |
| */ | |
| final class ReaderPreferringReadWriteLock { | |
| private static final VarHandle HOLDS; | |
| static { | |
| try { | |
| HOLDS = | |
| MethodHandles.lookup() | |
| .findVarHandle(ReaderPreferringReadWriteLock.class, "holds", int.class); | |
| } catch (ReflectiveOperationException e) { | |
| throw new ExceptionInInitializerError(e); | |
| } | |
| } | |
| private static final int WRITER = -1; | |
| private static final int WRITER_WAITING = 1 << 30; | |
| private static final int READER_COUNT_MASK = WRITER_WAITING - 1; | |
| // WRITER means that the writer holds the lock. Otherwise, the low bits count the readers holding | |
| // it and WRITER_WAITING is set while a writer waits for them to release it. The lock is free at | |
| // zero. | |
| private volatile int holds; | |
| void lockReadInterruptibly() throws InterruptedException { | |
| while (true) { | |
| if (Thread.interrupted()) { | |
| throw new InterruptedException(); | |
| } | |
| int currentHolds = holds; | |
| if (currentHolds != WRITER) { | |
| // Readers are admitted even if a writer is waiting. | |
| if (HOLDS.compareAndSet(this, currentHolds, currentHolds + 1)) { | |
| return; | |
| } | |
| } else { | |
| synchronized (this) { | |
| // Rechecked under the monitor so that the notification in unlockWrite can't be missed. | |
| while (holds == WRITER) { | |
| wait(); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void unlockRead() { | |
| int currentHolds; | |
| int newHolds; | |
| do { | |
| currentHolds = holds; | |
| if (currentHolds == WRITER || (currentHolds & READER_COUNT_MASK) == 0) { | |
| throw new IllegalMonitorStateException("holds: " + currentHolds); | |
| } | |
| // Clear the record of a waiting writer when the last reader releases the lock, even if all | |
| // waiting writers have been interrupted. Notified writers recheck the state and record | |
| // themselves again if needed. | |
| newHolds = currentHolds == WRITER_WAITING + 1 ? 0 : currentHolds - 1; | |
| } while (!HOLDS.compareAndSet(this, currentHolds, newHolds)); | |
| if (currentHolds == WRITER_WAITING + 1) { | |
| // This was the last reader and a writer may still be waiting for it. | |
| synchronized (this) { | |
| notifyAll(); | |
| } | |
| } | |
| } | |
| void lockWriteInterruptibly() throws InterruptedException { | |
| synchronized (this) { | |
| while (true) { | |
| if (Thread.interrupted()) { | |
| throw new InterruptedException(); | |
| } | |
| int currentHolds = holds; | |
| if (currentHolds == 0) { | |
| // Barging ahead of another waiting writer is safe since it will be woken up and recheck | |
| // the state after the next unlock. | |
| if (HOLDS.compareAndSet(this, currentHolds, WRITER)) { | |
| return; | |
| } | |
| } else if (currentHolds == WRITER || (currentHolds & WRITER_WAITING) != 0) { | |
| wait(); | |
| } else { | |
| // Readers hold the lock. Record the waiting writer so that the last reader notifies it. | |
| var _ = HOLDS.compareAndSet(this, currentHolds, currentHolds | WRITER_WAITING); | |
| } | |
| } | |
| } | |
| } | |
| void unlockWrite() { | |
| if (!HOLDS.compareAndSet(this, WRITER, 0)) { | |
| throw new IllegalMonitorStateException("holds: " + holds); | |
| } | |
| synchronized (this) { | |
| notifyAll(); | |
| } | |
| } | |
| @Override | |
| public String toString() { | |
| int currentHolds = holds; | |
| boolean writer = currentHolds == WRITER; | |
| return "ReaderPreferringReadWriteLock[readers=%d, writer=%b, writerWaiting=%b]" | |
| .formatted( | |
| writer ? 0 : currentHolds & READER_COUNT_MASK, | |
| writer, | |
| !writer && (currentHolds & WRITER_WAITING) != 0); | |
| } | |
| } |