2323import java .util .Arrays ;
2424import java .util .Collections ;
2525import java .util .Date ;
26+ import java .util .Iterator ;
2627import java .util .List ;
2728import java .util .Map ;
28- import java .util .Timer ;
29- import java .util .TimerTask ;
3029import java .util .concurrent .ConcurrentHashMap ;
3130import java .util .concurrent .CopyOnWriteArrayList ;
31+ import java .util .concurrent .Future ;
32+ import java .util .concurrent .RejectedExecutionException ;
3233import org .jetbrains .annotations .NotNull ;
3334import org .jetbrains .annotations .Nullable ;
3435
@@ -42,8 +43,9 @@ public final class RateLimiter implements Closeable {
4243 private final @ NotNull Map <DataCategory , @ NotNull Date > sentryRetryAfterLimit =
4344 new ConcurrentHashMap <>();
4445 private final @ NotNull List <IRateLimitObserver > rateLimitObservers = new CopyOnWriteArrayList <>();
45- private @ Nullable Timer timer = null ;
46- private final @ NotNull AutoClosableReentrantLock timerLock = new AutoClosableReentrantLock ();
46+ private final @ NotNull List <Future <?>> notifyObserversFutures = new ArrayList <>();
47+ private final @ NotNull AutoClosableReentrantLock notifyFuturesLock =
48+ new AutoClosableReentrantLock ();
4749
4850 public RateLimiter (
4951 final @ NotNull ICurrentDateProvider currentDateProvider ,
@@ -278,11 +280,11 @@ public void updateRetryAfterLimits(
278280 continue ;
279281 }
280282
281- applyRetryAfterOnlyIfLonger (dataCategory , date );
283+ applyRetryAfterOnlyIfLonger (dataCategory , date , retryAfterMillis );
282284 }
283285 } else {
284286 // if categories are empty, we should apply to "all" categories.
285- applyRetryAfterOnlyIfLonger (DataCategory .All , date );
287+ applyRetryAfterOnlyIfLonger (DataCategory .All , date , retryAfterMillis );
286288 }
287289 }
288290 }
@@ -291,7 +293,7 @@ public void updateRetryAfterLimits(
291293 final long retryAfterMillis = parseRetryAfterOrDefault (retryAfterHeader );
292294 // we dont care if Date is UTC as we just add the relative seconds
293295 final Date date = new Date (currentDateProvider .getCurrentTimeMillis () + retryAfterMillis );
294- applyRetryAfterOnlyIfLonger (DataCategory .All , date );
296+ applyRetryAfterOnlyIfLonger (DataCategory .All , date , retryAfterMillis );
295297 }
296298 }
297299
@@ -300,10 +302,11 @@ public void updateRetryAfterLimits(
300302 *
301303 * @param dataCategory the DataCategory
302304 * @param date the Date to be applied
305+ * @param delayMillis the millis until the rate limit is lifted
303306 */
304307 @ SuppressWarnings ({"JdkObsolete" , "JavaUtilDate" })
305308 private void applyRetryAfterOnlyIfLonger (
306- final @ NotNull DataCategory dataCategory , final @ NotNull Date date ) {
309+ final @ NotNull DataCategory dataCategory , final @ NotNull Date date , final long delayMillis ) {
307310 final Date oldDate = sentryRetryAfterLimit .get (dataCategory );
308311
309312 // only overwrite its previous date if the limit is even longer
@@ -312,19 +315,25 @@ private void applyRetryAfterOnlyIfLonger(
312315
313316 notifyRateLimitObservers ();
314317
315- try (final @ NotNull ISentryLifecycleToken ignored = timerLock .acquire ()) {
316- if (timer == null ) {
317- timer = new Timer (true );
318+ // notify observers again once the rate limit is lifted, using the shared timer executor
319+ // instead of a dedicated Timer thread
320+ try (final @ NotNull ISentryLifecycleToken ignored = notifyFuturesLock .acquire ()) {
321+ final @ NotNull Iterator <Future <?>> iterator = notifyObserversFutures .iterator ();
322+ while (iterator .hasNext ()) {
323+ if (iterator .next ().isDone ()) {
324+ iterator .remove ();
325+ }
326+ }
327+ try {
328+ notifyObserversFutures .add (
329+ options
330+ .getTimerExecutorService ()
331+ .schedule (() -> notifyRateLimitObservers (), delayMillis ));
332+ } catch (RejectedExecutionException e ) {
333+ options
334+ .getLogger ()
335+ .log (SentryLevel .WARNING , "Failed to schedule rate limit lifted notification." , e );
318336 }
319-
320- timer .schedule (
321- new TimerTask () {
322- @ Override
323- public void run () {
324- notifyRateLimitObservers ();
325- }
326- },
327- date );
328337 }
329338 }
330339 }
@@ -364,11 +373,11 @@ public void removeRateLimitObserver(@NotNull final IRateLimitObserver observer)
364373
365374 @ Override
366375 public void close () throws IOException {
367- try (final @ NotNull ISentryLifecycleToken ignored = timerLock .acquire ()) {
368- if (timer != null ) {
369- timer .cancel ();
370- timer = null ;
376+ try (final @ NotNull ISentryLifecycleToken ignored = notifyFuturesLock .acquire ()) {
377+ for (Future <?> future : notifyObserversFutures ) {
378+ future .cancel (false );
371379 }
380+ notifyObserversFutures .clear ();
372381 }
373382 rateLimitObservers .clear ();
374383 }
0 commit comments