>From Michael Blow <[email protected]>:

Michael Blow has uploaded this change for review. ( 
https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21185?usp=email )


Change subject: [NO ISSUE][HYR][MISC] Fix Span lifefycle / API issues
......................................................................

[NO ISSUE][HYR][MISC] Fix Span lifefycle / API issues

Ext-ref: MB-71012
Change-Id: I1111786ffbdb4edc7839525e8ef7907147314b0b
---
M 
asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/azure/blob/BlobUtils.java
M 
hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
A 
hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/SpanTest.java
3 files changed, 208 insertions(+), 6 deletions(-)



  git pull ssh://asterix-gerrit.ics.uci.edu:29418/asterixdb 
refs/changes/85/21185/1

diff --git 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/azure/blob/BlobUtils.java
 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/azure/blob/BlobUtils.java
index 8b6b99f..b4caa52 100644
--- 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/azure/blob/BlobUtils.java
+++ 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/azure/blob/BlobUtils.java
@@ -58,6 +58,7 @@
 import org.apache.hyracks.api.exceptions.IWarningCollector;
 import org.apache.hyracks.api.exceptions.SourceLocation;
 import org.apache.hyracks.api.exceptions.Warning;
+import org.apache.hyracks.util.annotations.AiProvenance;

 import com.azure.core.credential.AzureSasCredential;
 import com.azure.core.http.netty.NettyAsyncHttpClientBuilder;
@@ -75,7 +76,6 @@
 import io.netty.handler.ssl.SslContext;
 import io.netty.handler.ssl.SslContextBuilder;
 import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
-import org.apache.hyracks.util.annotations.AiProvenance;
 import reactor.netty.http.client.HttpClient;

 public class BlobUtils {
diff --git 
a/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
 
b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
index 8c8f1ce..a9eede7 100644
--- 
a/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
+++ 
b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/Span.java
@@ -83,7 +83,62 @@
         }
     };

-    public static final Span ELAPSED = start(0, TimeUnit.NANOSECONDS);
+    public static final Span ELAPSED = new Span() {
+        @Override
+        public void reset() {
+            // no-op
+        }
+
+        @Override
+        public long getSpanNanos() {
+            return 0;
+        }
+
+        @Override
+        public long getSpan(TimeUnit unit) {
+            return 0;
+        }
+
+        @Override
+        public boolean elapsed() {
+            return true;
+        }
+
+        @Override
+        public long elapsed(TimeUnit unit) {
+            return Long.MAX_VALUE;
+        }
+
+        @Override
+        public void sleep() {
+            // no-op: already elapsed
+        }
+
+        @Override
+        public void sleep(long sleep, TimeUnit unit) {
+            // no-op: already elapsed
+        }
+
+        @Override
+        public long remaining(TimeUnit unit) {
+            return 0;
+        }
+
+        @Override
+        public void wait(Object monitor) {
+            // no-op: already elapsed
+        }
+
+        @Override
+        public boolean await(CountDownLatch latch) {
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return "<ELAPSED>";
+        }
+    };

     private final long spanNanos;
     private volatile long startNanos;
@@ -114,12 +169,32 @@
         return s;
     }

