@@ -141,6 +141,7 @@ public class AppSecRequestContext implements DataBundle, Closeable {
141141 private boolean respDataPublished ;
142142 private boolean pathParamsPublished ;
143143 private volatile Map <String , Object > derivatives ;
144+ private final Object derivativesLock = new Object ();
144145
145146 private final AtomicBoolean rateLimited = new AtomicBoolean (false );
146147 private volatile boolean throttled ;
@@ -649,9 +650,11 @@ public void close() {
649650 requestHeaders .clear ();
650651 responseHeaders .clear ();
651652 persistentData .clear ();
652- if (derivatives != null ) {
653- derivatives .clear ();
654- derivatives = null ;
653+ synchronized (derivativesLock ) {
654+ if (derivatives != null ) {
655+ derivatives .clear ();
656+ derivatives = null ;
657+ }
655658 }
656659 }
657660 }
@@ -743,10 +746,7 @@ public void reportDerivatives(Map<String, Object> data) {
743746 log .debug ("Reporting derivatives: {}" , data );
744747 if (data == null || data .isEmpty ()) return ;
745748
746- // Store raw derivatives
747- if (derivatives == null ) {
748- derivatives = new HashMap <>();
749- }
749+ Map <String , Object > newDerivatives = new LinkedHashMap <>();
750750
751751 // Process each attribute according to the specification
752752 for (Map .Entry <String , Object > entry : data .entrySet ()) {
@@ -762,7 +762,7 @@ public void reportDerivatives(Map<String, Object> data) {
762762 Object literalValue = config .get ("value" );
763763 if (literalValue != null ) {
764764 // Preserve the original type - don't convert to string
765- derivatives .put (attributeKey , literalValue );
765+ newDerivatives .put (attributeKey , literalValue );
766766 log .debug (
767767 "Added literal attribute: {} = {} (type: {})" ,
768768 attributeKey ,
@@ -781,16 +781,25 @@ else if (config.containsKey("address")) {
781781 Object extractedValue = extractValueFromRequestData (address , keyPath , transformers );
782782 if (extractedValue != null ) {
783783 // For extracted values, convert to string as they come from request data
784- derivatives .put (attributeKey , extractedValue .toString ());
784+ newDerivatives .put (attributeKey , extractedValue .toString ());
785785 log .debug ("Added extracted attribute: {} = {}" , attributeKey , extractedValue );
786786 }
787787 }
788788 } else {
789789 // Handle plain string/numeric values
790- derivatives .put (attributeKey , attributeConfig );
790+ newDerivatives .put (attributeKey , attributeConfig );
791791 log .debug ("Added direct attribute: {} = {}" , attributeKey , attributeConfig );
792792 }
793793 }
794+
795+ if (!newDerivatives .isEmpty ()) {
796+ synchronized (derivativesLock ) {
797+ if (derivatives == null ) {
798+ derivatives = new HashMap <>();
799+ }
800+ derivatives .putAll (newDerivatives );
801+ }
802+ }
794803 }
795804
796805 /**
@@ -943,40 +952,48 @@ public boolean commitDerivatives(TraceSegment traceSegment) {
943952 return false ;
944953 }
945954
955+ Map <String , Object > derivativesSnapshot ;
956+ synchronized (derivativesLock ) {
957+ if (derivatives == null || derivatives .isEmpty ()) {
958+ derivatives = null ;
959+ return true ;
960+ }
961+ derivativesSnapshot = new LinkedHashMap <>(derivatives );
962+ derivatives = null ;
963+ }
964+
946965 // 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 );
966+ for (Map .Entry <String , Object > entry : derivativesSnapshot .entrySet ()) {
967+ String key = entry .getKey ();
968+ Object value = entry .getValue ();
969+
970+ // Handle different value types
971+ if (value instanceof Number ) {
972+ traceSegment .setTagTop (key , (Number ) value );
973+ } else if (value instanceof String ) {
974+ // Try to parse as numeric, otherwise use as string
975+ Number parsedNumber = convertToNumericAttribute ((String ) value );
976+ if (parsedNumber != null ) {
977+ traceSegment .setTagTop (key , parsedNumber );
965978 } else {
966- // Convert other types to string
967- traceSegment .setTagTop (key , value .toString ());
979+ traceSegment .setTagTop (key , value );
968980 }
981+ } else if (value instanceof Boolean ) {
982+ traceSegment .setTagTop (key , value );
983+ } else {
984+ // Convert other types to string
985+ traceSegment .setTagTop (key , value .toString ());
969986 }
970987 }
971988
972- // Clear all attribute maps
973- derivatives = null ;
974989 return true ;
975990 }
976991
977992 // Mainly used for testing and logging
978993 Set <String > getDerivativeKeys () {
979- return derivatives == null ? emptySet () : new HashSet <>(derivatives .keySet ());
994+ synchronized (derivativesLock ) {
995+ return derivatives == null ? emptySet () : new HashSet <>(derivatives .keySet ());
996+ }
980997 }
981998
982999 public boolean isThrottled (RateLimiter rateLimiter ) {
0 commit comments