Class CircularConcurrentLinkedDeque<E>
- All Implemented Interfaces:
Serializable,Iterable<E>,Collection<E>,Deque<E>,Queue<E>
ConcurrentLinkedDeque that evicts the oldest element(s) once it reaches
maxSize, invoking an optional callback on each evicted element.
Why the explicit size counter: ConcurrentLinkedDeque.size() is
documented as an O(n) operation (it walks the whole list). The eviction check runs
on every add(E)/offer(E), so relying on super.size() made each insertion
O(n) once the deque was full — the hot path for MockServer's request/event log. Under a
sustained request load this manifested as CPU usage that climbed as the log filled and stayed
high (GitHub issue #2329). An AtomicInteger maintained by every mutating method makes
size() and the eviction check O(1).
The counter is kept consistent by every size-changing method on this class
(add(E), offer(E), addAll(java.util.Collection<? extends E>) (via add), remove(java.lang.Object),
removeItem(E), clear(), and the internal eviction). Callers must mutate the deque
only through these methods (MockServer's MockServerEventLog does); direct use of other
inherited bulk mutators is not supported by this subclass.
Optional byte budget: in addition to the element-count bound, an optional
maxBytes budget can be supplied together with a weigher (via the 4-arg
constructor). Each element's weight is measured by the weigher on insertion and accumulated into
totalBytes; whenever an insertion would push the running total over maxBytes the
oldest elements are evicted first until it fits (or the deque is empty). This caps the heap held
by the event log when individual entries are large (e.g. big LLM-capture bodies) rather than only
by entry count. A single element whose weight alone exceeds maxBytes is still retained —
the byte-eviction loop stops once the deque is empty so we never reject the incoming element. The
budget is disabled when maxBytes <= 0 or the weigher is null, in which case the
deque behaves exactly as the count-bounded version.
Eviction accounting: getEvictedCount() reports how many elements this
deque has silently discarded to stay within its bounds. This deque is the only layer that can
distinguish an eviction (evidence lost because the buffer was full) from an intentional
clear() or a targeted removeItem(Object) — both of which invoke the same
eviction callback but are deliberate, not lossy. Only pollAndEvict() increments the
counter; clear() resets it, because after an explicit clear the caller has declared the
previous contents irrelevant. MockServer's event log surfaces this count so a verification can
tell "this never happened" apart from "the evidence was discarded".
- Author:
- jamesdbloom
- See Also:
-
Constructor Summary
ConstructorsConstructorDescriptionCircularConcurrentLinkedDeque(int maxSize, long maxBytes, ToLongFunction<E> weigher, Consumer<E> onEvictCallback) CircularConcurrentLinkedDeque(int maxSize, Consumer<E> onEvictCallback) -
Method Summary
Modifier and TypeMethodDescriptionbooleanbooleanaddAll(Collection<? extends E> collection) voidclear()longNumber of elements silently discarded to stay withinmaxSize/maxBytessince construction or the lastclear().booleanisEmpty()booleanbooleanDeprecated.use removeItem insteadbooleanremoveItem(E e) voidForget past evictions without touching the contents.voidsetMaxBytes(long maxBytes) Resize the optional byte budget.voidsetMaxSize(int maxSize) Resize the element-count bound.intsize()O(1) size, backed by an internal counter (unlikeConcurrentLinkedDeque.size()which is O(n)).Methods inherited from class java.util.concurrent.ConcurrentLinkedDeque
addFirst, addLast, contains, descendingIterator, element, forEach, getFirst, getLast, iterator, offerFirst, offerLast, peek, peekFirst, peekLast, poll, pollFirst, pollLast, pop, push, remove, removeAll, removeFirst, removeFirstOccurrence, removeIf, removeLast, removeLastOccurrence, retainAll, spliterator, toArray, toArray, toStringMethods inherited from class java.util.AbstractCollection
containsAllMethods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, waitMethods inherited from interface java.util.Collection
containsAll, equals, hashCode, parallelStream, stream, toArray
-
Constructor Details
-
CircularConcurrentLinkedDeque
-
CircularConcurrentLinkedDeque
public CircularConcurrentLinkedDeque(int maxSize, long maxBytes, ToLongFunction<E> weigher, Consumer<E> onEvictCallback)
-
-
Method Details
-
setMaxSize
public void setMaxSize(int maxSize) Resize the element-count bound. A SHRINK takes effect immediately: the oldest elements are evicted (firing the eviction callback, exactly as an overflow eviction would) until the deque fits the new bound, rather than waiting for the nextadd(E). This is what makes a livemaxLogEntrieschange viaPUT /mockserver/configurationrelease memory at once instead of only after further traffic. -
setMaxBytes
public void setMaxBytes(long maxBytes) Resize the optional byte budget. LikesetMaxSize(int)a shrink takes effect immediately, evicting the oldest elements until the running byte total fits the new budget. No-op accounting when the budget is disabled (maxBytes <= 0or no weigher). -
size
public int size()O(1) size, backed by an internal counter (unlikeConcurrentLinkedDeque.size()which is O(n)).- Specified by:
sizein interfaceCollection<E>- Specified by:
sizein interfaceDeque<E>- Overrides:
sizein classConcurrentLinkedDeque<E>
-
isEmpty
public boolean isEmpty()- Specified by:
isEmptyin interfaceCollection<E>- Overrides:
isEmptyin classConcurrentLinkedDeque<E>
-
add
-
addAll
- Specified by:
addAllin interfaceCollection<E>- Specified by:
addAllin interfaceDeque<E>- Overrides:
addAllin classConcurrentLinkedDeque<E>
-
offer
-
getEvictedCount
public long getEvictedCount()Number of elements silently discarded to stay withinmaxSize/maxBytessince construction or the lastclear(). A non-zero value means this deque no longer holds a complete record of what was added to it — any consumer that reasons about absence (e.g. a "this never happened" verification) must treat its answer as unreliable. -
resetEvictedCount
public void resetEvictedCount()Forget past evictions without touching the contents. Used when a caller declares everything recorded so far irrelevant but does not (or cannot) physically empty the deque — e.g. MockServer's "clear everything" path, which tombstones entries rather than removing them. Without this, one rollover would taint every later query for the lifetime of the process. -
clear
public void clear()- Specified by:
clearin interfaceCollection<E>- Overrides:
clearin classConcurrentLinkedDeque<E>
-
remove
Deprecated.use removeItem instead- Specified by:
removein interfaceCollection<E>- Specified by:
removein interfaceDeque<E>- Overrides:
removein classConcurrentLinkedDeque<E>
-
removeItem
-