-    public static Span init(long span, TimeUnit unit) {
-        return new Span(span, unit);
+    /**
+     * Creates a span that is immediately elapsed, analogous to a do/while 
loop: the elapsed condition is true on the
+     * first check, triggering immediate action, after which {@link #reset()} 
begins the countdown for subsequent
+     * iterations. Contrast with {@link #start(long, TimeUnit)}, which is 
analogous to a while/do loop: the span must
+     * actually expire before the elapsed condition becomes true.
+     * <p>
+     * Typical usage: bootstrapping state that has a TTL, where the first 
fetch should happen immediately and
+     * subsequent refreshes should be rate-limited by the span duration.
+     * <pre>
+     *   Span refreshSpan = Span.startElapsed(ttlSeconds, TimeUnit.SECONDS);
+     *   while (running) {
+     *       if (refreshSpan.elapsed()) {
+     *           refresh();
+     *           refreshSpan.reset();
+     *       }
+     *   }
+     * </pre>
+     */
+    public static Span startElapsed(long span, TimeUnit unit) {
+        Span s = new Span(span, unit);
+        s.startNanos = System.nanoTime() - s.spanNanos;
+        return s;
     }

     public boolean elapsed() {
-        return elapsed(TimeUnit.NANOSECONDS) > spanNanos;
+        return elapsed(TimeUnit.NANOSECONDS) >= spanNanos;
     }

     public long elapsed(TimeUnit unit) {
@@ -239,7 +314,7 @@
         if (micros > 0) {
             builder.append(micros).append("us");
         }
-        if (nanos > 0 || builder.length() == 0) {
+        if (nanos > 0 || builder.isEmpty()) {
             builder.append(nanos).append("ns");
         }
         return builder.toString();
diff --git 
a/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/SpanTest.java
 
b/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/SpanTest.java
new file mode 100644
index 0000000..2e8fd4f
--- /dev/null
+++ 
b/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/SpanTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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
+ *
+ *   http://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.hyracks.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.hyracks.util.annotations.AiProvenance;
+import org.junit.Test;
+
+@AiProvenance(agent = AiProvenance.Agent.CLAUDE_SONNET_4_6, tool = 
AiProvenance.Tool.GITHUB_COPILOT, contributionKind = 
AiProvenance.ContributionKind.TEST_GENERATED)
+public class SpanTest {
+
+    // --- Span.ELAPSED ---
+
+    @Test
+    public void elapsedConstant_alwaysElapsed() {
+        assertTrue(Span.ELAPSED.elapsed());
+    }
+
+    @Test
+    public void elapsedConstant_resetIsNoOp() {
+        Span.ELAPSED.reset();
+        assertTrue("ELAPSED.reset() should be a no-op; span should still be 
elapsed", Span.ELAPSED.elapsed());
+    }
+
+    @Test
+    public void elapsedConstant_remainingIsZero() {
+        assertEquals(0, Span.ELAPSED.remaining(TimeUnit.NANOSECONDS));
+        assertEquals(0, Span.ELAPSED.remaining(TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void elapsedConstant_elapsedUnitIsMaxValue() {
+        assertEquals(Long.MAX_VALUE, 
Span.ELAPSED.elapsed(TimeUnit.NANOSECONDS));
+    }
+
+    @Test
+    public void elapsedConstant_awaitReturnsFalseImmediately() throws 
InterruptedException {
+        CountDownLatch latch = new CountDownLatch(1);
+        assertFalse(Span.ELAPSED.await(latch));
+    }
+
+    @Test
+    public void elapsedConstant_toStringIsElapsed() {
+        assertEquals("<ELAPSED>", Span.ELAPSED.toString());
+    }
+
+    // --- Span.startElapsed() ---
+
+    @Test
+    public void startElapsed_immediatelyElapsed() {
+        Span span = Span.startElapsed(5, TimeUnit.SECONDS);
+        assertTrue("startElapsed span should immediately report elapsed()", 
span.elapsed());
+    }
+
+    @Test
+    public void startElapsed_notElapsedAfterReset() {
+        Span span = Span.startElapsed(5, TimeUnit.SECONDS);
+        span.reset();
+        assertFalse("startElapsed span should not be elapsed immediately after 
reset()", span.elapsed());
+    }
+
+    @Test
+    public void startElapsed_elapsedAfterSpanDuration() throws 
InterruptedException {
+        Span span = Span.startElapsed(10, TimeUnit.MILLISECONDS);
+        span.reset();
+        assertFalse(span.elapsed());
+        TimeUnit.MILLISECONDS.sleep(15);
+        assertTrue("startElapsed span should be elapsed after duration has 
passed", span.elapsed());
+    }
+
+    @Test
+    public void startElapsed_remainingIsZeroBeforeReset() {
+        Span span = Span.startElapsed(5, TimeUnit.SECONDS);
+        assertEquals(0, span.remaining(TimeUnit.NANOSECONDS));
+    }
+
+    @Test
+    public void startElapsed_remainingIsPositiveAfterReset() {
+        Span span = Span.startElapsed(5, TimeUnit.SECONDS);
+        span.reset();
+        assertTrue("remaining should be positive after reset", 
span.remaining(TimeUnit.MILLISECONDS) > 0);
+    }
+
+    // --- elapsed() boundary: >= ensures span is elapsed at exactly spanNanos 
---
+
+    @Test
+    public void start_elapsedAtExactlySpanDuration() throws 
InterruptedException {
+        // A span of 0ns should be immediately elapsed after reset (>= 0)
+        Span span = Span.start(0, TimeUnit.NANOSECONDS);
+        assertTrue("0ns span should be elapsed immediately (elapsed >= 
spanNanos)", span.elapsed());
+    }
+
+    // --- Span.INFINITE sanity ---
+
+    @Test
+    public void infiniteConstant_neverElapsed() {
+        assertFalse(Span.INFINITE.elapsed());
+    }
+
+    @Test
+    public void infiniteConstant_remainingIsMaxValue() {
+        assertEquals(Long.MAX_VALUE, 
Span.INFINITE.remaining(TimeUnit.NANOSECONDS));
+    }
+}

--
To view, visit https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21185?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://asterix-gerrit.ics.uci.edu/settings?usp=email

Gerrit-MessageType: newchange
Gerrit-Project: asterixdb
Gerrit-Branch: phoenix
Gerrit-Change-Id: I1111786ffbdb4edc7839525e8ef7907147314b0b
Gerrit-Change-Number: 21185
Gerrit-PatchSet: 1
Gerrit-Owner: Michael Blow <[email protected]>

Reply via email to