This is an automated email from the ASF dual-hosted git repository. thomasm pushed a commit to branch OAK-12012 in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
commit da37c2565500ef78ce99bcc6111115664b900ef5 Author: Thomas Mueller <[email protected]> AuthorDate: Wed Nov 19 08:10:18 2025 +0100 OAK-12012 Sporadic SessionSaveDelayerConfigTest failure --- .../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
