1818
1919import java .util .ArrayList ;
2020import java .util .List ;
21+ import java .util .concurrent .atomic .AtomicLong ;
2122import java .util .regex .Pattern ;
2223import java .util .regex .PatternSyntaxException ;
2324
@@ -96,10 +97,10 @@ public List<DelayEntry> getEntries() {
9697 return entries ;
9798 }
9899
99- public long getDelayNanos (@ NotNull String threadName , @ Nullable String stackTrace ) {
100+ public long getDelayNanos (@ NotNull String threadName , @ Nullable String userData , @ Nullable String stackTrace ) {
100101 for (DelayEntry d : entries ) {
101- if (d .matches (threadName , stackTrace )) {
102- return d .delayNanos ;
102+ if (d .matches (threadName , userData , stackTrace )) {
103+ return d .getDelayNanos () ;
103104 }
104105 }
105106 return 0 ;
@@ -109,7 +110,9 @@ public long getDelayNanos(@NotNull String threadName, @Nullable String stackTrac
109110 private static DelayEntry parseDelayEntry (JsonObject entryObj ) {
110111 String delayMillis = entryObj .getProperties ().get ("delayMillis" );
111112 String threadNameRegex = entryObj .getProperties ().get ("threadNameRegex" );
113+ String userDataRegex = entryObj .getProperties ().get ("userDataRegex" );
112114 String stackTraceRegex = entryObj .getProperties ().get ("stackTraceRegex" );
115+ String maxSavesPerSecond = entryObj .getProperties ().get ("maxSavesPerSecond" );
113116 if (delayMillis == null || threadNameRegex == null ) {
114117 LOG .warn ("Skipping entry with missing required fields (delay or threadNameRegex)" );
115118 return null ;
@@ -120,14 +123,26 @@ private static DelayEntry parseDelayEntry(JsonObject entryObj) {
120123 LOG .warn ("Skipping entry with negative delay" );
121124 return null ;
122125 }
126+ double maxSaves = 0.0 ;
127+ if (maxSavesPerSecond != null ) {
128+ maxSaves = Double .parseDouble (maxSavesPerSecond );
129+ if (maxSaves < 0 ) {
130+ LOG .warn ("Skipping entry with negative maxSavesPerSecond" );
131+ return null ;
132+ }
133+ }
123134 Pattern threadPattern = Pattern .compile (JsopTokenizer .decodeQuoted (threadNameRegex ));
124135 Pattern stackPattern = null ;
125136 if (stackTraceRegex != null ) {
126137 stackPattern = Pattern .compile (JsopTokenizer .decodeQuoted (stackTraceRegex ));
127138 }
128- return new DelayEntry (delay , threadPattern , stackPattern );
139+ Pattern userDataPattern = null ;
140+ if (userDataRegex != null ) {
141+ userDataPattern = Pattern .compile (JsopTokenizer .decodeQuoted (userDataRegex ));
142+ }
143+ return new DelayEntry (delay , threadPattern , userDataPattern , stackPattern , maxSaves );
129144 } catch (NumberFormatException e ) {
130- LOG .warn ("Skipping entry with invalid delay value: {}" , delayMillis );
145+ LOG .warn ("Skipping entry with invalid delay value or maxSavesPerSecond : {}" , e . getMessage () );
131146 return null ;
132147 } catch (PatternSyntaxException e ) {
133148 LOG .warn ("Skipping entry with invalid regex pattern: {}" , e .getMessage ());
@@ -170,18 +185,45 @@ public static String getCurrentStackTrace() {
170185 }
171186
172187 public static class DelayEntry {
173- private final long delayNanos ;
188+ private final long baseDelayNanos ;
174189 private final Pattern threadNamePattern ;
175190 private final Pattern stackTracePattern ;
191+ private final Pattern userDataPattern ;
192+ private final double maxSavesPerSecond ;
193+ private final AtomicLong lastMatch = new AtomicLong (0 );
176194
177- public DelayEntry (double delayMillis , @ NotNull Pattern threadNamePattern , @ Nullable Pattern stackTracePattern ) {
178- this .delayNanos = (long ) (delayMillis * 1_000_000 );
195+ public DelayEntry (double delayMillis , @ NotNull Pattern threadNamePattern , @ Nullable Pattern userDataPattern , @ Nullable Pattern stackTracePattern , double maxSavesPerSecond ) {
196+ this .baseDelayNanos = (long ) (delayMillis * 1_000_000 );
179197 this .threadNamePattern = threadNamePattern ;
198+ this .userDataPattern = userDataPattern ;
180199 this .stackTracePattern = stackTracePattern ;
200+ this .maxSavesPerSecond = maxSavesPerSecond ;
181201 }
182202
183203 public long getDelayNanos () {
184- return delayNanos ;
204+ long totalDelayNanos = baseDelayNanos ;
205+ if (maxSavesPerSecond > 0 ) {
206+ long currentTime = System .currentTimeMillis ();
207+ double intervalMs = 1000.0 / maxSavesPerSecond ;
208+ long lastMatchTime = lastMatch .get ();
209+ if (lastMatchTime > 0 ) {
210+ long nextAllowedTime = lastMatchTime + (long ) intervalMs ;
211+ if (currentTime < nextAllowedTime ) {
212+ long rateLimitDelayMs = nextAllowedTime - currentTime ;
213+ totalDelayNanos += rateLimitDelayMs * 1_000_000 ;
214+ }
215+ }
216+ lastMatch .set (currentTime );
217+ }
218+ return totalDelayNanos ;
219+ }
220+
221+ public long getBaseDelayNanos () {
222+ return baseDelayNanos ;
223+ }
224+
225+ public double getMaxSavesPerSecond () {
226+ return maxSavesPerSecond ;
185227 }
186228
187229 @ NotNull
@@ -194,10 +236,23 @@ public Pattern getStackTracePattern() {
194236 return stackTracePattern ;
195237 }
196238
197- boolean matches (@ NotNull String threadName , @ Nullable String stackTrace ) {
239+ @ Nullable
240+ public Pattern getUserDataPattern () {
241+ return userDataPattern ;
242+ }
243+
244+ boolean matches (@ NotNull String threadName , @ Nullable String userData , @ Nullable String stackTrace ) {
198245 if (!threadNamePattern .matcher (threadName ).matches ()) {
199246 return false ;
200247 }
248+ if (userDataPattern != null ) {
249+ if (userData == null ) {
250+ return false ;
251+ }
252+ if (!userDataPattern .matcher (userData ).find ()) {
253+ return false ;
254+ }
255+ }
201256 if (stackTracePattern != null ) {
202257 if (stackTrace == null ) {
203258 stackTrace = SessionSaveDelayerConfig .getCurrentStackTrace ();
@@ -214,12 +269,18 @@ public String toString() {
214269
215270 public JsopBuilder toJson (JsopBuilder json ) {
216271 json .object ();
217- double delayMillis = delayNanos / 1_000_000.0 ;
272+ double delayMillis = baseDelayNanos / 1_000_000.0 ;
218273 json .key ("delayMillis" ).encodedValue (Double .toString (delayMillis ));
219274 json .key ("threadNameRegex" ).value (threadNamePattern .pattern ());
275+ if (userDataPattern != null ) {
276+ json .key ("userDataRegex" ).value (userDataPattern .pattern ());
277+ }
220278 if (stackTracePattern != null ) {
221279 json .key ("stackTraceRegex" ).value (stackTracePattern .pattern ());
222280 }
281+ if (maxSavesPerSecond > 0 ) {
282+ json .key ("maxSavesPerSecond" ).encodedValue (Double .toString (maxSavesPerSecond ));
283+ }
223284 return json .endObject ();
224285 }
225286
0 commit comments