diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java
index 16affc8ac59..02ea658406b 100644
--- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java
+++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java
@@ -60,6 +60,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.StampedLock;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import org.slf4j.Logger;
@@ -97,6 +98,13 @@ private static class CtxAndAddresses {
final Collection
> addressesOfInterest;
final WafHandle ctx;
+ /**
+ * Read-held while creating a context from {@link #ctx}; write-held while closing it. Keep the
+ * protected section callback-free: StampedLock is not reentrant. This protects context creation
+ * until libddwaf-java locks and checks the handle itself.
+ */
+ final StampedLock handleLock = new StampedLock();
+
private CtxAndAddresses(Collection> addressesOfInterest, WafHandle ctx) {
this.addressesOfInterest = addressesOfInterest;
this.ctx = ctx;
@@ -208,7 +216,13 @@ private void initOrUpdateWafHandle(AppSecModuleConfigurer.Reconfiguration reconf
}
if (prevContextAndAddresses != null) {
- prevContextAndAddresses.ctx.close();
+ // Context creation must finish acquiring native ruleset ownership before retiring the handle.
+ long stamp = prevContextAndAddresses.handleLock.writeLock();
+ try {
+ prevContextAndAddresses.ctx.close();
+ } finally {
+ prevContextAndAddresses.handleLock.unlockWrite(stamp);
+ }
}
reconf.reloadSubscriptions();
@@ -568,8 +582,30 @@ private Waf.ResultWithData doRunWaf(
CtxAndAddresses ctxAndAddr,
GatewayContext gwCtx)
throws AbstractWafException {
- WafContext wafContext =
- reqCtx.getOrCreateWafContext(ctxAndAddr.ctx, wafMetricsEnabled, gwCtx.isRasp);
+ // Existing contexts own their native ruleset, so using one needs no lock.
+ WafContext wafContext = reqCtx.getWafContextIfReady(wafMetricsEnabled, gwCtx.isRasp);
+ if (wafContext == null) {
+ for (; ; ) {
+ long stamp = ctxAndAddr.handleLock.tryReadLock();
+ if (stamp == 0L) {
+ // Avoid waiting for destruction of a retired handle; retry with the current snapshot.
+ ctxAndAddr = ctxAndAddresses.get();
+ continue;
+ }
+ try {
+ // A callback may have captured this snapshot before remote config replaced it; never
+ // create a context from a retired handle.
+ if (ctxAndAddr == ctxAndAddresses.get()) {
+ wafContext =
+ reqCtx.getOrCreateWafContext(ctxAndAddr.ctx, wafMetricsEnabled, gwCtx.isRasp);
+ break;
+ }
+ } finally {
+ ctxAndAddr.handleLock.unlockRead(stamp);
+ }
+ ctxAndAddr = ctxAndAddresses.get();
+ }
+ }
if (wafContext == null) {
// Context closed concurrently with the isWafContextClosed() check in onDataAvailable; skip
// (APPSEC-69085).
diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java
index af817f7eca7..3cfea27eb66 100644
--- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java
+++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java
@@ -393,7 +393,7 @@ public WafContext getOrCreateWafContext(
if (wafContextClosed) {
return null;
}
- if (createMetrics) {
+ if (!metricsReady(createMetrics, isRasp)) {
if (wafMetrics == null) {
this.wafMetrics = new WafMetrics();
}
@@ -410,6 +410,24 @@ public WafContext getOrCreateWafContext(
}
}
+ /**
+ * Returns the request's open {@link WafContext} without locking, or {@code null} when it does not
+ * exist yet, is closed, or still lacks metrics that {@link #getOrCreateWafContext} would create.
+ * On {@code null}, callers fall back to {@link #getOrCreateWafContext}.
+ */
+ public WafContext getWafContextIfReady(boolean createMetrics, boolean isRasp) {
+ WafContext curWafContext = this.wafContext;
+ if (curWafContext == null || wafContextClosed) {
+ return null;
+ }
+ return metricsReady(createMetrics, isRasp) ? curWafContext : null;
+ }
+
+ /** Whether the metrics a WAF call with these flags needs already exist. */
+ private boolean metricsReady(boolean createMetrics, boolean isRasp) {
+ return !createMetrics || (wafMetrics != null && (!isRasp || raspMetrics != null));
+ }
+
public void closeWafContext() {
if (wafContextClosed) {
// Fast path for the common case of redundant close() calls (e.g. the generic fallback
diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/ddwaf/WAFModuleSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/ddwaf/WAFModuleSpecification.groovy
index ec4904c4882..6dd7c4ec92a 100644
--- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/ddwaf/WAFModuleSpecification.groovy
+++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/ddwaf/WAFModuleSpecification.groovy
@@ -209,6 +209,7 @@ class WAFModuleSpecification extends DDSpecification {
rba.blockingContentType == BlockingContentType.JSON
})
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_ as WafHandle, true, false)
2 * tracer.activeSpan()
1 * ctx.reportEvents(_ as Collection)
@@ -242,6 +243,7 @@ class WAFModuleSpecification extends DDSpecification {
rba.statusCode == 403 &&
rba.blockingContentType == BlockingContentType.AUTO
})
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_ as WafHandle, true, false) >> {
wafContext = new WafContext(it[0])
}
@@ -280,6 +282,7 @@ class WAFModuleSpecification extends DDSpecification {
rba.statusCode == 403 &&
rba.blockingContentType == BlockingContentType.AUTO
})
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_ as WafHandle, true, false) >> { wafContext = new WafContext(it[0]) }
2 * tracer.activeSpan()
1 * ctx.reportEvents(_ as Collection)
@@ -304,6 +307,7 @@ class WAFModuleSpecification extends DDSpecification {
rba.statusCode == 403 &&
rba.blockingContentType == BlockingContentType.AUTO
})
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_ as WafHandle, true, false) >> { wafContext = new WafContext(it[0]) }
2 * tracer.activeSpan()
1 * ctx.reportEvents(_ as Collection)
@@ -356,6 +360,7 @@ class WAFModuleSpecification extends DDSpecification {
rba.statusCode == 403 &&
rba.blockingContentType == BlockingContentType.AUTO
})
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_ as WafHandle, true, false) >> { wafContext = new WafContext(it[0]) }
2 * tracer.activeSpan()
1 * ctx.reportEvents(_ as Collection)
@@ -376,6 +381,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_ as WafHandle, true, false) >> { wafContext = new WafContext(it[0]) }
2 * ctx.getWafMetrics()
1 * ctx.isWafContextClosed() >> false
@@ -428,6 +434,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> { wafContext = new WafContext(it[0]) }
2 * tracer.activeSpan()
1 * ctx.reportEvents(_ as Collection)
@@ -450,6 +457,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> { wafContext = new WafContext(it[0]) }
2 * ctx.getWafMetrics()
1 * ctx.isWafContextClosed() >> false
@@ -505,6 +513,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false)
2 * tracer.activeSpan()
1 * ctx.reportEvents({ it.size() == 1 })
@@ -572,6 +581,7 @@ class WAFModuleSpecification extends DDSpecification {
rba.statusCode == 401 &&
rba.blockingContentType == BlockingContentType.AUTO
})
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false)
2 * tracer.activeSpan()
1 * ctx.reportEvents(_ as Collection)
@@ -595,6 +605,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false)
2 * ctx.getWafMetrics() >> metrics
1 * ctx.isWafContextClosed() >> false
@@ -665,6 +676,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false)
2 * ctx.getWafMetrics() >> metrics
1 * ctx.isWafContextClosed() >> false
@@ -693,6 +705,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(false, false)
1 * ctx.getOrCreateWafContext(_, false, false)
2 * ctx.getWafMetrics() >> null
1 * ctx.isWafContextClosed() >> false
@@ -719,6 +732,7 @@ class WAFModuleSpecification extends DDSpecification {
pp.processTraceSegment(segment, ctx, [])
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false)
1 * ctx.closeWafContext()
3 * ctx.getWafMetrics() >> {
@@ -748,6 +762,7 @@ class WAFModuleSpecification extends DDSpecification {
dataListener.onDataAvailable(flow, ctx, ATTACK_BUNDLE, gwCtx)
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false)
2 * ctx.getWafMetrics() >> metrics
1 * ctx.reportEvents(*_)
@@ -773,6 +788,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, _)
1 * ctx.getOrCreateWafContext(_, true, _)
1 * ctx.reportEvents(_ as Collection) >> { event = it[0].iterator().next() }
1 * ctx.reportStackTrace(_ as StackTraceEvent) >> { stackTrace = it[0] }
@@ -808,6 +824,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, _)
1 * ctx.getOrCreateWafContext(_, true, _)
ctx.reportEvents(_ as Collection) >> { event = it[0].iterator().next() }
@@ -849,6 +866,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, _)
1 * ctx.getOrCreateWafContext(_, true, _)
ctx.reportEvents(_ as Collection) >> { event = it[0].iterator().next() }
@@ -868,6 +886,7 @@ class WAFModuleSpecification extends DDSpecification {
dataListener.onDataAvailable(flow, ctx, db, gwCtx)
then:
+ 1 * ctx.getWafContextIfReady(true, _)
1 * ctx.getOrCreateWafContext(_, true, _)
!flow.blocking
}
@@ -895,6 +914,7 @@ class WAFModuleSpecification extends DDSpecification {
dataListener.onDataAvailable(flow, ctx, db, gwCtx)
then:
+ 1 * ctx.getWafContextIfReady(true, _)
1 * ctx.getOrCreateWafContext(_, true, _)
!flow.blocking
}
@@ -908,6 +928,7 @@ class WAFModuleSpecification extends DDSpecification {
dataListener.onDataAvailable(flow, ctx, db, gwCtx)
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false)
2 * ctx.getWafMetrics()
1 * ctx.setWafErrors()
@@ -934,6 +955,7 @@ class WAFModuleSpecification extends DDSpecification {
then:
assert !flow.blocking
1 * ctx.isWafContextClosed()
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false)
2 * ctx.getWafMetrics()
1 * ctx.increaseWafTimeouts()
@@ -970,6 +992,7 @@ class WAFModuleSpecification extends DDSpecification {
then:
assert !flow.blocking
+ 1 * ctx.getWafContextIfReady(true, true)
1 * ctx.getOrCreateWafContext(_, true, true)
1 * ctx.isWafContextClosed()
1 * ctx.getRaspMetrics()
@@ -1007,6 +1030,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false)
1 * ctx.reportEvents(_ as Collection)
1 * ctx.isWafContextClosed()
@@ -1045,6 +1069,7 @@ class WAFModuleSpecification extends DDSpecification {
then:
1 * wafMetricCollector.wafUpdates(_, true)
1 * reconf.reloadSubscriptions()
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false)
2 * tracer.activeSpan()
1 * ctx.reportEvents(_ as Collection)
@@ -1117,6 +1142,7 @@ class WAFModuleSpecification extends DDSpecification {
then: 'no match; rule is disabled'
1 * wafMetricCollector.wafUpdates(_, true)
1 * reconf.reloadSubscriptions()
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> { wafContext = new WafContext(it[0]) }
2 * ctx.getWafMetrics()
1 * ctx.isWafContextClosed() >> false
@@ -1135,6 +1161,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then: 'no match; data was cleared (though rule is no longer disabled)'
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> { wafContext = new WafContext(it[0]) }
1 * ctx.isWafContextClosed() >> false
1 * wafMetricCollector.wafUpdates(_, true)
@@ -1156,6 +1183,7 @@ class WAFModuleSpecification extends DDSpecification {
then: 'now we have match'
1 * wafMetricCollector.wafUpdates(_, true)
1 * reconf.reloadSubscriptions()
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> { wafContext = new WafContext(it[0]) }
2 * tracer.activeSpan()
1 * ctx.reportEvents(_ as Collection)
@@ -1181,6 +1209,7 @@ class WAFModuleSpecification extends DDSpecification {
then: 'nothing again; we disabled the rule'
1 * wafMetricCollector.wafUpdates(_, true)
1 * reconf.reloadSubscriptions()
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> { wafContext = new WafContext(it[0]) }
2 * ctx.getWafMetrics()
1 * ctx.isWafContextClosed() >> false
@@ -1207,6 +1236,7 @@ class WAFModuleSpecification extends DDSpecification {
1 * wafMetricCollector.wafUpdates(null, true)
1 * reconf.reloadSubscriptions()
// no attack
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> {
WafHandle wafHandle = it[0] as WafHandle
wafContext = new WafContext(wafHandle)
@@ -1230,6 +1260,7 @@ class WAFModuleSpecification extends DDSpecification {
1 * wafMetricCollector.wafUpdates(_, true)
1 * reconf.reloadSubscriptions()
// no attack
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> {
WafHandle wafHandle = it[0] as WafHandle
wafContext = new WafContext(wafHandle)
@@ -1254,6 +1285,7 @@ class WAFModuleSpecification extends DDSpecification {
1 * wafMetricCollector.wafUpdates(_, true)
1 * reconf.reloadSubscriptions()
// attack found
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> {
WafHandle wafHandle = it[0] as WafHandle
wafContext = new WafContext(wafHandle)
@@ -1285,6 +1317,7 @@ class WAFModuleSpecification extends DDSpecification {
1 * wafMetricCollector.wafUpdates(_, true)
1 * reconf.reloadSubscriptions()
// no attack
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> {
WafHandle wafHandle = it[0] as WafHandle
wafContext = new WafContext(wafHandle)
@@ -1403,6 +1436,7 @@ class WAFModuleSpecification extends DDSpecification {
dataListener.onDataAvailable(flow, ctx, transientBundle, gwCtx)
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false)
2 * tracer.activeSpan()
1 * ctx.reportEvents(_ as Collection) >> {
@@ -1420,7 +1454,9 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
- 1 * ctx.getOrCreateWafContext(_, true, false)
+ // the context created by the first call is reused without going through creation
+ 1 * ctx.getWafContextIfReady(true, false)
+ 0 * ctx.getOrCreateWafContext(_, _, _)
1 * flow.setAction({ it.blocking })
2 * tracer.activeSpan()
1 * ctx.reportEvents(_ as Collection) >> {
@@ -1499,6 +1535,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_ as WafHandle, true, false) >> { wafContext = new WafContext(it[0]) }
2 * ctx.getWafMetrics()
1 * ctx.isThrottled(null)
@@ -1532,6 +1569,7 @@ class WAFModuleSpecification extends DDSpecification {
})
1 * flow.isBlocking()
1 * ctx.isWafContextClosed() >> false
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_ as WafHandle, true, false) >> { wafContext = new WafContext(it[0]) }
2 * ctx.getWafMetrics()
1 * ctx.isThrottled(null)
@@ -1760,6 +1798,7 @@ class WAFModuleSpecification extends DDSpecification {
then:
(1..2) * ctx.isWafContextClosed() >> false // if UnclassifiedWafException it's called twice
+ 1 * ctx.getWafContextIfReady(true, true)
1 * ctx.getOrCreateWafContext(_, true, true) >> wafContext
1 * wafMetricCollector.raspRuleEval(RuleType.SQL_INJECTION)
1 * wafContext.run(_, _, _) >> { throw createWafException(wafErrorCode as WafErrorCode) }
@@ -1789,6 +1828,7 @@ class WAFModuleSpecification extends DDSpecification {
then:
(1..2) * ctx.isWafContextClosed() >> false // if UnclassifiedWafException it's called twice
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> wafContext
1 * wafContext.run(_, _, _) >> { throw createWafException(wafErrorCode as WafErrorCode) }
1 * wafMetricCollector.wafInit(Waf.LIB_VERSION, _, true)
@@ -2001,6 +2041,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> { wafContext = new WafContext(it[0]) }
2 * ctx.getWafMetrics() >> metrics
1 * ctx.isWafContextClosed() >> false
@@ -2019,6 +2060,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> { wafContext = new WafContext(it[0]) }
2 * ctx.getWafMetrics() >> metrics
1 * ctx.isWafContextClosed() >> false
@@ -2039,6 +2081,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> { wafContext = new WafContext(it[0]) }
2 * ctx.getWafMetrics() >> metrics
1 * ctx.isWafContextClosed() >> false
@@ -2056,6 +2099,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false) >> { wafContext = new WafContext(it[0]) }
2 * ctx.getWafMetrics() >> metrics
1 * ctx.isWafContextClosed() >> false
@@ -2159,6 +2203,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false)
2 * ctx.getWafMetrics() >> metrics
1 * ctx.isWafContextClosed() >> false
@@ -2224,6 +2269,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false)
2 * ctx.getWafMetrics() >> metrics
1 * ctx.isWafContextClosed() >> false
@@ -2436,6 +2482,7 @@ class WAFModuleSpecification extends DDSpecification {
ctx.closeWafContext()
then:
+ 1 * ctx.getWafContextIfReady(true, false)
1 * ctx.getOrCreateWafContext(_, true, false)
2 * ctx.getWafMetrics() >> metrics
1 * ctx.isWafContextClosed() >> false
diff --git a/dd-java-agent/appsec/src/test/java/com/datadog/appsec/ddwaf/WAFModuleHandleReloadRaceTest.java b/dd-java-agent/appsec/src/test/java/com/datadog/appsec/ddwaf/WAFModuleHandleReloadRaceTest.java
new file mode 100644
index 00000000000..c3c6f6bf2fc
--- /dev/null
+++ b/dd-java-agent/appsec/src/test/java/com/datadog/appsec/ddwaf/WAFModuleHandleReloadRaceTest.java
@@ -0,0 +1,370 @@
+package com.datadog.appsec.ddwaf;
+
+import static java.util.Collections.emptyMap;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.datadog.appsec.config.AppSecModuleConfigurer;
+import com.datadog.appsec.config.TraceSegmentPostProcessor;
+import com.datadog.appsec.event.ChangeableFlow;
+import com.datadog.appsec.event.DataListener;
+import com.datadog.appsec.event.data.MapDataBundle;
+import com.datadog.appsec.gateway.AppSecRequestContext;
+import com.datadog.appsec.gateway.GatewayContext;
+import com.datadog.ddwaf.Waf;
+import com.datadog.ddwaf.WafBuilder;
+import com.datadog.ddwaf.WafContext;
+import com.datadog.ddwaf.WafHandle;
+import com.squareup.moshi.Moshi;
+import com.squareup.moshi.Types;
+import datadog.trace.api.telemetry.RuleType;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.StampedLock;
+import okio.Okio;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** Tests handle replacement against context creation without accessing retired native pointers. */
+class WAFModuleHandleReloadRaceTest {
+ private WafHandle initialHandle;
+ private WafBuilder builder;
+ private WAFModule module;
+ private final AppSecRequestContext request = mock(AppSecRequestContext.class);
+ private final WafContext context = mock(WafContext.class);
+ private AppSecModuleConfigurer.SubconfigListener listener;
+ private DataListener callback;
+
+ @BeforeEach
+ void setup() throws Exception {
+ assertTrue(WafInitialization.ONLINE);
+ builder = new WafBuilder();
+ try (InputStream stream = getClass().getResourceAsStream("/test_multi_config.json")) {
+ Map config =
+ new Moshi.Builder()
+ .build()
+ .