This is an automated email from the ASF dual-hosted git repository.
thomasm pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/trunk by this push:
new f879578d5d OAK-12012 Sporadic SessionSaveDelayerConfigTest failure
(#2619)
f879578d5d is described below
commit f879578d5d7659066555cd6345f10cd2c1bf6621
Author: Thomas Mueller <[email protected]>
AuthorDate: Wed Nov 19 10:32:06 2025 +0100
OAK-12012 Sporadic SessionSaveDelayerConfigTest failure (#2619)
---
.../oak/jcr/session/SessionSaveDelayerConfig.java | 17 ++++-
.../jcr/session/SessionSaveDelayerConfigTest.java | 74 +++++++++++++---------
2 files changed, 61 insertions(+), 30 deletions(-)
diff --git
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayerConfig.java
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayerConfig.java
index 88376fd7fd..05b3cfc448 100644
---
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayerConfig.java
+++
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayerConfig.java
@@ -200,10 +200,25 @@ public class SessionSaveDelayerConfig {
this.maxSavesPerSecond = maxSavesPerSecond;
}
+ /**
+ * Get the number of nanoseconds to delay the next operation.
+ *
+ * @return the time in nanoseconds
+ */
public long getDelayNanos() {
+ return getDelayNanos(0);
+ }
+
+ /**
+ * Get the number of nanoseconds to delay the next operation.
+ *
+ * @param timeMillis the injected current time, or 0 to read it using
System.currentTimeMillis() if needed
+ * @return the time in nanoseconds
+ */
+ public long getDelayNanos(long timeMillis) {
long totalDelayNanos = baseDelayNanos;
if (maxSavesPerSecond > 0) {
- long currentTime = System.currentTimeMillis();
+ long currentTime = timeMillis != 0 ? timeMillis :
System.currentTimeMillis();
double intervalMs = 1000.0 / maxSavesPerSecond;
long lastMatchTime = lastMatch.get();
if (lastMatchTime > 0) {
diff --git
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayerConfigTest.java
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayerConfigTest.java
index 2e0120ff6c..3ca1da0434 100644
---
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayerConfigTest.java
+++
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayerConfigTest.java
@@ -68,13 +68,15 @@ public class SessionSaveDelayerConfigTest {
List<SessionSaveDelayerConfig.DelayEntry> entries =
config.getEntries();
assertEquals(2, entries.size());
+ long timeMillis = System.currentTimeMillis();
+
SessionSaveDelayerConfig.DelayEntry first = entries.get(0);
- assertEquals(500_000L, first.getDelayNanos());
+ assertEquals(500_000L, first.getDelayNanos(timeMillis));
assertEquals("worker-\\d+", first.getThreadNamePattern().pattern());
assertNull(first.getStackTracePattern());
SessionSaveDelayerConfig.DelayEntry second = entries.get(1);
- assertEquals(1_000_000L, second.getDelayNanos());
+ assertEquals(1_000_000L, second.getDelayNanos(timeMillis));
assertEquals("thread-.*", second.getThreadNamePattern().pattern());
assertNotNull(second.getStackTracePattern());
assertEquals(".*SomeClass.*", second.getStackTracePattern().pattern());
@@ -137,9 +139,11 @@ public class SessionSaveDelayerConfigTest {
List<SessionSaveDelayerConfig.DelayEntry> entries =
config.getEntries();
// Only the first entry should be valid (has both delay and
threadNameRegex)
assertEquals(1, entries.size());
-
+
+ long timeMillis = System.currentTimeMillis();
+
SessionSaveDelayerConfig.DelayEntry entry = entries.get(0);
- assertEquals(1000_000L, entry.getDelayNanos());
+ assertEquals(1000_000L, entry.getDelayNanos(timeMillis));
assertEquals("thread-.*", entry.getThreadNamePattern().pattern());
assertNull(entry.getStackTracePattern());
}
@@ -217,7 +221,7 @@ public class SessionSaveDelayerConfigTest {
"}";
SessionSaveDelayerConfig config =
SessionSaveDelayerConfig.fromJson(json);
-
+
assertEquals("{\n"
+ " \"entries\": [{\n"
+ " \"delayMillis\": 1.0, \"threadNameRegex\":
\"thread-.*\", \"stackTraceRegex\": \".*SomeClass.*\"\n"
@@ -243,13 +247,15 @@ public class SessionSaveDelayerConfigTest {
List<SessionSaveDelayerConfig.DelayEntry> entries =
config.getEntries();
assertEquals(1, entries.size());
+ long timeMillis = System.currentTimeMillis();
+
SessionSaveDelayerConfig.DelayEntry entry = entries.get(0);
- assertEquals(2_000_000L, entry.getDelayNanos());
+ assertEquals(2_000_000L, entry.getDelayNanos(timeMillis));
// Test case-insensitive thread name matching
assertTrue(entry.matches("pool-1-thread-5", null, "at
com.example.Service.save()"));
assertTrue(entry.matches("POOL-2-THREAD-10", null, "at
com.example.Service.update()"));
-
+
// Test stack trace pattern matching
assertTrue(entry.matches("pool-1-thread-1", null, "at
com.example.Repository.delete(Repository.java:100)"));
assertFalse(entry.matches("pool-1-thread-1", null, "at
com.example.Service.get()"));
@@ -273,8 +279,10 @@ public class SessionSaveDelayerConfigTest {
List<SessionSaveDelayerConfig.DelayEntry> entries =
config.getEntries();
assertEquals(1, entries.size());
+ long timeMillis = System.currentTimeMillis();
+
SessionSaveDelayerConfig.DelayEntry entry = entries.get(0);
- assertEquals(1_000_000L, entry.getDelayNanos());
+ assertEquals(1_000_000L, entry.getDelayNanos(timeMillis));
// Test userDataPattern matching
assertTrue(entry.matches("any-thread", "admin", null));
@@ -365,7 +373,7 @@ public class SessionSaveDelayerConfigTest {
"}";
SessionSaveDelayerConfig config =
SessionSaveDelayerConfig.fromJson(json);
-
+
// Test that first matching entry is used
assertEquals(100_000L, config.getDelayNanos("thread-1", "admin",
null));
assertEquals(200_000L, config.getDelayNanos("thread-1", "guest123",
null));
@@ -446,7 +454,7 @@ public class SessionSaveDelayerConfigTest {
assertFalse(entry.matches("any-thread", "user", null));
}
- @Test
+ @Test
public void testGetDelayNanosWithUserDataPattern() {
String json = "{\n" +
" \"entries\": [\n" +
@@ -463,15 +471,15 @@ public class SessionSaveDelayerConfigTest {
"}";
SessionSaveDelayerConfig config =
SessionSaveDelayerConfig.fromJson(json);
-
+
// When userData matches first entry's pattern
assertEquals(1_000_000L, config.getDelayNanos("thread-1", "admin",
null));
assertEquals(1_000_000L, config.getDelayNanos("thread-1", "admin123",
null));
-
+
// When userData doesn't match first entry but matches second (no
userData pattern)
assertEquals(500_000L, config.getDelayNanos("thread-1", "user", null));
assertEquals(500_000L, config.getDelayNanos("thread-1", "guest",
null));
-
+
// When userData is null, first entry shouldn't match but second should
assertEquals(500_000L, config.getDelayNanos("thread-1", null, null));
}
@@ -498,14 +506,16 @@ public class SessionSaveDelayerConfigTest {
assertEquals(100_000L, entry.getBaseDelayNanos());
assertEquals(2.0, entry.getMaxSavesPerSecond(), 0.001);
+ long timeMillis = System.currentTimeMillis();
+
// First call should only have base delay
- long firstDelay = entry.getDelayNanos();
+ long firstDelay = entry.getDelayNanos(timeMillis);
assertEquals(100_000L, firstDelay);
// Second call immediately should have additional rate limit delay
// With 2 saves per second, minimum interval is 500ms
- long secondDelay = entry.getDelayNanos();
- assertTrue("Second delay should be >= base delay + rate limit delay",
+ long secondDelay = entry.getDelayNanos(timeMillis);
+ assertTrue("Second delay should be >= base delay + rate limit delay,
was " + secondDelay,
secondDelay >= 100_000L + 400_000_000L); // ~500ms in nanos
}
@@ -530,10 +540,12 @@ public class SessionSaveDelayerConfigTest {
SessionSaveDelayerConfig.DelayEntry entry = entries.get(0);
assertEquals(0.0, entry.getMaxSavesPerSecond(), 0.001);
+ long timeMillis = System.currentTimeMillis();
+
// All calls should only have base delay since rate limiting is
disabled
- assertEquals(100_000L, entry.getDelayNanos());
- assertEquals(100_000L, entry.getDelayNanos());
- assertEquals(100_000L, entry.getDelayNanos());
+ assertEquals(100_000L, entry.getDelayNanos(timeMillis));
+ assertEquals(100_000L, entry.getDelayNanos(timeMillis));
+ assertEquals(100_000L, entry.getDelayNanos(timeMillis));
}
@Test
@@ -589,18 +601,20 @@ public class SessionSaveDelayerConfigTest {
SessionSaveDelayerConfig config =
SessionSaveDelayerConfig.fromJson(json);
SessionSaveDelayerConfig.DelayEntry entry = config.getEntries().get(0);
+ long timeMillis = System.currentTimeMillis();
+
// With 10 saves per second, minimum interval is 100ms
- long firstDelay = entry.getDelayNanos();
+ long firstDelay = entry.getDelayNanos(timeMillis);
assertEquals(0L, firstDelay); // No base delay
- long secondDelay = entry.getDelayNanos();
- assertTrue("Should have rate limit delay", secondDelay >=
90_000_000L); // ~100ms in nanos
+ long secondDelay = entry.getDelayNanos(timeMillis);
+ assertTrue("Should have rate limit delay, was " + secondDelay,
secondDelay >= 90_000_000L); // ~100ms in nanos
}
@Test
- public void testRateLimitingCombinedWithUserDataPattern() {
+ public void testRateLimitingCombinedWithUserDataPattern() throws
InterruptedException {
String json = "{\n" +
" \"entries\": [\n" +
" {\n" +
@@ -619,12 +633,14 @@ public class SessionSaveDelayerConfigTest {
assertTrue(entry.matches("thread-1", "admin", null));
assertFalse(entry.matches("thread-1", "user", null));
+ long timeMillis = System.currentTimeMillis();
+
// Rate limiting should work for matching entries
- long firstDelay = entry.getDelayNanos();
+ long firstDelay = entry.getDelayNanos(timeMillis);
assertEquals(100_000L, firstDelay);
- long secondDelay = entry.getDelayNanos();
- assertTrue("Should have rate limit delay", secondDelay >=
1_000_000_000L); // ~1000ms
+ long secondDelay = entry.getDelayNanos(timeMillis);
+ assertTrue("Should have rate limit delay, was " + secondDelay,
secondDelay >= 1_000_000_000L); // ~1000ms
}
@Test
@@ -679,13 +695,13 @@ public class SessionSaveDelayerConfigTest {
"}";
SessionSaveDelayerConfig config =
SessionSaveDelayerConfig.fromJson(json);
-
+
// First call should only have base delay
long firstDelay = config.getDelayNanos("thread-1", null, null);
assertEquals(100_000L, firstDelay);
// Second call should have additional rate limit delay
long secondDelay = config.getDelayNanos("thread-1", null, null);
- assertTrue("Should have rate limit delay", secondDelay >=
400_000_000L);
+ assertTrue("Should have rate limit delay, was " + secondDelay,
secondDelay >= 400_000_000L);
}
-}
\ No newline at end of file
+}
\ No newline at end of file