Class InMemoryReplayCache
- All Implemented Interfaces:
ReplayCache
ReplayCache: a per-process, ConcurrentHashMap-backed seen-identifier set with
self-eviction bounded by each record's own expiresAtMs.
Not shareable across process instances by design. Two separate JVMs (or two instances behind a load
balancer) each holding their own InMemoryReplayCache will not see each other's recorded identifiers, so
an identifier consumed against one instance and then submitted again against the other is not caught.
Consumers who need cross-node single-use enforcement must supply a ReplayCache backed by a store shared
across every node instead of relying on this default.
Memory bound (size honestly). An identifier is retained only until its own expiresAtMs, so the
map's steady-state size is bounded by the product of the distinct-identifier submission rate and the
identifier lifetime — i.e. roughly the number of distinct, still-unexpired identifiers outstanding at
once, plus at most one lifetime's worth of already-expired records awaiting the next sweep. Under a high rate
with a long lifetime this is not inherently small; consumers should size accordingly, or supply a store-backed
ReplayCache with server-side expiry. There is deliberately no hard cap that drops
still-unexpired identifiers: doing so would silently weaken replay detection by letting an evicted-but-valid
identifier be submitted again.
Eviction is throttled. Sweeping out already-expired records is an O(n) scan, so it runs at most once per
DEFAULT_SWEEP_INTERVAL_MS rather than on every call (which would make checkAndRecord(java.lang.String, long)
effectively O(n) per invocation under load). The sweep exists purely to bound memory.
Thread-safety. checkAndRecord(String, long) is safe for concurrent invocation: the
check-and-record step is a single ConcurrentHashMap.putIfAbsent(K, V) call, so of any two concurrent calls
with the same identifier, exactly one observes a first-seen (
- Since:
- 10.0.0
-
Nested Class Summary
Nested classes/interfaces inherited from interface org.apache.juneau.commons.concurrent.ReplayCache
ReplayCache.FailMode -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final longDefault minimum interval, in milliseconds, between opportunistic expired-record sweeps. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionbooleancheckAndRecord(String id, long expiresAtMs) Atomically checks whether the specified identifier has been observed before and records it as seen if not.Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods inherited from interface org.apache.juneau.commons.concurrent.ReplayCache
checkAndRecord, checkAndRecord
-
Field Details
-
DEFAULT_SWEEP_INTERVAL_MS
Default minimum interval, in milliseconds, between opportunistic expired-record sweeps.- See Also:
-
-
Constructor Details
-
InMemoryReplayCache
public InMemoryReplayCache()Constructor using thedefaultsweep interval.
-
-
Method Details
-
checkAndRecord
Description copied from interface:ReplayCacheAtomically checks whether the specified identifier has been observed before and records it as seen if not.Implementations own their own timeout. A call that blocks indefinitely (for example a remote store with no client-side deadline) blocks the caller with it. An implementation backed by a network store must bound its own call so a slow/unreachable backend surfaces promptly as a throw (which the caller resolves per its chosen
ReplayCache.FailMode) or a return, never an unbounded stall.- Specified by:
checkAndRecordin interfaceReplayCache- Parameters:
id- The identifier to check. Nevernull .expiresAtMs- The identifier's own absolute expiry, in epoch milliseconds. An implementation may use this to self-evict a record once it can no longer matter.- Returns:
true if this is the first time the identifier has been observed (the caller should proceed);false if it has been observed before (the caller should reject the submission as a replay).
-