Skip to content

Commit ad3abff

Browse files
committed
Lock-free MemoryUsage accounting via LongAdder
Replace the exclusive-lock byte counter in MemoryUsage with a striped java.util.concurrent.atomic.LongAdder and make percentUsage volatile. increaseUsage/decreaseUsage become lock-free adds; the existing usageLock/setPercentUsage path (listener events, waitForSpace signalling) is entered only when the rounded percentUsage actually changes - at most ~100/percentUsageMinDelta locked updates per limit traversal instead of one exclusive write lock (plus the parent chain's) per message. isFull() becomes a volatile read. waitForSpace and all public APIs are unchanged. Rationale: profiling the previous commit's benchmark showed ~86% of lock wait time in this class - Topic.send -> isFull (read lock), Message.incrementReferenceCount -> increaseUsage (write lock) and decrementReferenceCount -> decreaseUsage (write lock), each recursing into the broker-global SystemUsage parent. That made usage accounting a broker-wide serialization point that destination sharding cannot avoid. The multi-producer degradation curve is eliminated; the shared-topic and uncontended single-producer cases also improve (~+22% / ~+11%). Allocation is unchanged (JMH gc profiler: ~480 vs ~527 B/op, GC count ~0 in both), so the gain is purely lock behaviour. Semantics validation (all pass against this change): ProducerFlowControlTest (7), ProducerFlowControlSendFailTest (8), TopicProducerFlowControlTest (3), CompositeMessageCursorUsageTest (1), QueueMemoryAndStoreUsageCleanupTest (1). Known trade-off: between an add and the locked percent update there is a sub-percentUsageMinDelta staleness window; rapid crossings may coalesce listener events (pairs stay consistent and the locked update recomputes from the live sum, so it self-corrects). StoreUsage and TempUsage still use the base-class locked path and can be converted the same way as a follow-up.
1 parent 6f45655 commit ad3abff

2 files changed

Lines changed: 38 additions & 25 deletions

File tree

activemq-client/src/main/java/org/apache/activemq/usage/MemoryUsage.java

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.apache.activemq.usage;
1818

1919
import java.util.concurrent.TimeUnit;
20+
import java.util.concurrent.atomic.LongAdder;
2021

2122
/**
2223
* Used to keep track of how much of something is being used so that a
@@ -28,7 +29,11 @@
2829
*/
2930
public class MemoryUsage extends Usage<MemoryUsage> {
3031

31-
private long usage;
32+
// PROTOTYPE: lock-free usage accounting. The counter is a striped LongAdder so
33+
// increase/decrease never take an exclusive lock; the usageLock is only taken when the
34+
// rounded percentUsage actually changes (at most ~100/percentUsageMinDelta times per
35+
// limit traversal), which preserves listener events and waitForSpace signalling.
36+
private final LongAdder usage = new LongAdder();
3237

3338
public MemoryUsage() {
3439
this(null, null);
@@ -129,12 +134,8 @@ public boolean isFull() {
129134
if (parent != null && parent.isFull()) {
130135
return true;
131136
}
132-
usageLock.readLock().lock();
133-
try {
134-
return percentUsage >= 100;
135-
} finally {
136-
usageLock.readLock().unlock();
137-
}
137+
// percentUsage is volatile; no lock needed for a read.
138+
return percentUsage >= 100;
138139
}
139140

140141
/**
@@ -159,13 +160,8 @@ public void increaseUsage(long value) {
159160
return;
160161
}
161162

162-
usageLock.writeLock().lock();
163-
try {
164-
usage += value;
165-
setPercentUsage(caclPercentUsage());
166-
} finally {
167-
usageLock.writeLock().unlock();
168-
}
163+
usage.add(value);
164+
maybeUpdatePercent();
169165

170166
if (parent != null) {
171167
parent.increaseUsage(value);
@@ -182,31 +178,46 @@ public void decreaseUsage(long value) {
182178
return;
183179
}
184180

185-
usageLock.writeLock().lock();
186-
try {
187-
usage -= value;
188-
setPercentUsage(caclPercentUsage());
189-
} finally {
190-
usageLock.writeLock().unlock();
191-
}
181+
usage.add(-value);
182+
maybeUpdatePercent();
192183

193184
if (parent != null) {
194185
parent.decreaseUsage(value);
195186
}
196187
}
197188

189+
/**
190+
* Fast-path percent maintenance: a dirty compare against the volatile percentUsage; only
191+
* when the rounded percent has actually changed do we take the writeLock and run the
192+
* existing setPercentUsage() (which fires listener events and signals waitForSpace
193+
* waiters). setPercentUsage recomputes from the live sum under the lock, so the last
194+
* writer always stores a fresh value and transient races self-correct on the next update.
195+
*/
196+
private void maybeUpdatePercent() {
197+
if (caclPercentUsage() != percentUsage) {
198+
usageLock.writeLock().lock();
199+
try {
200+
setPercentUsage(caclPercentUsage());
201+
} finally {
202+
usageLock.writeLock().unlock();
203+
}
204+
}
205+
}
206+
198207
@Override
199208
protected long retrieveUsage() {
200-
return usage;
209+
return usage.sum();
201210
}
202211

203212
@Override
204213
public long getUsage() {
205-
return usage;
214+
return usage.sum();
206215
}
207216

208217
public void setUsage(long usage) {
209-
this.usage = usage;
218+
this.usage.reset();
219+
this.usage.add(usage);
220+
maybeUpdatePercent();
210221
}
211222

212223
public void setPercentOfJvmHeap(int percentOfJvmHeap) {

activemq-client/src/main/java/org/apache/activemq/usage/Usage.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ public abstract class Usage<T extends Usage> implements Service {
4242

4343
protected final ReentrantReadWriteLock usageLock = new ReentrantReadWriteLock();
4444
protected final Condition waitForSpaceCondition = usageLock.writeLock().newCondition();
45-
protected int percentUsage;
45+
// volatile so lock-free hot paths (isFull, percent-change detection) can read it without
46+
// taking the usageLock; all writes still happen under the writeLock via setPercentUsage().
47+
protected volatile int percentUsage;
4648
protected T parent;
4749
protected String name;
4850

0 commit comments

Comments
 (0)