Class CircularConcurrentLinkedDeque<E>

java.lang.Object
java.util.AbstractCollection<E>
java.util.concurrent.ConcurrentLinkedDeque<E>
org.mockserver.collections.CircularConcurrentLinkedDeque<E>
All Implemented Interfaces:
Serializable, Iterable<E>, Collection<E>, Deque<E>, Queue<E>

public class CircularConcurrentLinkedDeque<E> extends ConcurrentLinkedDeque<E>
A bounded 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.

Author:
jamesdbloom
See Also: