2727import java .util .concurrent .RejectedExecutionHandler ;
2828import java .util .concurrent .ThreadFactory ;
2929import java .util .concurrent .TimeUnit ;
30- import java .util .concurrent .atomic .AtomicBoolean ;
3130import java .util .concurrent .atomic .AtomicLong ;
3231import java .util .concurrent .atomic .LongAccumulator ;
3332import java .util .concurrent .atomic .LongAdder ;
33+ import java .util .concurrent .locks .ReentrantReadWriteLock ;
3434import java .util .function .Function ;
3535import java .util .function .Supplier ;
3636
@@ -260,13 +260,13 @@ public boolean trackingMaxQueueLatency() {
260260 * Can be extended to remember multiple past frames.
261261 */
262262 public static class FramedTimeTracker {
263- final long interval ;
263+ private final long interval ;
264264 private final Supplier <Long > timeNow ;
265+ private final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock ();
265266 private final AtomicLong ongoingTasks = new AtomicLong ();
266267 private final AtomicLong currentFrame = new AtomicLong ();
267268 private final AtomicLong currentTime = new AtomicLong ();
268269 private final AtomicLong previousTime = new AtomicLong ();
269- private final AtomicBoolean updatingFrame = new AtomicBoolean ();
270270
271271 // for testing
272272 public FramedTimeTracker (long intervalNano , Supplier <Long > timeNow ) {
@@ -281,6 +281,10 @@ public FramedTimeTracker(long intervalNano, Supplier<Long> timeNow) {
281281 this .timeNow = System ::nanoTime ;
282282 }
283283
284+ public long interval () {
285+ return interval ;
286+ }
287+
284288 /**
285289 * Update frames to current time. There are no guaranties that it will be invoked frequently.
286290 * For example when there are no tasks and no requests for previousFrameTime.
@@ -296,7 +300,10 @@ private void updateFrame0(long nowTime) {
296300 var now = nowTime / interval ;
297301 var current = currentFrame .get ();
298302 if (current < now ) {
299- if (updatingFrame .compareAndSet (false , true )) {
303+ rwlock .readLock ().unlock ();
304+ rwlock .writeLock ().lock ();
305+ current = currentFrame .get (); // make sure it didnt change during lock acquisition
306+ if (current < now ) {
300307 var tasks = ongoingTasks .get ();
301308 if (current == now - 1 ) {
302309 previousTime .set (currentTime .get ());
@@ -305,12 +312,9 @@ private void updateFrame0(long nowTime) {
305312 }
306313 currentTime .set (tasks * interval );
307314 currentFrame .set (now );
308- updatingFrame .set (false );
309- } else {
310- while (currentFrame .get () != now ) {
311- Thread .onSpinWait ();
312- }
313315 }
316+ rwlock .readLock ().lock ();
317+ rwlock .writeLock ().unlock ();
314318 }
315319 }
316320
@@ -319,28 +323,37 @@ private void updateFrame0(long nowTime) {
319323 * If task finishes sooner than end of interval {@link FramedTimeTracker#endTask()} will deduct remaining time.
320324 */
321325 public void startTask () {
326+ rwlock .readLock ().lock ();
322327 var now = timeNow .get ();
323328 updateFrame0 (now );
324329 ongoingTasks .incrementAndGet ();
325330 currentTime .updateAndGet ((t ) -> t + (currentFrame .get () + 1 ) * interval - now );
331+ rwlock .readLock ().unlock ();
326332 }
327333
328334 /**
329335 * Stop task tracking. We already assumed that task runs till end of frame, here we deduct not used time.
330336 */
331337 public void endTask () {
338+ rwlock .readLock ().lock ();
332339 var now = timeNow .get ();
333340 updateFrame0 (now );
334341 ongoingTasks .decrementAndGet ();
335342 currentTime .updateAndGet ((t ) -> t - (currentFrame .get () + 1 ) * interval + now );
343+ rwlock .readLock ().unlock ();
336344 }
337345
338346 /**
339347 * Returns previous frame total execution time.
340348 */
341349 public long previousFrameTime () {
342- updateFrame0 (timeNow .get ());
343- return previousTime .get ();
350+ try {
351+ rwlock .readLock ().lock ();
352+ updateFrame0 (timeNow .get ());
353+ return previousTime .get ();
354+ } finally {
355+ rwlock .readLock ().unlock ();
356+ }
344357 }
345358 }
346359}
0 commit comments