mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-11-01 08:50:06 +00:00
Unqueued history claim waits
- Semantic claims and saved biome writes elect one writer per batch instead of queueing every caller on the monitor - Waiting callers park until their batch settles and wake the next queued caller when the writer leaves - Admission tests count parked followers as queued behind the history writer
This commit is contained in:
1 parent
1a914a404a
commit
ed83c6fdeb
3 files changed
+86
-13
No files matched your search
@@ -26,6 +26,8 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public final class GenerationHistory {
|
||||
@@ -34,6 +36,7 @@ public final class GenerationHistory {
|
||||
private static final int MAXIMUM_CACHED_TRANSITION_PLANS = 4;
|
||||
private static final int MAXIMUM_SEMANTIC_BATCH = 32;
|
||||
private static final int MAXIMUM_PENDING_SEMANTIC_CLAIMS = 128;
|
||||
private static final long SEMANTIC_CLAIM_PARK_NANOS = 10_000_000L;
|
||||
|
||||
private final GenerationHistoryPaths paths;
|
||||
private final GenerationPackRepository packs;
|
||||
@@ -50,6 +53,7 @@ public final class GenerationHistory {
|
||||
private final Map<Long, TransitionGenerationPlan> transitionPlanCache;
|
||||
private final ArrayBlockingQueue<PendingSemanticClaim> pendingSemanticClaims =
|
||||
new ArrayBlockingQueue<>(MAXIMUM_PENDING_SEMANTIC_CLAIMS);
|
||||
private final AtomicBoolean semanticWriter = new AtomicBoolean();
|
||||
|
||||
private GenerationHistory(
|
||||
GenerationHistoryPaths paths,
|
||||
@@ -776,12 +780,26 @@ public final class GenerationHistory {
|
||||
persistPendingSemanticClaims();
|
||||
}
|
||||
}
|
||||
while (!pending.settled) {
|
||||
if (!semanticWriter.compareAndSet(false, true)) {
|
||||
LockSupport.parkNanos(pending, SEMANTIC_CLAIM_PARK_NANOS);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
synchronized (this) {
|
||||
while (!pending.claim.completed) {
|
||||
persistPendingSemanticClaims();
|
||||
}
|
||||
return pending.claim.result();
|
||||
}
|
||||
} finally {
|
||||
semanticWriter.set(false);
|
||||
PendingSemanticClaim next = pendingSemanticClaims.peek();
|
||||
if (next != null) {
|
||||
LockSupport.unpark(next.owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
return pending.claim.result();
|
||||
}
|
||||
|
||||
private void persistPendingSemanticClaims() {
|
||||
@@ -814,10 +832,15 @@ public final class GenerationHistory {
|
||||
}
|
||||
while ((current = pendingSemanticClaims.poll()) != null) {
|
||||
reported |= current.claim.fail(failure);
|
||||
current.settle();
|
||||
}
|
||||
if (!reported) {
|
||||
throw failure;
|
||||
}
|
||||
} finally {
|
||||
for (PendingSemanticClaim pending : batch) {
|
||||
pending.settle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1354,11 +1377,21 @@ public final class GenerationHistory {
|
||||
private static final class PendingSemanticClaim {
|
||||
private final GenerationStage stage;
|
||||
private final GenerationSemanticIndex.Claim claim;
|
||||
private final Thread owner = Thread.currentThread();
|
||||
private volatile boolean settled;
|
||||
|
||||
private PendingSemanticClaim(GenerationStage stage, ChunkGenerationSemantics update) {
|
||||
this.stage = stage;
|
||||
this.claim = new GenerationSemanticIndex.Claim(update);
|
||||
}
|
||||
|
||||
private void settle() {
|
||||
if (!claim.completed || settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
LockSupport.unpark(owner);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class GenerationStage implements AutoCloseable {
|
||||
|
||||
@@ -29,6 +29,8 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.CRC32;
|
||||
@@ -49,6 +51,7 @@ public final class SavedBiomeStore {
|
||||
private static final int MAXIMUM_CACHED_CHUNKS = 128;
|
||||
private static final long MAXIMUM_CACHED_BYTES = 64L * 1024L * 1024L;
|
||||
private static final int MAXIMUM_WRITE_BATCH = 64;
|
||||
private static final long WRITE_PARK_NANOS = 10_000_000L;
|
||||
private static final ThreadLocal<Deflater> DEFLATERS = ThreadLocal.withInitial(() -> new Deflater(Deflater.BEST_SPEED));
|
||||
|
||||
private final Path dimensionRoot;
|
||||
@@ -132,19 +135,34 @@ public final class SavedBiomeStore {
|
||||
.putInt(body.length).put(body).putInt(checksum(body)).array();
|
||||
PendingWrite pending = new PendingWrite(required, record);
|
||||
lock.pending.add(pending);
|
||||
while (!pending.settled) {
|
||||
if (!lock.writer.compareAndSet(false, true)) {
|
||||
LockSupport.parkNanos(pending, WRITE_PARK_NANOS);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
synchronized (lock) {
|
||||
while (!pending.completed) {
|
||||
persistPending(lock);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.writer.set(false);
|
||||
PendingWrite next = lock.pending.peek();
|
||||
if (next != null) {
|
||||
LockSupport.unpark(next.owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pending.failure != null) {
|
||||
throw pending.failure;
|
||||
}
|
||||
return pending.persisted;
|
||||
}
|
||||
}
|
||||
|
||||
private void persistPending(WriteStripe stripe) {
|
||||
Map<Long, List<PendingWrite>> batches = new LinkedHashMap<>();
|
||||
List<PendingWrite> polled = new ArrayList<>(MAXIMUM_WRITE_BATCH);
|
||||
PendingWrite grouping = null;
|
||||
try {
|
||||
for (int count = 0; count < MAXIMUM_WRITE_BATCH; count++) {
|
||||
@@ -152,6 +170,7 @@ public final class SavedBiomeStore {
|
||||
if (grouping == null) {
|
||||
break;
|
||||
}
|
||||
polled.add(grouping);
|
||||
long regionKey = key(grouping.chunk.chunkX() >> 5, grouping.chunk.chunkZ() >> 5);
|
||||
batches.computeIfAbsent(regionKey, ignored -> new ArrayList<>()).add(grouping);
|
||||
grouping = null;
|
||||
@@ -175,6 +194,10 @@ public final class SavedBiomeStore {
|
||||
}
|
||||
}
|
||||
throw failure;
|
||||
} finally {
|
||||
for (PendingWrite pending : polled) {
|
||||
pending.settle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,19 +845,30 @@ public final class SavedBiomeStore {
|
||||
|
||||
private static final class WriteStripe {
|
||||
private final ConcurrentLinkedQueue<PendingWrite> pending = new ConcurrentLinkedQueue<>();
|
||||
private final AtomicBoolean writer = new AtomicBoolean();
|
||||
}
|
||||
|
||||
private static final class PendingWrite {
|
||||
private final SavedBiomeChunk chunk;
|
||||
private final byte[] record;
|
||||
private final Thread owner = Thread.currentThread();
|
||||
private boolean completed;
|
||||
private boolean persisted;
|
||||
private IOException failure;
|
||||
private volatile boolean settled;
|
||||
|
||||
private PendingWrite(SavedBiomeChunk chunk, byte[] record) {
|
||||
this.chunk = chunk;
|
||||
this.record = record;
|
||||
}
|
||||
|
||||
private void settle() {
|
||||
if (!completed || settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
LockSupport.unpark(owner);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DecodeBudget {
|
||||
|
||||
+17
-11
@@ -250,7 +250,7 @@ public class GenerationHistoryAdmissionTest {
|
||||
.addSurfaceBiome("iris:conflict").seal().build()
|
||||
: claim(stage);
|
||||
results.add(executor.submit(() -> persistCounting(history, stage, update, appends, forces)));
|
||||
awaitBlockedOnHistory(history, workers, index + 1);
|
||||
awaitQueuedOnHistory(history, workers, index + 1);
|
||||
}
|
||||
stages.get(5).close();
|
||||
}
|
||||
@@ -340,7 +340,7 @@ public class GenerationHistoryAdmissionTest {
|
||||
for (int index = 0; index < stages.size(); index++) {
|
||||
GenerationHistory.GenerationStage stage = stages.get(index);
|
||||
results.add(executor.submit(() -> persistFailingRegion(history, stage, failure)));
|
||||
awaitBlockedOnHistory(history, workers, index + 1);
|
||||
awaitQueuedOnHistory(history, workers, index + 1);
|
||||
}
|
||||
}
|
||||
assertTrue(results.getFirst().get(5, TimeUnit.SECONDS));
|
||||
@@ -404,21 +404,27 @@ public class GenerationHistoryAdmissionTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static void awaitBlockedOnHistory(GenerationHistory history, List<Thread> workers, int count) {
|
||||
Await.until("semantic claim callers to queue behind the History monitor", Duration.ofSeconds(5L),
|
||||
() -> blockedOnHistory(history, workers) == count);
|
||||
private static void awaitQueuedOnHistory(GenerationHistory history, List<Thread> workers, int count) {
|
||||
Await.until("semantic claim callers to queue behind the History writer", Duration.ofSeconds(5L),
|
||||
() -> queuedOnHistory(history, workers) == count);
|
||||
}
|
||||
|
||||
private static int blockedOnHistory(GenerationHistory history, List<Thread> workers) {
|
||||
int blocked = 0;
|
||||
private static int queuedOnHistory(GenerationHistory history, List<Thread> workers) {
|
||||
int queued = 0;
|
||||
for (Thread worker : workers) {
|
||||
ThreadInfo info = ManagementFactory.getThreadMXBean().getThreadInfo(worker.threadId());
|
||||
if (info != null && info.getThreadState() == Thread.State.BLOCKED
|
||||
&& info.getLockInfo().getIdentityHashCode() == System.identityHashCode(history)) {
|
||||
blocked++;
|
||||
if (info == null || info.getLockInfo() == null) {
|
||||
continue;
|
||||
}
|
||||
boolean writer = info.getThreadState() == Thread.State.BLOCKED
|
||||
&& info.getLockInfo().getIdentityHashCode() == System.identityHashCode(history);
|
||||
boolean follower = info.getThreadState() == Thread.State.TIMED_WAITING
|
||||
&& info.getLockInfo().getClassName().endsWith("GenerationHistory$PendingSemanticClaim");
|
||||
if (writer || follower) {
|
||||
queued++;
|
||||
}
|
||||
}
|
||||
return blocked;
|
||||
return queued;
|
||||
}
|
||||
|
||||
private static boolean persistPaused(GenerationHistory history, GenerationHistory.GenerationStage stage,
|
||||
|
||||
Reference in new issue
Block a user