1919import java .util .Arrays ;
2020import java .util .Collection ;
2121import java .util .Collections ;
22- import java .util .HashMap ;
2322import java .util .HashSet ;
2423import java .util .Iterator ;
2524import java .util .LinkedHashMap ;
@@ -140,7 +139,8 @@ public class AppSecRequestContext implements DataBundle, Closeable {
140139 private boolean responseBodyPublished ;
141140 private boolean respDataPublished ;
142141 private boolean pathParamsPublished ;
143- private volatile Map <String , Object > derivatives ;
142+ private volatile ConcurrentHashMap <String , Object > derivatives ;
143+ private final Object derivativesSwapLock = new Object ();
144144
145145 private final AtomicBoolean rateLimited = new AtomicBoolean (false );
146146 private volatile boolean throttled ;
@@ -649,10 +649,7 @@ public void close() {
649649 requestHeaders .clear ();
650650 responseHeaders .clear ();
651651 persistentData .clear ();
652- if (derivatives != null ) {
653- derivatives .clear ();
654- derivatives = null ;
655- }
652+ derivatives = null ;
656653 }
657654 }
658655
@@ -743,9 +740,16 @@ public void reportDerivatives(Map<String, Object> data) {
743740 log .debug ("Reporting derivatives: {}" , data );
744741 if (data == null || data .isEmpty ()) return ;
745742
746- // Store raw derivatives
747- if (derivatives == null ) {
748- derivatives = new HashMap <>();
743+ // Ensure derivatives map exists with lock only for initialization check
744+ ConcurrentHashMap <String , Object > map = derivatives ;
745+ if (map == null ) {
746+ synchronized (derivativesSwapLock ) {
747+ map = derivatives ;
748+ if (map == null ) {
749+ map = new ConcurrentHashMap <>();
750+ derivatives = map ;
751+ }
752+ }
749753 }
750754
751755 // Process each attribute according to the specification
@@ -762,7 +766,7 @@ public void reportDerivatives(Map<String, Object> data) {
762766 Object literalValue = config .get ("value" );
763767 if (literalValue != null ) {
764768 // Preserve the original type - don't convert to string
765- derivatives .put (attributeKey , literalValue );
769+ map .put (attributeKey , literalValue );
766770 log .debug (
767771 "Added literal attribute: {} = {} (type: {})" ,
768772 attributeKey ,
@@ -781,13 +785,13 @@ else if (config.containsKey("address")) {
781785 Object extractedValue = extractValueFromRequestData (address , keyPath , transformers );
782786 if (extractedValue != null ) {
783787 // For extracted values, convert to string as they come from request data
784- derivatives .put (attributeKey , extractedValue .toString ());
788+ map .put (attributeKey , extractedValue .toString ());
785789 log .debug ("Added extracted attribute: {} = {}" , attributeKey , extractedValue );
786790 }
787791 }
788792 } else {
789793 // Handle plain string/numeric values
790- derivatives .put (attributeKey , attributeConfig );
794+ map .put (attributeKey , attributeConfig );
791795 log .debug ("Added direct attribute: {} = {}" , attributeKey , attributeConfig );
792796 }
793797 }
@@ -938,45 +942,54 @@ private Object applyTransformers(Object value, List<String> transformers) {
938942 }
939943
940944 public boolean commitDerivatives (TraceSegment traceSegment ) {
941- log .debug ("Committing derivatives: {} for {}" , derivatives , traceSegment );
942945 if (traceSegment == null ) {
943946 return false ;
944947 }
945948
949+ // Atomically swap out the map to iterate safely
950+ ConcurrentHashMap <String , Object > snapshot ;
951+ synchronized (derivativesSwapLock ) {
952+ snapshot = derivatives ;
953+ derivatives = null ;
954+ }
955+
956+ if (snapshot == null || snapshot .isEmpty ()) {
957+ return true ;
958+ }
959+
960+ log .debug ("Committing derivatives: {} for {}" , snapshot , traceSegment );
961+
946962 // Process and commit derivatives directly
947- if (derivatives != null && !derivatives .isEmpty ()) {
948- for (Map .Entry <String , Object > entry : derivatives .entrySet ()) {
949- String key = entry .getKey ();
950- Object value = entry .getValue ();
951-
952- // Handle different value types
953- if (value instanceof Number ) {
954- traceSegment .setTagTop (key , (Number ) value );
955- } else if (value instanceof String ) {
956- // Try to parse as numeric, otherwise use as string
957- Number parsedNumber = convertToNumericAttribute ((String ) value );
958- if (parsedNumber != null ) {
959- traceSegment .setTagTop (key , parsedNumber );
960- } else {
961- traceSegment .setTagTop (key , value );
962- }
963- } else if (value instanceof Boolean ) {
964- traceSegment .setTagTop (key , value );
963+ for (Map .Entry <String , Object > entry : snapshot .entrySet ()) {
964+ String key = entry .getKey ();
965+ Object value = entry .getValue ();
966+
967+ // Handle different value types
968+ if (value instanceof Number ) {
969+ traceSegment .setTagTop (key , (Number ) value );
970+ } else if (value instanceof String ) {
971+ // Try to parse as numeric, otherwise use as string
972+ Number parsedNumber = convertToNumericAttribute ((String ) value );
973+ if (parsedNumber != null ) {
974+ traceSegment .setTagTop (key , parsedNumber );
965975 } else {
966- // Convert other types to string
967- traceSegment .setTagTop (key , value .toString ());
976+ traceSegment .setTagTop (key , value );
968977 }
978+ } else if (value instanceof Boolean ) {
979+ traceSegment .setTagTop (key , value );
980+ } else {
981+ // Convert other types to string
982+ traceSegment .setTagTop (key , value .toString ());
969983 }
970984 }
971985
972- // Clear all attribute maps
973- derivatives = null ;
974986 return true ;
975987 }
976988
977989 // Mainly used for testing and logging
978990 Set <String > getDerivativeKeys () {
979- return derivatives == null ? emptySet () : new HashSet <>(derivatives .keySet ());
991+ ConcurrentHashMap <String , Object > map = derivatives ;
992+ return map == null ? emptySet () : new HashSet <>(map .keySet ());
980993 }
981994
982995 public boolean isThrottled (RateLimiter rateLimiter ) {
0 commit comments