3636import java .util .concurrent .atomic .AtomicBoolean ;
3737import java .util .concurrent .atomic .AtomicInteger ;
3838import java .util .concurrent .atomic .AtomicIntegerFieldUpdater ;
39+ import java .util .concurrent .atomic .AtomicReference ;
3940import org .slf4j .Logger ;
4041import org .slf4j .LoggerFactory ;
4142
@@ -142,7 +143,7 @@ public class AppSecRequestContext implements DataBundle, Closeable {
142143 private boolean responseBodyPublished ;
143144 private boolean respDataPublished ;
144145 private boolean pathParamsPublished ;
145- private volatile Map <String , Object > derivatives ;
146+ private final AtomicReference < Map <String , Object >> derivatives = new AtomicReference <>() ;
146147
147148 private final AtomicBoolean rateLimited = new AtomicBoolean (false );
148149 private volatile boolean throttled ;
@@ -651,9 +652,9 @@ public void close() {
651652 requestHeaders .clear ();
652653 responseHeaders .clear ();
653654 persistentData .clear ();
655+ final Map <String , Object > derivatives = this .derivatives .getAndSet (null );
654656 if (derivatives != null ) {
655657 derivatives .clear ();
656- derivatives = null ;
657658 }
658659 }
659660 }
@@ -745,54 +746,57 @@ public void reportDerivatives(Map<String, Object> data) {
745746 log .debug ("Reporting derivatives: {}" , data );
746747 if (data == null || data .isEmpty ()) return ;
747748
748- // Store raw derivatives
749- if (derivatives == null ) {
750- derivatives = new HashMap <>();
751- }
752-
753- // Process each attribute according to the specification
754- for (Map .Entry <String , Object > entry : data .entrySet ()) {
755- String attributeKey = entry .getKey ();
756- Object attributeConfig = entry .getValue ();
757-
758- if (attributeConfig instanceof Map ) {
759- @ SuppressWarnings ("unchecked" )
760- Map <String , Object > config = (Map <String , Object >) attributeConfig ;
761-
762- // Check if it's a literal value schema
763- if (config .containsKey ("value" )) {
764- Object literalValue = config .get ("value" );
765- if (literalValue != null ) {
766- // Preserve the original type - don't convert to string
767- derivatives .put (attributeKey , literalValue );
768- log .debug (
769- "Added literal attribute: {} = {} (type: {})" ,
770- attributeKey ,
771- literalValue ,
772- literalValue .getClass ().getSimpleName ());
749+ // Initialize or update derivatives atomically
750+ derivatives .updateAndGet (
751+ current -> {
752+ Map <String , Object > updated = current != null ? new HashMap <>(current ) : new HashMap <>();
753+
754+ // Process each attribute according to the specification
755+ for (Map .Entry <String , Object > entry : data .entrySet ()) {
756+ String attributeKey = entry .getKey ();
757+ Object attributeConfig = entry .getValue ();
758+
759+ if (attributeConfig instanceof Map ) {
760+ @ SuppressWarnings ("unchecked" )
761+ Map <String , Object > config = (Map <String , Object >) attributeConfig ;
762+
763+ // Check if it's a literal value schema
764+ if (config .containsKey ("value" )) {
765+ Object literalValue = config .get ("value" );
766+ if (literalValue != null ) {
767+ // Preserve the original type - don't convert to string
768+ updated .put (attributeKey , literalValue );
769+ log .debug (
770+ "Added literal attribute: {} = {} (type: {})" ,
771+ attributeKey ,
772+ literalValue ,
773+ literalValue .getClass ().getSimpleName ());
774+ }
775+ }
776+ // Check if it's a request data schema
777+ else if (config .containsKey ("address" )) {
778+ String address = (String ) config .get ("address" );
779+ @ SuppressWarnings ("unchecked" )
780+ List <String > keyPath = (List <String >) config .get ("key_path" );
781+ @ SuppressWarnings ("unchecked" )
782+ List <String > transformers = (List <String >) config .get ("transformers" );
783+
784+ Object extractedValue = extractValueFromRequestData (address , keyPath , transformers );
785+ if (extractedValue != null ) {
786+ // For extracted values, convert to string as they come from request data
787+ updated .put (attributeKey , extractedValue .toString ());
788+ log .debug ("Added extracted attribute: {} = {}" , attributeKey , extractedValue );
789+ }
790+ }
791+ } else {
792+ // Handle plain string/numeric values
793+ updated .put (attributeKey , attributeConfig );
794+ log .debug ("Added direct attribute: {} = {}" , attributeKey , attributeConfig );
795+ }
773796 }
774- }
775- // Check if it's a request data schema
776- else if (config .containsKey ("address" )) {
777- String address = (String ) config .get ("address" );
778- @ SuppressWarnings ("unchecked" )
779- List <String > keyPath = (List <String >) config .get ("key_path" );
780- @ SuppressWarnings ("unchecked" )
781- List <String > transformers = (List <String >) config .get ("transformers" );
782797
783- Object extractedValue = extractValueFromRequestData (address , keyPath , transformers );
784- if (extractedValue != null ) {
785- // For extracted values, convert to string as they come from request data
786- derivatives .put (attributeKey , extractedValue .toString ());
787- log .debug ("Added extracted attribute: {} = {}" , attributeKey , extractedValue );
788- }
789- }
790- } else {
791- // Handle plain string/numeric values
792- derivatives .put (attributeKey , attributeConfig );
793- log .debug ("Added direct attribute: {} = {}" , attributeKey , attributeConfig );
794- }
795- }
798+ return updated ;
799+ });
796800 }
797801
798802 /**
@@ -940,14 +944,17 @@ private Object applyTransformers(Object value, List<String> transformers) {
940944 }
941945
942946 public boolean commitDerivatives (TraceSegment traceSegment ) {
943- log .debug ("Committing derivatives: {} for {}" , derivatives , traceSegment );
944947 if (traceSegment == null ) {
945948 return false ;
946949 }
947950
951+ // Get and clear derivatives atomically
952+ Map <String , Object > derivativesToCommit = derivatives .getAndSet (null );
953+ log .debug ("Committing derivatives: {} for {}" , derivativesToCommit , traceSegment );
954+
948955 // Process and commit derivatives directly
949- if (derivatives != null && !derivatives .isEmpty ()) {
950- for (Map .Entry <String , Object > entry : derivatives .entrySet ()) {
956+ if (derivativesToCommit != null && !derivativesToCommit .isEmpty ()) {
957+ for (Map .Entry <String , Object > entry : derivativesToCommit .entrySet ()) {
951958 String key = entry .getKey ();
952959 Object value = entry .getValue ();
953960
@@ -971,14 +978,13 @@ public boolean commitDerivatives(TraceSegment traceSegment) {
971978 }
972979 }
973980
974- // Clear all attribute maps
975- derivatives = null ;
976981 return true ;
977982 }
978983
979984 // Mainly used for testing and logging
980985 Set <String > getDerivativeKeys () {
981- return derivatives == null ? emptySet () : new HashSet <>(derivatives .keySet ());
986+ Map <String , Object > current = derivatives .get ();
987+ return current == null ? emptySet () : new HashSet <>(current .keySet ());
982988 }
983989
984990 public boolean isThrottled (RateLimiter rateLimiter ) {
0 commit comments