This is an automated email from the ASF dual-hosted git repository. kturner pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/main by this push: new d64bd908d4 adds a new count down timer (#4796) d64bd908d4 is described below commit d64bd908d4eb815ceb7b7570666d48f1ff76fac5 Author: Keith Turner <ktur...@apache.org> AuthorDate: Tue Aug 20 14:21:45 2024 -0700 adds a new count down timer (#4796) Adds a class used to mesure the amount of elapsed time left from an initial duration. Co-authored-by: Dom G. <domgargu...@apache.org> --- .../org/apache/accumulo/core/fate/ZooStore.java | 11 ++- .../apache/accumulo/core/util/CountDownTimer.java | 94 +++++++++++++++++++++ .../accumulo/core/util/CountDownTimerTest.java | 97 ++++++++++++++++++++++ 3 files changed, 196 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/apache/accumulo/core/fate/ZooStore.java b/core/src/main/java/org/apache/accumulo/core/fate/ZooStore.java index c3de5f29df..ac4bf7f876 100644 --- a/core/src/main/java/org/apache/accumulo/core/fate/ZooStore.java +++ b/core/src/main/java/org/apache/accumulo/core/fate/ZooStore.java @@ -43,8 +43,8 @@ import java.util.Set; import org.apache.accumulo.core.fate.zookeeper.ZooReaderWriter; import org.apache.accumulo.core.fate.zookeeper.ZooUtil.NodeExistsPolicy; import org.apache.accumulo.core.fate.zookeeper.ZooUtil.NodeMissingPolicy; +import org.apache.accumulo.core.util.CountDownTimer; import org.apache.accumulo.core.util.FastFormat; -import org.apache.accumulo.core.util.time.NanoTime; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException.NoNodeException; import org.apache.zookeeper.KeeperException.NodeExistsException; @@ -64,7 +64,7 @@ public class ZooStore<T> implements TStore<T> { private ZooReaderWriter zk; private String lastReserved = ""; private Set<Long> reserved; - private Map<Long,NanoTime> deferred; + private Map<Long,CountDownTimer> deferred; private long statusChangeEvents = 0; private int reservationsWaiting = 0; @@ -164,7 +164,7 @@ public class ZooStore<T> implements TStore<T> { } if (deferred.containsKey(tid)) { - if (deferred.get(tid).elapsed().compareTo(Duration.ZERO) > 0) { + if (deferred.get(tid).isExpired()) { deferred.remove(tid); } else { continue; @@ -203,9 +203,8 @@ public class ZooStore<T> implements TStore<T> { if (deferred.isEmpty()) { this.wait(5000); } else { - var now = NanoTime.now(); long minWait = deferred.values().stream() - .mapToLong(nanoTime -> nanoTime.subtract(now).toMillis()).min().orElseThrow(); + .mapToLong(timer -> timer.timeLeft(MILLISECONDS)).min().orElseThrow(); if (minWait > 0) { this.wait(Math.min(minWait, 5000)); } @@ -285,7 +284,7 @@ public class ZooStore<T> implements TStore<T> { } if (deferTime.compareTo(Duration.ZERO) > 0) { - deferred.put(tid, NanoTime.nowPlus(deferTime)); + deferred.put(tid, CountDownTimer.startNew(deferTime)); } this.notifyAll(); diff --git a/core/src/main/java/org/apache/accumulo/core/util/CountDownTimer.java b/core/src/main/java/org/apache/accumulo/core/util/CountDownTimer.java new file mode 100644 index 0000000000..3c7c3792c1 --- /dev/null +++ b/core/src/main/java/org/apache/accumulo/core/util/CountDownTimer.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.core.util; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +import com.google.common.base.Preconditions; + +/** + * A utility class that tracks the time remaining from an initial duration. It allows the caller to + * check how much time is left on the timer and if the countdown has expired. + * <p> + * Example usage: + * + * <pre> + * CountDownTimer timer = CountDownTimer.startNew(Duration.ofMillis(100)); + * Thread.sleep(10); + * long timeLeft = timer.timeLeft(TimeUnit.MILLISECONDS); // approximately 90ms remaining + * boolean expired = timer.isExpired(); // false + * Thread.sleep(100); + * expired = timer.isExpired(); // true + * </pre> + */ +public class CountDownTimer { + private final long startNanos; + private final long durationNanos; + + private CountDownTimer(long durationNanos) { + this.startNanos = System.nanoTime(); + this.durationNanos = durationNanos; + } + + /** + * Starts a new countdown timer with the specified duration. + * + * @param duration the countdown duration, must be non-negative. + */ + public static CountDownTimer startNew(Duration duration) { + Preconditions.checkArgument(!duration.isNegative()); + return new CountDownTimer(duration.toNanos()); + } + + /** + * Starts a new countdown timer with the specified duration. + * + * @param duration the countdown duration, must be non-negative. + * @param unit the time unit of the duration. + */ + public static CountDownTimer startNew(long duration, TimeUnit unit) { + Preconditions.checkArgument(duration >= 0); + return new CountDownTimer(unit.toNanos(duration)); + } + + /** + * @param unit the desired {@link TimeUnit} for the returned time. + * @return the remaining time in the specified unit, or zero if expired. + */ + public long timeLeft(TimeUnit unit) { + var elapsed = (System.nanoTime() - startNanos); + var timeLeft = durationNanos - elapsed; + if (timeLeft < 0) { + timeLeft = 0; + } + + return unit.convert(timeLeft, TimeUnit.NANOSECONDS); + } + + /** + * Checks if the countdown timer has expired. + * + * @return true if the elapsed time since creation is greater than or equals to the initial + * duration, otherwise return false. + */ + public boolean isExpired() { + return timeLeft(TimeUnit.NANOSECONDS) == 0; + } +} diff --git a/core/src/test/java/org/apache/accumulo/core/util/CountDownTimerTest.java b/core/src/test/java/org/apache/accumulo/core/util/CountDownTimerTest.java new file mode 100644 index 0000000000..b0ce9953a6 --- /dev/null +++ b/core/src/test/java/org/apache/accumulo/core/util/CountDownTimerTest.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.core.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +public class CountDownTimerTest { + @Test + public void testCountDownTimer() throws Exception { + + var totalTimer = Timer.startNew(); + + var cdTimer1 = CountDownTimer.startNew(Duration.ofMillis(100)); + Thread.sleep(10); + var cdTimer2 = CountDownTimer.startNew(100, TimeUnit.MILLISECONDS); + Thread.sleep(10); + var cdTimer3 = CountDownTimer.startNew(Duration.ofMillis(100)); + Thread.sleep(10); + + boolean expired1 = cdTimer1.isExpired(); + boolean expired2 = cdTimer1.isExpired(); + boolean expired3 = cdTimer1.isExpired(); + + var left3 = cdTimer3.timeLeft(TimeUnit.MILLISECONDS); + var left2 = cdTimer2.timeLeft(TimeUnit.MILLISECONDS); + var left1 = cdTimer1.timeLeft(TimeUnit.MILLISECONDS); + + var elapsed = totalTimer.elapsed(); + + assertTrue(left3 <= 90); + assertTrue(left2 <= 80); + assertTrue(left1 <= 70); + + assertTrue(Math.max(left3 - 10, 0) >= left2); + assertTrue(Math.max(left2 - 10, 0) >= left1); + assertTrue(left1 >= Duration.ofMillis(100).minus(elapsed).toMillis(), + "left1:" + left1 + " elapsed:" + elapsed); + assertTrue(left1 >= 0); + + if (left1 > 0) { + assertFalse(expired1); + } else { + assertTrue(expired1); + } + + if (left2 > 0) { + assertFalse(expired2); + } else { + assertTrue(expired2); + } + + if (left3 > 0) { + assertFalse(expired3); + } else { + assertTrue(expired3); + } + + Thread.sleep(92); + assertEquals(0, cdTimer1.timeLeft(TimeUnit.MILLISECONDS)); + assertEquals(0, cdTimer2.timeLeft(TimeUnit.MILLISECONDS)); + assertEquals(0, cdTimer3.timeLeft(TimeUnit.MILLISECONDS)); + + assertTrue(cdTimer1.isExpired()); + assertTrue(cdTimer2.isExpired()); + assertTrue(cdTimer3.isExpired()); + } + + @Test + public void testNegative() { + assertThrows(IllegalArgumentException.class, + () -> CountDownTimer.startNew(Duration.ofMillis(-1))); + } +}