Skip to content
This repository was archived by the owner on Jul 21, 2026. It is now read-only.

Commit fa75361

Browse files
committed
Refactor HMBIRD scheduler queues & threading
Replace raw concurrent deques/queues with priority queues ordered by scheduled/enqueue time and id, adding synchronization around queue mutations to ensure correct timed ordering. Improve HMBIRDLatencySampler to track actual sample count and return trimmed, sorted snapshots. Expand HMBIRDMetrics with new counters (coalesced notifications, intermediate requeues, soft-watermark activations, autoscale events), rename latency samplers, and broaden the snapshot API to include worker/queue/soft-reject/autoscale fields. Major rewrite of HMBIRDSchedulerThreadPool: introduce dynamic worker lifecycle and selection, separate timer handling that enqueues due ticks, strengthened cancellation/coalescing logic for task notifications, soft-watermark backpressure with autoscaling, safer shutdown/start semantics, and various concurrency/locking improvements (task enqueue tracking, defer/requeue handling). Also adjust local queue steal/offer semantics, polling logic, and logging. Update localization strings to reflect soft-watermark wording.
1 parent 5bfadf5 commit fa75361

7 files changed

Lines changed: 700 additions & 222 deletions

File tree

mint-server/src/main/java/dev/bacteriawa/mint/scheduler/HMBIRDDispatchQueue.java

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,33 @@
11
package dev.bacteriawa.mint.scheduler;
22

3-
import java.util.concurrent.ConcurrentLinkedQueue;
3+
import ca.spottedleaf.concurrentutil.util.TimeUtil;
4+
import java.util.Comparator;
5+
import java.util.PriorityQueue;
46
import java.util.concurrent.atomic.LongAdder;
57

68
public final class HMBIRDDispatchQueue {
79

810
// Global queues per deadline level (higher levels are polled first).
9-
private final ConcurrentLinkedQueue<HMBIRDTask>[] byDeadlineLevel;
11+
private final PriorityQueue<HMBIRDTask>[] byDeadlineLevel;
1012
// Approximate size across all levels.
1113
private final LongAdder size = new LongAdder();
14+
private static final Comparator<HMBIRDTask> TASK_ORDER = (a, b) -> {
15+
int cmp = TimeUtil.compareTimes(a.scheduledNanos(), b.scheduledNanos());
16+
if (cmp != 0) {
17+
return cmp;
18+
}
19+
cmp = TimeUtil.compareTimes(a.enqueueNanos(), b.enqueueNanos());
20+
if (cmp != 0) {
21+
return cmp;
22+
}
23+
return Long.compare(a.id(), b.id());
24+
};
1225

1326
@SuppressWarnings("unchecked")
1427
public HMBIRDDispatchQueue() {
15-
this.byDeadlineLevel = (ConcurrentLinkedQueue<HMBIRDTask>[])new ConcurrentLinkedQueue[4];
28+
this.byDeadlineLevel = (PriorityQueue<HMBIRDTask>[])new PriorityQueue[4];
1629
for (int i = 0; i < this.byDeadlineLevel.length; ++i) {
17-
this.byDeadlineLevel[i] = new ConcurrentLinkedQueue<>();
30+
this.byDeadlineLevel[i] = new PriorityQueue<>(TASK_ORDER);
1831
}
1932
}
2033

@@ -26,44 +39,36 @@ public void offer(final HMBIRDTask task) {
2639
if (!task.tryMarkQueued()) {
2740
return;
2841
}
29-
this.byDeadlineLevel[task.prop().deadlineLevel()].offer(task);
30-
this.size.increment();
42+
final PriorityQueue<HMBIRDTask> queue = this.byDeadlineLevel[task.prop().deadlineLevel()];
43+
synchronized (queue) {
44+
queue.offer(task);
45+
this.size.increment();
46+
}
3147
}
3248

3349
public HMBIRDTask pollAny() {
34-
// Prefer higher deadline levels.
35-
for (int level = this.byDeadlineLevel.length - 1; level >= 0; --level) {
36-
final ConcurrentLinkedQueue<HMBIRDTask> queue = this.byDeadlineLevel[level];
37-
final HMBIRDTask task = queue.poll();
38-
if (task == null) {
39-
continue;
40-
}
41-
this.size.decrement();
42-
if (task.tryMarkDispatching()) {
43-
return task;
44-
}
45-
}
46-
return null;
50+
return this.pollReady(Long.MAX_VALUE);
4751
}
4852

4953
public HMBIRDTask pollReady(final long nowNanos) {
5054
// Only return tasks whose scheduled time has passed.
5155
for (int level = this.byDeadlineLevel.length - 1; level >= 0; --level) {
52-
final ConcurrentLinkedQueue<HMBIRDTask> queue = this.byDeadlineLevel[level];
53-
final HMBIRDTask task = queue.peek();
54-
if (task == null) {
55-
continue;
56-
}
57-
if (task.scheduledNanos() > nowNanos) {
58-
continue;
59-
}
60-
final HMBIRDTask polled = queue.poll();
61-
if (polled == null) {
62-
continue;
63-
}
64-
this.size.decrement();
65-
if (polled.tryMarkDispatching()) {
66-
return polled;
56+
final PriorityQueue<HMBIRDTask> queue = this.byDeadlineLevel[level];
57+
synchronized (queue) {
58+
while (true) {
59+
final HMBIRDTask task = queue.peek();
60+
if (task == null) {
61+
break;
62+
}
63+
if (TimeUtil.compareTimes(task.scheduledNanos(), nowNanos) > 0) {
64+
break;
65+
}
66+
final HMBIRDTask polled = queue.poll();
67+
this.size.decrement();
68+
if (polled.tryMarkDispatching()) {
69+
return polled;
70+
}
71+
}
6772
}
6873
}
6974
return null;

mint-server/src/main/java/dev/bacteriawa/mint/scheduler/HMBIRDLatencySampler.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public final class HMBIRDLatencySampler {
88
// Ring buffer of recent samples (nanoseconds).
99
private final long[] samples;
1010
private final AtomicInteger writeIndex = new AtomicInteger();
11+
private final AtomicInteger sampleCount = new AtomicInteger();
1112

1213
public HMBIRDLatencySampler(final int capacity) {
1314
if (capacity <= 0) {
@@ -20,10 +21,12 @@ public void record(final long value) {
2021
// Overwrites oldest samples in a ring.
2122
final int idx = Math.floorMod(this.writeIndex.getAndIncrement(), this.samples.length);
2223
this.samples[idx] = value;
24+
this.sampleCount.updateAndGet(count -> Math.min(this.samples.length, count + 1));
2325
}
2426

2527
public Snapshot snapshot() {
26-
final long[] copy = Arrays.copyOf(this.samples, this.samples.length);
28+
final int count = Math.min(this.sampleCount.get(), this.samples.length);
29+
final long[] copy = Arrays.copyOf(this.samples, count);
2730
Arrays.sort(copy);
2831
return new Snapshot(copy);
2932
}

mint-server/src/main/java/dev/bacteriawa/mint/scheduler/HMBIRDLocalQueue.java

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,109 @@
11
package dev.bacteriawa.mint.scheduler;
22

3-
import java.util.concurrent.ConcurrentLinkedDeque;
3+
import ca.spottedleaf.concurrentutil.util.TimeUtil;
4+
import java.util.ArrayList;
5+
import java.util.Comparator;
6+
import java.util.List;
7+
import java.util.PriorityQueue;
48
import java.util.concurrent.atomic.LongAdder;
59

610
public final class HMBIRDLocalQueue {
711

8-
// Per-worker queues, LIFO for locality; higher levels are polled first.
9-
private final ConcurrentLinkedDeque<HMBIRDTask>[] byDeadlineLevel;
12+
// Per-worker queues; higher levels are polled first, then older due/enqueued tasks.
13+
private final PriorityQueue<HMBIRDTask>[] byDeadlineLevel;
1014
// Approximate size across all levels.
1115
private final LongAdder size = new LongAdder();
16+
private static final Comparator<HMBIRDTask> TASK_ORDER = (a, b) -> {
17+
int cmp = TimeUtil.compareTimes(a.scheduledNanos(), b.scheduledNanos());
18+
if (cmp != 0) {
19+
return cmp;
20+
}
21+
cmp = TimeUtil.compareTimes(a.enqueueNanos(), b.enqueueNanos());
22+
if (cmp != 0) {
23+
return cmp;
24+
}
25+
return Long.compare(a.id(), b.id());
26+
};
1227

1328
@SuppressWarnings("unchecked")
1429
public HMBIRDLocalQueue() {
15-
this.byDeadlineLevel = (ConcurrentLinkedDeque<HMBIRDTask>[])new ConcurrentLinkedDeque[4];
30+
this.byDeadlineLevel = (PriorityQueue<HMBIRDTask>[])new PriorityQueue[4];
1631
for (int i = 0; i < this.byDeadlineLevel.length; ++i) {
17-
this.byDeadlineLevel[i] = new ConcurrentLinkedDeque<>();
32+
this.byDeadlineLevel[i] = new PriorityQueue<>(TASK_ORDER);
1833
}
1934
}
2035

2136
public long size() {
2237
return this.size.sum();
2338
}
2439

40+
public boolean isEmpty() {
41+
return this.size() <= 0L;
42+
}
43+
2544
public void offer(final HMBIRDTask task) {
2645
if (!task.tryMarkQueued()) {
2746
return;
2847
}
29-
this.byDeadlineLevel[task.prop().deadlineLevel()].offerFirst(task);
30-
this.size.increment();
48+
final PriorityQueue<HMBIRDTask> queue = this.byDeadlineLevel[task.prop().deadlineLevel()];
49+
synchronized (queue) {
50+
queue.offer(task);
51+
this.size.increment();
52+
}
3153
}
3254

3355
public HMBIRDTask pollAny() {
3456
// Prefer higher deadline levels.
3557
for (int level = this.byDeadlineLevel.length - 1; level >= 0; --level) {
36-
final ConcurrentLinkedDeque<HMBIRDTask> queue = this.byDeadlineLevel[level];
37-
final HMBIRDTask task = queue.pollFirst();
38-
if (task == null) {
39-
continue;
40-
}
41-
this.size.decrement();
42-
if (task.tryMarkDispatching()) {
43-
return task;
58+
final PriorityQueue<HMBIRDTask> queue = this.byDeadlineLevel[level];
59+
synchronized (queue) {
60+
while (true) {
61+
final HMBIRDTask task = queue.poll();
62+
if (task == null) {
63+
break;
64+
}
65+
this.size.decrement();
66+
if (task.tryMarkDispatching()) {
67+
return task;
68+
}
69+
}
4470
}
4571
}
4672
return null;
4773
}
4874

4975
public HMBIRDTask stealAny() {
50-
// Steal from tail to reduce contention and preserve locality.
76+
// Steal the oldest stealable task at the highest available deadline level.
5177
for (int level = this.byDeadlineLevel.length - 1; level >= 0; --level) {
52-
final ConcurrentLinkedDeque<HMBIRDTask> queue = this.byDeadlineLevel[level];
53-
final HMBIRDTask task = queue.pollLast();
54-
if (task == null) {
55-
continue;
56-
}
57-
this.size.decrement();
58-
if (task.prop().allowSteal() && task.tryMarkDispatching()) {
59-
return task;
78+
final PriorityQueue<HMBIRDTask> queue = this.byDeadlineLevel[level];
79+
synchronized (queue) {
80+
List<HMBIRDTask> skipped = null;
81+
try {
82+
while (true) {
83+
final HMBIRDTask task = queue.poll();
84+
if (task == null) {
85+
break;
86+
}
87+
this.size.decrement();
88+
if (!task.prop().allowSteal()) {
89+
if (skipped == null) {
90+
skipped = new ArrayList<>();
91+
}
92+
skipped.add(task);
93+
continue;
94+
}
95+
if (task.tryMarkDispatching()) {
96+
return task;
97+
}
98+
}
99+
} finally {
100+
if (skipped != null) {
101+
for (final HMBIRDTask task : skipped) {
102+
queue.offer(task);
103+
this.size.increment();
104+
}
105+
}
106+
}
60107
}
61108
}
62109
return null;

0 commit comments

Comments
 (0)