This is an automated email from the ASF dual-hosted git repository.

reschke 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 7c96ca531a Revert "OAK-11766 Write Throttling Mechanism - 
Session.save() delay (#2339)"
7c96ca531a is described below

commit 7c96ca531a1f91c10423ad311a04aa28ace5bc8b
Author: Julian Reschke <[email protected]>
AuthorDate: Tue Jun 24 14:51:33 2025 +0100

    Revert "OAK-11766 Write Throttling Mechanism - Session.save() delay (#2339)"
    
    This reverts commit 201c1c894f233b16ed0559011fee32d21b0ce257.
---
 .../oak/api/jmx/RepositoryManagementMBean.java     |  16 -
 .../jackrabbit/oak/api/jmx/package-info.java       |   2 +-
 .../oak/management/RepositoryManager.java          |  11 -
 .../oak/jcr/delegate/SessionDelegate.java          |   9 +-
 .../oak/jcr/repository/RepositoryImpl.java         |   6 +-
 .../oak/jcr/session/SessionSaveDelayer.java        | 136 ----
 .../oak/jcr/session/SessionSaveDelayerConfig.java  | 288 ---------
 .../oak/jcr/delegate/AbstractDelegatorTest.java    |   4 +-
 .../jcr/session/SessionSaveDelayerConfigTest.java  | 691 ---------------------
 .../oak/jcr/session/SessionSaveDelayerTest.java    | 401 ------------
 10 files changed, 4 insertions(+), 1560 deletions(-)

diff --git 
a/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/RepositoryManagementMBean.java
 
b/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/RepositoryManagementMBean.java
index ae326dbc69..9c879ef4e3 100644
--- 
a/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/RepositoryManagementMBean.java
+++ 
b/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/RepositoryManagementMBean.java
@@ -263,20 +263,4 @@ public interface RepositoryManagementMBean {
     @Description("Refresh all currently open sessions")
     TabularData refreshAllSessions();
 
-    /**
-     * Get the Session.save() delay configuration.
-     *
-     * @return the configuration
-     */
-    @Description("The Session.save() delay configuration")
-    String getSessionSaveDelayerConfig();
-
-    /**
-     * Set the Session.save() delay configuration.
-     *
-     * @param config the new configuration
-     */
-    @Description("The Session.save() delay configuration")
-    void setSessionSaveDelayerConfig(String config);
-
 }
diff --git 
a/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/package-info.java 
b/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/package-info.java
index 47361eda97..678163b257 100644
--- a/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/package-info.java
+++ b/oak-api/src/main/java/org/apache/jackrabbit/oak/api/jmx/package-info.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-@Version("4.15.0")
+@Version("4.14.0")
 package org.apache.jackrabbit.oak.api.jmx;
 
 import org.osgi.annotation.versioning.Version;
diff --git 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/management/RepositoryManager.java
 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/management/RepositoryManager.java
index c465d270f0..738b2bde8f 100644
--- 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/management/RepositoryManager.java
+++ 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/management/RepositoryManager.java
@@ -56,7 +56,6 @@ import org.jetbrains.annotations.NotNull;
  */
 public class RepositoryManager extends AnnotatedStandardMBean implements 
RepositoryManagementMBean {
     private final Whiteboard whiteboard;
-    private String sessionSaveConfig;
 
     public RepositoryManager(@NotNull Whiteboard whiteboard) {
         super(RepositoryManagementMBean.class);
@@ -278,14 +277,4 @@ public class RepositoryManager extends 
AnnotatedStandardMBean implements Reposit
             }
         }));
     }
-
-    @Override
-    public String getSessionSaveDelayerConfig() {
-        return sessionSaveConfig;
-    }
-
-    @Override
-    public void setSessionSaveDelayerConfig(String config) {
-        this.sessionSaveConfig = config;
-    }
 }
diff --git 
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java
 
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java
index 34c3c70fb6..20b0b702b6 100644
--- 
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java
+++ 
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java
@@ -54,7 +54,6 @@ import org.apache.jackrabbit.oak.jcr.observation.EventFactory;
 import org.apache.jackrabbit.oak.jcr.session.RefreshStrategy;
 import org.apache.jackrabbit.oak.jcr.session.RefreshStrategy.Composite;
 import org.apache.jackrabbit.oak.jcr.session.SessionNamespaces;
-import org.apache.jackrabbit.oak.jcr.session.SessionSaveDelayer;
 import org.apache.jackrabbit.oak.jcr.session.SessionStats;
 import org.apache.jackrabbit.oak.jcr.session.SessionStats.Counters;
 import org.apache.jackrabbit.oak.jcr.session.operation.SessionOperation;
@@ -139,8 +138,6 @@ public class SessionDelegate {
 
     private final SessionNamespaces namespaces;
 
-    private final SessionSaveDelayer sessionSaveDelayer;
-
     /**
      * Create a new session delegate for a {@code ContentSession}. The refresh 
behaviour of the
      * session is governed by the value of the {@code refreshInterval} 
argument: if the session
@@ -153,7 +150,6 @@ public class SessionDelegate {
      * @param securityProvider the security provider
      * @param refreshStrategy  the refresh strategy used for auto refreshing 
this session
      * @param statisticManager the statistics manager for tracking session 
operations
-     * @param sessionSaveDelayer the session save delay mechanism
      */
     public SessionDelegate(
             @NotNull ContentSession contentSession,
@@ -161,8 +157,7 @@ public class SessionDelegate {
             @NotNull RefreshStrategy refreshStrategy,
             @NotNull ThreadLocal<Long> threadSaveCount,
             @NotNull StatisticManager statisticManager,
-            @NotNull Clock clock,
-            @NotNull SessionSaveDelayer sessionSaveDelayer) {
+            @NotNull Clock clock) {
         this.contentSession = requireNonNull(contentSession);
         this.securityProvider = requireNonNull(securityProvider);
         this.root = contentSession.getLatestRoot();
@@ -181,7 +176,6 @@ public class SessionDelegate {
         readDuration = statisticManager.getTimer(SESSION_READ_DURATION);
         writeCounter = statisticManager.getMeter(SESSION_WRITE_COUNTER);
         writeDuration = statisticManager.getTimer(SESSION_WRITE_DURATION);
-        this.sessionSaveDelayer = sessionSaveDelayer;
     }
 
     @NotNull
@@ -398,7 +392,6 @@ public class SessionDelegate {
         if (userData != null) {
             info.put(EventFactory.USER_DATA, userData);
         }
-        sessionSaveDelayer.delayIfNeeded(userData);
         root.commit(Collections.unmodifiableMap(info));
         if (permissionProvider != null && refreshPermissionProvider) {
             permissionProvider.refresh();
diff --git 
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
 
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
index d5b9edd4bd..b7f607b33b 100644
--- 
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
+++ 
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
@@ -54,7 +54,6 @@ import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate;
 import org.apache.jackrabbit.oak.jcr.session.RefreshStrategy;
 import org.apache.jackrabbit.oak.jcr.session.RefreshStrategy.Composite;
 import org.apache.jackrabbit.oak.jcr.session.SessionContext;
-import org.apache.jackrabbit.oak.jcr.session.SessionSaveDelayer;
 import org.apache.jackrabbit.oak.jcr.session.SessionStats;
 import org.apache.jackrabbit.oak.jcr.version.FrozenNodeLogger;
 import org.apache.jackrabbit.oak.plugins.observation.CommitRateLimiter;
@@ -121,7 +120,6 @@ public class RepositoryImpl implements JackrabbitRepository 
{
     private final MountInfoProvider mountInfoProvider;
     private final BlobAccessProvider blobAccessProvider;
     private final SessionQuerySettingsProvider sessionQuerySettingsProvider;
-    private final SessionSaveDelayer sessionSaveDelayer;
 
     /**
      * {@link ThreadLocal} counter that keeps track of the save operations
@@ -178,7 +176,6 @@ public class RepositoryImpl implements JackrabbitRepository 
{
         this.frozenNodeLogger = new FrozenNodeLogger(clock, whiteboard);
         this.sessionQuerySettingsProvider = 
Optional.ofNullable(WhiteboardUtils.getService(whiteboard, 
SessionQuerySettingsProvider.class))
                 .orElseGet(() -> new 
FastQuerySizeSettingsProvider(fastQueryResultSize));
-        this.sessionSaveDelayer = new SessionSaveDelayer(whiteboard);
     }
 
     //---------------------------------------------------------< Repository 
>---
@@ -327,7 +324,7 @@ public class RepositoryImpl implements JackrabbitRepository 
{
 
         return new SessionDelegate(
                 contentSession, securityProvider, refreshStrategy,
-                threadSaveCount, statisticManager, clock, sessionSaveDelayer) {
+                threadSaveCount, statisticManager, clock) {
             
             // Defer session MBean registration to avoid cluttering the
             // JMX name space with short lived sessions
@@ -357,7 +354,6 @@ public class RepositoryImpl implements JackrabbitRepository 
{
         statisticManager.dispose();
         gcMonitorRegistration.unregister();
         frozenNodeLogger.close();
-        sessionSaveDelayer.close();
         clock.close();
         new ExecutorCloser(scheduledExecutor).close();
         if (contentRepository instanceof Closeable) {
diff --git 
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayer.java
 
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayer.java
deleted file mode 100644
index 2a9d1f1695..0000000000
--- 
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayer.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * 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.jackrabbit.oak.jcr.session;
-
-import static org.apache.jackrabbit.oak.spi.toggle.Feature.newFeature;
-
-import java.io.Closeable;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.jackrabbit.guava.common.base.Strings;
-import org.apache.jackrabbit.oak.api.jmx.RepositoryManagementMBean;
-import org.apache.jackrabbit.oak.spi.toggle.Feature;
-import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
-import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
-import org.jetbrains.annotations.NotNull;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A delay mechanism for Session.save() operations. By default, Session.save
- * calls are not delayed. If enabled, some of the save() operations can be
- * delayed for a certain number of microseconds.
- *
- * This facility is enabled / disabled via feature toggle, and controlled via
- * JMX bean, or (for testing) via two system properties. There is no attempt to
- * control the delay, or which threads to delay, from within. It is meant for
- * emergency situation, specially for cases where some threads write too much.
- */
-public class SessionSaveDelayer implements Closeable {
-
-    private static final Logger LOG = 
LoggerFactory.getLogger(SessionSaveDelayer.class);
-
-    private static final String FT_SAVE_DELAY_NAME = "FT_SAVE_DELAY_OAK-11766";
-    private static final String ENABLED_PROP_NAME = "oak.sessionSaveDelayer";
-    private static final String CONFIG_PROP_NAME = 
"oak.sessionSaveDelayerConfig";
-
-    private final boolean enabledViaSysPropertey = 
Boolean.getBoolean(ENABLED_PROP_NAME);
-    private final String sysPropertyConfig = 
System.getProperty(CONFIG_PROP_NAME, "");
-    private final Feature feature;
-    private final Whiteboard whiteboard;
-    private final AtomicBoolean closed = new AtomicBoolean();
-
-    private RepositoryManagementMBean cachedMbean;
-    private String lastConfigJson;
-    private SessionSaveDelayerConfig lastConfig;
-    private volatile boolean logNextDelay;
-
-    public SessionSaveDelayer(@NotNull Whiteboard whiteboard) {
-        this.feature = newFeature(FT_SAVE_DELAY_NAME, whiteboard);
-        LOG.info("Initialized");
-        if (enabledViaSysPropertey) {
-            LOG.info("Enabled via system property: " + ENABLED_PROP_NAME);
-        }
-        this.whiteboard = whiteboard;
-    }
-
-    private RepositoryManagementMBean getRepositoryMBean() {
-        if (cachedMbean == null) {
-            cachedMbean = WhiteboardUtils.getService(whiteboard, 
RepositoryManagementMBean.class);
-        }
-        return cachedMbean;
-    }
-
-    public long delayIfNeeded(String userData) {
-        if (closed.get() || (!feature.isEnabled() && !enabledViaSysPropertey)) 
{
-            return 0;
-        }
-        String config = sysPropertyConfig;
-        RepositoryManagementMBean mbean = getRepositoryMBean();
-        if (mbean != null) {
-            String jmxConfig = mbean.getSessionSaveDelayerConfig();
-            if (!Strings.isNullOrEmpty(jmxConfig)) {
-                config = jmxConfig;
-            }
-        }
-        if (Strings.isNullOrEmpty(config)) {
-            return 0;
-        }
-        if (!config.equals(lastConfigJson)) {
-            logNextDelay = true;
-            lastConfigJson = config;
-            try {
-                // reset, if already set
-                lastConfig = null;
-                lastConfig = SessionSaveDelayerConfig.fromJson(config);
-                LOG.info("New config: {}", lastConfig.toString());
-            } catch (IllegalArgumentException e) {
-                LOG.warn("Can not parse config {}", e);
-                // don't delay
-                return 0;
-            }
-        }
-        if (lastConfig == null) {
-            return 0;
-        }
-        String threadName = Thread.currentThread().getName();
-        long delayNanos = lastConfig.getDelayNanos(threadName, userData, null);
-        if (delayNanos > 0) {
-            long millis = delayNanos / 1_000_000;
-            int nanos = (int) (delayNanos % 1_000_000);
-            if (logNextDelay) {
-                LOG.info("Sleep {} ms {} ns for user {}", millis, nanos, 
userData);
-                logNextDelay = false;
-            }
-            try {
-                Thread.sleep(millis, nanos);
-            } catch (InterruptedException e) {
-                // ignore
-                Thread.currentThread().interrupt();
-            }
-        }
-        return delayNanos;
-    }
-
-    @Override
-    public void close() {
-        if (!closed.getAndSet(true)) {
-            feature.close();
-        }
-    }
-
-}
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
deleted file mode 100644
index e89614c103..0000000000
--- 
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayerConfig.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * 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.jackrabbit.oak.jcr.session;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-import org.apache.jackrabbit.guava.common.base.Strings;
-import org.apache.jackrabbit.oak.commons.json.JsonObject;
-import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
-import org.apache.jackrabbit.oak.commons.json.JsopTokenizer;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Configuration parser for a session save delay JSON configuration:
- * <pre>
- * {
- *   "entries": [
- *     {
- *       "delayMillis": 1.0,
- *       "threadNameRegex": "thread-.*",
- *       "stackTraceRegex": ".*SomeClass.*"
- *     },
- *     {
- *       "delayMillis": 0.5,
- *       "threadNameRegex": "worker-\\d+"
- *     }
- *   ]
- * }
- * </pre>
- */
-public class SessionSaveDelayerConfig {
-
-    private static final Logger LOG = 
LoggerFactory.getLogger(SessionSaveDelayerConfig.class);
-
-    private final List<DelayEntry> entries;
-
-    public SessionSaveDelayerConfig(@NotNull List<DelayEntry> entries) {
-        this.entries = new ArrayList<>(entries);
-    }
-
-    @NotNull
-    public static SessionSaveDelayerConfig fromJson(@NotNull String 
jsonConfig) throws IllegalArgumentException {
-        if (Strings.isNullOrEmpty(jsonConfig)) {
-            return new SessionSaveDelayerConfig(List.of());
-        }
-        try {
-            JsopTokenizer tokenizer = new JsopTokenizer(jsonConfig);
-            tokenizer.read('{');
-            JsonObject root = JsonObject.create(tokenizer);
-            List<DelayEntry> entries = new ArrayList<>();
-            String entriesJson = root.getProperties().get("entries");
-            if (entriesJson != null) {
-                JsopTokenizer entryTokenizer = new JsopTokenizer(entriesJson);
-                entryTokenizer.read('[');
-                if (!entryTokenizer.matches(']')) {
-                    do {
-                        if (entryTokenizer.matches('{')) {
-                            DelayEntry entry = 
parseDelayEntry(JsonObject.create(entryTokenizer));
-                            if (entry != null) {
-                                entries.add(entry);
-                            }
-                        } else {
-                            throw new IllegalArgumentException("Expected 
object in entries array");
-                        }
-                    } while (entryTokenizer.matches(','));
-                    entryTokenizer.read(']');
-                }
-            }
-            return new SessionSaveDelayerConfig(entries);
-        } catch (Exception e) {
-            throw new IllegalArgumentException("Failed to parse JSON 
configuration: " + e.getMessage(), e);
-        }
-    }
-
-    public List<DelayEntry> getEntries() {
-        return entries;
-    }
-
-    public long getDelayNanos(@NotNull String threadName, @Nullable String 
userData, @Nullable String stackTrace) {
-        for (DelayEntry d : entries) {
-            if (d.matches(threadName, userData, stackTrace)) {
-                return d.getDelayNanos();
-            }
-        }
-        return 0;
-    }
-
-    @Nullable
-    private static DelayEntry parseDelayEntry(JsonObject entryObj) {
-        String delayMillis = entryObj.getProperties().get("delayMillis");
-        String threadNameRegex = 
entryObj.getProperties().get("threadNameRegex");
-        String userDataRegex = entryObj.getProperties().get("userDataRegex");
-        String stackTraceRegex = 
entryObj.getProperties().get("stackTraceRegex");
-        String maxSavesPerSecond = 
entryObj.getProperties().get("maxSavesPerSecond");
-        if (delayMillis == null || threadNameRegex == null) {
-            LOG.warn("Skipping entry with missing required fields (delay or 
threadNameRegex)");
-            return null;
-        }
-        try {
-            double delay = Double.parseDouble(delayMillis);
-            if (delay < 0) {
-                LOG.warn("Skipping entry with negative delay");
-                return null;
-            }
-            double maxSaves = 0.0;
-            if (maxSavesPerSecond != null) {
-                maxSaves = Double.parseDouble(maxSavesPerSecond);
-                if (maxSaves < 0) {
-                    LOG.warn("Skipping entry with negative maxSavesPerSecond");
-                    return null;
-                }
-            }
-            Pattern threadPattern = 
Pattern.compile(JsopTokenizer.decodeQuoted(threadNameRegex));
-            Pattern stackPattern = null;
-            if (stackTraceRegex != null) {
-                stackPattern = 
Pattern.compile(JsopTokenizer.decodeQuoted(stackTraceRegex));
-            }
-            Pattern userDataPattern = null;
-            if (userDataRegex != null) {
-                userDataPattern = 
Pattern.compile(JsopTokenizer.decodeQuoted(userDataRegex));
-            }
-            return new DelayEntry(delay, threadPattern, userDataPattern, 
stackPattern, maxSaves);
-        } catch (NumberFormatException e) {
-            LOG.warn("Skipping entry with invalid delay value or 
maxSavesPerSecond: {}", e.getMessage());
-            return null;
-        } catch (PatternSyntaxException e) {
-            LOG.warn("Skipping entry with invalid regex pattern: {}", 
e.getMessage());
-            return null;
-        }
-    }
-
-    @Override
-    public String toString() {
-        JsopBuilder json = new JsopBuilder();
-        json.object().key("entries").array();
-        for (DelayEntry entry : entries) {
-            entry.toJson(json);
-        }
-        json.endArray().endObject();
-        return JsopBuilder.prettyPrint(json.toString());
-    }
-
-    /**
-     * Gets the stack trace of the current thread as a string.
-     *
-     * @return the current stack trace as a formatted string, or null if no 
stack trace is available
-     */
-    @Nullable
-    public static String getCurrentStackTrace() {
-        StackTraceElement[] stackTrace = 
Thread.currentThread().getStackTrace();
-        if (stackTrace == null || stackTrace.length == 0) {
-            return null;
-        }
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < stackTrace.length; i++) {
-            if (i > 0) {
-                sb.append("\n\tat ");
-            } else {
-                sb.append("at ");
-            }
-            sb.append(stackTrace[i]);
-        }
-        return sb.toString();
-    }
-
-    public static class DelayEntry {
-        private final long baseDelayNanos;
-        private final Pattern threadNamePattern;
-        private final Pattern stackTracePattern;
-        private final Pattern userDataPattern;
-        private final double maxSavesPerSecond;
-        private final AtomicLong lastMatch = new AtomicLong(0);
-
-        public DelayEntry(double delayMillis, @NotNull Pattern 
threadNamePattern, @Nullable Pattern userDataPattern, @Nullable Pattern 
stackTracePattern, double maxSavesPerSecond) {
-            this.baseDelayNanos = (long) (delayMillis * 1_000_000);
-            this.threadNamePattern = threadNamePattern;
-            this.userDataPattern = userDataPattern;
-            this.stackTracePattern = stackTracePattern;
-            this.maxSavesPerSecond = maxSavesPerSecond;
-        }
-
-        public long getDelayNanos() {
-            long totalDelayNanos = baseDelayNanos;
-            if (maxSavesPerSecond > 0) {
-                long currentTime = System.currentTimeMillis();
-                double intervalMs = 1000.0 / maxSavesPerSecond;
-                long lastMatchTime = lastMatch.get();
-                if (lastMatchTime > 0) {
-                    long nextAllowedTime = lastMatchTime + (long) intervalMs;
-                    if (currentTime < nextAllowedTime) {
-                        long rateLimitDelayMs = nextAllowedTime - currentTime;
-                        totalDelayNanos += rateLimitDelayMs * 1_000_000;
-                    }
-                }
-                lastMatch.set(currentTime);
-            }
-            return totalDelayNanos;
-        }
-
-        public long getBaseDelayNanos() {
-            return baseDelayNanos;
-        }
-
-        public double getMaxSavesPerSecond() {
-            return maxSavesPerSecond;
-        }
-
-        @NotNull
-        public Pattern getThreadNamePattern() {
-            return threadNamePattern;
-        }
-
-        @Nullable
-        public Pattern getStackTracePattern() {
-            return stackTracePattern;
-        }
-
-        @Nullable
-        public Pattern getUserDataPattern() {
-            return userDataPattern;
-        }
-
-        boolean matches(@NotNull String threadName, @Nullable String userData, 
@Nullable String stackTrace) {
-            if (!threadNamePattern.matcher(threadName).matches()) {
-                return false;
-            }
-            if (userDataPattern != null) {
-                if (userData == null) {
-                    return false;
-                }
-                if (!userDataPattern.matcher(userData).find()) {
-                    return false;
-                }
-            }
-            if (stackTracePattern != null) {
-                if (stackTrace == null) {
-                    stackTrace = 
SessionSaveDelayerConfig.getCurrentStackTrace();
-                }
-                return stackTracePattern.matcher(stackTrace).find();
-            }
-            return true;
-        }
-
-        @Override
-        public String toString() {
-            return toJson(new JsopBuilder()).toString();
-        }
-
-        public JsopBuilder toJson(JsopBuilder json) {
-            json.object();
-            double delayMillis = baseDelayNanos / 1_000_000.0;
-            json.key("delayMillis").encodedValue(Double.toString(delayMillis));
-            json.key("threadNameRegex").value(threadNamePattern.pattern());
-            if (userDataPattern != null) {
-                json.key("userDataRegex").value(userDataPattern.pattern());
-            }
-            if (stackTracePattern != null) {
-                json.key("stackTraceRegex").value(stackTracePattern.pattern());
-            }
-            if (maxSavesPerSecond > 0) {
-                
json.key("maxSavesPerSecond").encodedValue(Double.toString(maxSavesPerSecond));
-            }
-            return json.endObject();
-        }
-
-    }
-}
diff --git 
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/delegate/AbstractDelegatorTest.java
 
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/delegate/AbstractDelegatorTest.java
index 649d0a5686..68a35e9fc8 100644
--- 
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/delegate/AbstractDelegatorTest.java
+++ 
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/delegate/AbstractDelegatorTest.java
@@ -22,7 +22,6 @@ import org.apache.jackrabbit.oak.api.ContentSession;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.jcr.session.RefreshStrategy;
-import org.apache.jackrabbit.oak.jcr.session.SessionSaveDelayer;
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
 import 
org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
 import 
org.apache.jackrabbit.oak.spi.security.authorization.permission.PermissionAware;
@@ -64,8 +63,7 @@ public abstract class AbstractDelegatorTest {
         Whiteboard wb = new DefaultWhiteboard();
         StatisticManager statisticManager = new StatisticManager(wb, 
executorService);
         return spy(new SessionDelegate(mockContentSession(root), 
mockSecurityProvider(root, pp),
-                RefreshStrategy.Composite.create(), new ThreadLocal<>(), 
statisticManager, new Clock.Virtual(),
-                new SessionSaveDelayer(wb)));
+                RefreshStrategy.Composite.create(), new ThreadLocal<>(), 
statisticManager, new Clock.Virtual()));
     }
 
     @NotNull
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
deleted file mode 100644
index 2e0120ff6c..0000000000
--- 
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayerConfigTest.java
+++ /dev/null
@@ -1,691 +0,0 @@
-/*
- * 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.jackrabbit.oak.jcr.session;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.util.List;
-
-import org.junit.Test;
-
-/**
- * Tests for {@link SessionSaveDelayerConfig}.
- */
-public class SessionSaveDelayerConfigTest {
-
-    @Test
-    public void testEmptyConfiguration() {
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson("");
-        assertNotNull(config);
-        assertTrue(config.getEntries().isEmpty());
-    }
-
-    @Test
-    public void testNullConfiguration() {
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(null);
-        assertNotNull(config);
-        assertTrue(config.getEntries().isEmpty());
-    }
-
-    @Test
-    public void testBasicConfiguration() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.5,\n" +
-                "      \"threadNameRegex\": \"worker-\\\\d+\"\n" +
-                "    },\n" +
-                "    {\n" +
-                "      \"delayMillis\": 1,\n" +
-                "      \"threadNameRegex\": \"thread-.*\",\n" +
-                "      \"stackTraceRegex\": \".*SomeClass.*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-
-        List<SessionSaveDelayerConfig.DelayEntry> entries = 
config.getEntries();
-        assertEquals(2, entries.size());
-
-        SessionSaveDelayerConfig.DelayEntry first = entries.get(0);
-        assertEquals(500_000L, first.getDelayNanos());
-        assertEquals("worker-\\d+", first.getThreadNamePattern().pattern());
-        assertNull(first.getStackTracePattern());
-
-        SessionSaveDelayerConfig.DelayEntry second = entries.get(1);
-        assertEquals(1_000_000L, second.getDelayNanos());
-        assertEquals("thread-.*", second.getThreadNamePattern().pattern());
-        assertNotNull(second.getStackTracePattern());
-        assertEquals(".*SomeClass.*", second.getStackTracePattern().pattern());
-    }
-
-    @Test
-    public void testEntryMatching() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 1,\n" +
-                "      \"threadNameRegex\": \"thread-.*\",\n" +
-                "      \"stackTraceRegex\": \".*SomeClass.*\"\n" +
-                "    },\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.5,\n" +
-                "      \"threadNameRegex\": \"worker-\\\\d+\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        List<SessionSaveDelayerConfig.DelayEntry> entries = 
config.getEntries();
-
-        SessionSaveDelayerConfig.DelayEntry first = entries.get(0);
-        SessionSaveDelayerConfig.DelayEntry second = entries.get(1);
-
-        assertTrue(first.matches("thread-123", null, "at 
com.example.SomeClass.method()"));
-        assertTrue(first.matches("thread-abc", null, "SomeClass is here"));
-        assertFalse(first.matches("thread-123", null, "no matching class"));
-        assertFalse(first.matches("thread-123", null, null));
-        assertFalse(first.matches("worker-123", null, "at 
com.example.SomeClass.method()"));
-
-        assertTrue(second.matches("worker-123", null, "any stack trace"));
-        assertTrue(second.matches("worker-456", null, null));
-        assertFalse(second.matches("worker-abc", null, "any stack trace"));
-        assertFalse(second.matches("thread-123", null, "any stack trace"));
-    }
-
-    @Test
-    public void testConfigurationWithMissingFields() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 1,\n" +
-                "      \"threadNameRegex\": \"thread-.*\"\n" +
-                "    },\n" +
-                "    {\n" +
-                "      \"threadNameRegex\": \"worker-\\\\d+\"\n" +
-                "    },\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.5\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-
-        List<SessionSaveDelayerConfig.DelayEntry> entries = 
config.getEntries();
-        // Only the first entry should be valid (has both delay and 
threadNameRegex)
-        assertEquals(1, entries.size());
-        
-        SessionSaveDelayerConfig.DelayEntry entry = entries.get(0);
-        assertEquals(1000_000L, entry.getDelayNanos());
-        assertEquals("thread-.*", entry.getThreadNamePattern().pattern());
-        assertNull(entry.getStackTracePattern());
-    }
-
-    @Test
-    public void testConfigurationWithInvalidValues() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": \"invalid\",\n" +
-                "      \"threadNameRegex\": \"thread-.*\"\n" +
-                "    },\n" +
-                "    {\n" +
-                "      \"delayMillis\": -100,\n" +
-                "      \"threadNameRegex\": \"worker-\\\\d+\"\n" +
-                "    },\n" +
-                "    {\n" +
-                "      \"delay\": 500,\n" +
-                "      \"threadNameRegex\": \"[invalid-regex\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-
-        // All entries should be invalid and skipped
-        assertTrue(config.getEntries().isEmpty());
-    }
-
-    @Test
-    public void testEmptyEntriesArray() {
-        String json = "{\n" +
-                "  \"entries\": []\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-        assertTrue(config.getEntries().isEmpty());
-    }
-
-    @Test
-    public void testConfigurationWithoutEntriesProperty() {
-        String json = "{\n" +
-                "  \"someOtherProperty\": \"value\"\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-        assertTrue(config.getEntries().isEmpty());
-    }
-
-    @Test
-    public void testInvalidJsonThrowsException() {
-        String invalidJson = "{ invalid json }";
-
-        try {
-            SessionSaveDelayerConfig.fromJson(invalidJson);
-            fail("Expected IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("Failed to parse JSON 
configuration"));
-        }
-    }
-
-    @Test
-    public void testDelayConfigToString() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 1.0,\n" +
-                "      \"threadNameRegex\": \"thread-.*\",\n" +
-                "      \"stackTraceRegex\": \".*SomeClass.*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        
-        assertEquals("{\n"
-                + "  \"entries\": [{\n"
-                + "    \"delayMillis\": 1.0, \"threadNameRegex\": 
\"thread-.*\", \"stackTraceRegex\": \".*SomeClass.*\"\n"
-                + "  }]\n"
-                + "}", config.toString());
-    }
-
-    @Test
-    public void testComplexRegexPatterns() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 2,\n" +
-                "      \"threadNameRegex\": 
\"(?i)pool-\\\\d+-thread-\\\\d+\",\n" +
-                "      \"stackTraceRegex\": 
\".*\\\\.(save|update|delete)\\\\(.*\\\\).*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-
-        List<SessionSaveDelayerConfig.DelayEntry> entries = 
config.getEntries();
-        assertEquals(1, entries.size());
-
-        SessionSaveDelayerConfig.DelayEntry entry = entries.get(0);
-        assertEquals(2_000_000L, entry.getDelayNanos());
-
-        // 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()"));
-    }
-
-    @Test
-    public void testUserDataPatternBasic() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 1.0,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": \"admin.*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-
-        List<SessionSaveDelayerConfig.DelayEntry> entries = 
config.getEntries();
-        assertEquals(1, entries.size());
-
-        SessionSaveDelayerConfig.DelayEntry entry = entries.get(0);
-        assertEquals(1_000_000L, entry.getDelayNanos());
-
-        // Test userDataPattern matching
-        assertTrue(entry.matches("any-thread", "admin", null));
-        assertTrue(entry.matches("any-thread", "admin123", null));
-        assertTrue(entry.matches("any-thread", "adminUser", null));
-        assertFalse(entry.matches("any-thread", "user", null));
-        assertFalse(entry.matches("any-thread", "testAdmin", null));
-        assertFalse(entry.matches("any-thread", null, null)); // null userData 
should not match
-    }
-
-    @Test
-    public void testUserDataPatternWithComplexRegex() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.5,\n" +
-                "      \"threadNameRegex\": \"worker-.*\",\n" +
-                "      \"userDataRegex\": 
\"(admin|root|system)@.*\\\\.com$\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-
-        List<SessionSaveDelayerConfig.DelayEntry> entries = 
config.getEntries();
-        assertEquals(1, entries.size());
-
-        SessionSaveDelayerConfig.DelayEntry entry = entries.get(0);
-
-        // Test complex userDataPattern matching
-        assertTrue(entry.matches("worker-1", "[email protected]", null));
-        assertTrue(entry.matches("worker-2", "[email protected]", null));
-        assertTrue(entry.matches("worker-3", "[email protected]", null));
-        assertFalse(entry.matches("worker-1", "[email protected]", null));
-        assertFalse(entry.matches("worker-1", "[email protected]", null));
-        assertFalse(entry.matches("worker-1", "admin", null));
-        assertFalse(entry.matches("other-thread", "[email protected]", null)); 
// thread name doesn't match
-    }
-
-    @Test
-    public void testUserDataPatternCombinedWithStackTrace() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 2.0,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": \"privileged.*\",\n" +
-                "      \"stackTraceRegex\": \".*Session\\\\.save.*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-
-        List<SessionSaveDelayerConfig.DelayEntry> entries = 
config.getEntries();
-        assertEquals(1, entries.size());
-
-        SessionSaveDelayerConfig.DelayEntry entry = entries.get(0);
-
-        // All conditions must match
-        assertTrue(entry.matches("any-thread", "privilegedUser", "at 
javax.jcr.Session.save(Session.java:123)"));
-        assertFalse(entry.matches("any-thread", "privilegedUser", "at 
javax.jcr.Session.refresh(Session.java:456)")); // stack trace doesn't match
-        assertFalse(entry.matches("any-thread", "normalUser", "at 
javax.jcr.Session.save(Session.java:123)")); // userData doesn't match
-        assertFalse(entry.matches("any-thread", null, "at 
javax.jcr.Session.save(Session.java:123)")); // null userData
-    }
-
-    @Test
-    public void testUserDataPatternMultipleEntries() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.1,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": \"admin.*\"\n" +
-                "    },\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.2,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": \"guest.*\"\n" +
-                "    },\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.3,\n" +
-                "      \"threadNameRegex\": \".*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        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));
-        assertEquals(300_000L, config.getDelayNanos("thread-1", "normalUser", 
null)); // matches third entry (no userData pattern)
-        assertEquals(300_000L, config.getDelayNanos("thread-1", null, null)); 
// matches third entry (no userData pattern)
-    }
-
-    @Test
-    public void testUserDataPatternWithInvalidRegex() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 1.0,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": \"[invalid-regex\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-
-        // Entry should be skipped due to invalid regex
-        assertTrue(config.getEntries().isEmpty());
-    }
-
-    @Test
-    public void testUserDataPatternEmptyString() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 1.0,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": \"\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-
-        List<SessionSaveDelayerConfig.DelayEntry> entries = 
config.getEntries();
-        assertEquals(1, entries.size());
-
-        SessionSaveDelayerConfig.DelayEntry entry = entries.get(0);
-
-        // Empty pattern should match empty string but not non-empty strings
-        assertTrue(entry.matches("any-thread", "", null));
-        assertTrue(entry.matches("any-thread", "anything", null)); // empty 
regex matches anything using find()
-        assertFalse(entry.matches("any-thread", null, null)); // null userData 
should not match
-    }
-
-    @Test
-    public void testUserDataPatternCaseInsensitive() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 1.0,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": \"(?i)ADMIN.*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-
-        List<SessionSaveDelayerConfig.DelayEntry> entries = 
config.getEntries();
-        assertEquals(1, entries.size());
-
-        SessionSaveDelayerConfig.DelayEntry entry = entries.get(0);
-
-        // Test case-insensitive matching
-        assertTrue(entry.matches("any-thread", "admin", null));
-        assertTrue(entry.matches("any-thread", "ADMIN", null));
-        assertTrue(entry.matches("any-thread", "Admin123", null));
-        assertTrue(entry.matches("any-thread", "administrator", null));
-        assertFalse(entry.matches("any-thread", "user", null));
-    }
-
-    @Test 
-    public void testGetDelayNanosWithUserDataPattern() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 1.0,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": \"admin.*\"\n" +
-                "    },\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.5,\n" +
-                "      \"threadNameRegex\": \".*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        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));
-    }
-
-    @Test
-    public void testRateLimitingBasic() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.1,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"maxSavesPerSecond\": 2.0\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-
-        List<SessionSaveDelayerConfig.DelayEntry> entries = 
config.getEntries();
-        assertEquals(1, entries.size());
-
-        SessionSaveDelayerConfig.DelayEntry entry = entries.get(0);
-        assertEquals(100_000L, entry.getBaseDelayNanos());
-        assertEquals(2.0, entry.getMaxSavesPerSecond(), 0.001);
-
-        // First call should only have base delay
-        long firstDelay = entry.getDelayNanos();
-        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", 
-                secondDelay >= 100_000L + 400_000_000L); // ~500ms in nanos
-    }
-
-    @Test
-    public void testRateLimitingWithZeroMaxSaves() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.1,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"maxSavesPerSecond\": 0.0\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-
-        List<SessionSaveDelayerConfig.DelayEntry> entries = 
config.getEntries();
-        assertEquals(1, entries.size());
-
-        SessionSaveDelayerConfig.DelayEntry entry = entries.get(0);
-        assertEquals(0.0, entry.getMaxSavesPerSecond(), 0.001);
-
-        // 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());
-    }
-
-    @Test
-    public void testRateLimitingWithNegativeMaxSaves() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.1,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"maxSavesPerSecond\": -1.0\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-
-        // Entry should be skipped due to negative maxSavesPerSecond
-        assertTrue(config.getEntries().isEmpty());
-    }
-
-    @Test
-    public void testRateLimitingWithInvalidMaxSaves() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.1,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"maxSavesPerSecond\": \"invalid\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        assertNotNull(config);
-
-        // Entry should be skipped due to invalid maxSavesPerSecond
-        assertTrue(config.getEntries().isEmpty());
-    }
-
-    @Test
-    public void testRateLimitingHighFrequency() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.0,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"maxSavesPerSecond\": 10.0\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        SessionSaveDelayerConfig.DelayEntry entry = config.getEntries().get(0);
-
-        // With 10 saves per second, minimum interval is 100ms
-        long firstDelay = entry.getDelayNanos();
-        assertEquals(0L, firstDelay); // No base delay
-
-        long secondDelay = entry.getDelayNanos();
-        assertTrue("Should have rate limit delay", secondDelay >= 
90_000_000L); // ~100ms in nanos
-    }
-
-
-
-    @Test
-    public void testRateLimitingCombinedWithUserDataPattern() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.1,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": \"admin.*\",\n" +
-                "      \"maxSavesPerSecond\": 1.0\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        SessionSaveDelayerConfig.DelayEntry entry = config.getEntries().get(0);
-
-        // Test rate limiting only applies when entry matches
-        assertTrue(entry.matches("thread-1", "admin", null));
-        assertFalse(entry.matches("thread-1", "user", null));
-
-        // Rate limiting should work for matching entries
-        long firstDelay = entry.getDelayNanos();
-        assertEquals(100_000L, firstDelay);
-
-        long secondDelay = entry.getDelayNanos();
-        assertTrue("Should have rate limit delay", secondDelay >= 
1_000_000_000L); // ~1000ms
-    }
-
-    @Test
-    public void testRateLimitingJsonSerialization() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.5,\n" +
-                "      \"threadNameRegex\": \"worker-.*\",\n" +
-                "      \"maxSavesPerSecond\": 3.5\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        String serialized = config.toString();
-
-        // Check that serialization includes maxSavesPerSecond
-        assertTrue(serialized.contains("\"maxSavesPerSecond\": 3.5"));
-        assertTrue(serialized.contains("\"delayMillis\": 0.5"));
-        assertTrue(serialized.contains("\"threadNameRegex\": \"worker-.*\""));
-    }
-
-    @Test
-    public void testRateLimitingJsonSerializationWithoutMaxSaves() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.5,\n" +
-                "      \"threadNameRegex\": \"worker-.*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        SessionSaveDelayerConfig config = 
SessionSaveDelayerConfig.fromJson(json);
-        String serialized = config.toString();
-
-        // Check that serialization doesn't include maxSavesPerSecond when 
it's 0
-        assertFalse(serialized.contains("maxSavesPerSecond"));
-    }
-
-    @Test
-    public void testGetDelayNanosWithRateLimit() {
-        String json = "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.1,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"maxSavesPerSecond\": 2.0\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}";
-
-        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);
-    }
-} 
\ No newline at end of file
diff --git 
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayerTest.java
 
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayerTest.java
deleted file mode 100644
index f21001f07e..0000000000
--- 
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionSaveDelayerTest.java
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- * 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.jackrabbit.oak.jcr.session;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.util.Map;
-
-import org.apache.jackrabbit.oak.api.jmx.RepositoryManagementMBean;
-import org.apache.jackrabbit.oak.commons.junit.TemporarySystemProperty;
-import org.apache.jackrabbit.oak.spi.whiteboard.DefaultWhiteboard;
-import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-/**
- * Tests for {@link SessionSaveDelayer}.
- */
-public class SessionSaveDelayerTest {
-
-    private static final String ENABLED_PROP_NAME = "oak.sessionSaveDelayer";
-    private static final String CONFIG_PROP_NAME = 
"oak.sessionSaveDelayerConfig";
-    
-    @Rule 
-    public TemporarySystemProperty temp;
-  
-    private Whiteboard whiteboard;
-    private SessionSaveDelayer delayer;
-
-    @Before
-    public void setUp() {
-        whiteboard = new DefaultWhiteboard();
-        delayer = new SessionSaveDelayer(whiteboard);
-    }
-
-    @After
-    public void tearDown() {
-        if (delayer != null) {
-            delayer.close();
-        }
-    }
-
-    @Test
-    public void testGetCurrentStackTrace() {
-        String stackTrace = SessionSaveDelayerConfig.getCurrentStackTrace();
-        assertNotNull(stackTrace);
-        assertTrue(stackTrace.contains("testGetCurrentStackTrace"));
-        assertTrue(stackTrace.contains("at "));
-    }
-
-    @Test
-    public void testDelayIfNeededDisabled() {
-        System.clearProperty(ENABLED_PROP_NAME);
-        delayer = new SessionSaveDelayer(whiteboard);        
-        long delay = delayer.delayIfNeeded(null);
-        assertEquals(0, delay);
-    }
-
-    @Test
-    public void testDelayIfNeededEnabledViaSystemProperty() {
-        System.setProperty(ENABLED_PROP_NAME, "true");
-        System.clearProperty(CONFIG_PROP_NAME);
-        delayer = new SessionSaveDelayer(whiteboard);
-        long delay = delayer.delayIfNeeded(null);
-        assertEquals(0, delay);
-    }
-
-    @Test
-    public void testDelayIfNeededWithSystemPropertyConfig() {
-        System.setProperty(ENABLED_PROP_NAME, "true");
-        System.setProperty(CONFIG_PROP_NAME, "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.1,\n" +
-                "      \"threadNameRegex\": \".*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}");
-        delayer = new SessionSaveDelayer(whiteboard);
-        long delay = delayer.delayIfNeeded(null);
-        assertEquals(100_000L, delay);
-    }
-
-    @Test
-    public void testDelayIfNeededWithJMXConfig() {
-        System.setProperty(ENABLED_PROP_NAME, "true");
-        RepositoryManagementMBean mbean = 
mock(RepositoryManagementMBean.class);
-        when(mbean.getSessionSaveDelayerConfig()).thenReturn("{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.02,\n" +
-                "      \"threadNameRegex\": \".*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}");
-        whiteboard.register(RepositoryManagementMBean.class, mbean, Map.of());
-        delayer = new SessionSaveDelayer(whiteboard);
-        long delay = delayer.delayIfNeeded(null);
-        assertEquals(20_000L, delay);
-    }
-
-    @Test
-    public void testDelayIfNeededWithNonMatchingThreadName() {
-        System.setProperty(ENABLED_PROP_NAME, "true");
-        System.setProperty(CONFIG_PROP_NAME, "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 5.0,\n" +
-                "      \"threadNameRegex\": \"non-matching-pattern\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}");
-        delayer = new SessionSaveDelayer(whiteboard);
-        long delay = delayer.delayIfNeeded(null);
-        assertEquals(0, delay);
-    }
-
-    @Test
-    public void testDelayIfNeededWithInvalidConfig() {
-        System.setProperty(ENABLED_PROP_NAME, "true");
-        System.setProperty(CONFIG_PROP_NAME, "{ invalid json }");
-        delayer = new SessionSaveDelayer(whiteboard);
-        long delay = delayer.delayIfNeeded(null);
-        assertEquals(0, delay);
-    }
-
-    @Test
-    public void testDelayIfNeededConfigCaching() {
-        System.setProperty(ENABLED_PROP_NAME, "true");
-        System.setProperty(CONFIG_PROP_NAME, "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.1,\n" +
-                "      \"threadNameRegex\": \".*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}");
-        delayer = new SessionSaveDelayer(whiteboard);
-        long delay1 = delayer.delayIfNeeded(null);
-        assertEquals(100_000L, delay1);
-        long delay2 = delayer.delayIfNeeded(null);
-        assertEquals(100_000L, delay2);
-    }
-
-    @Test
-    public void testDelayIfNeededEmptyConfig() {
-        System.setProperty(ENABLED_PROP_NAME, "true");
-        System.setProperty(CONFIG_PROP_NAME, "");
-        delayer = new SessionSaveDelayer(whiteboard);
-                long delay = delayer.delayIfNeeded(null);
-        assertEquals(0, delay);
-    }
-
-    @Test
-    public void testDelayIfNeededAfterClose() {
-        long delay = delayer.delayIfNeeded(null);
-        assertEquals(0, delay);
-    }
-
-    @Test
-    public void testJMXConfigOverridesSystemProperty() {
-        System.setProperty(ENABLED_PROP_NAME, "true");
-        System.setProperty(CONFIG_PROP_NAME, "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.1,\n" +
-                "      \"threadNameRegex\": \".*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}");
-        RepositoryManagementMBean mbean = 
mock(RepositoryManagementMBean.class);
-        when(mbean.getSessionSaveDelayerConfig()).thenReturn("{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.3,\n" +
-                "      \"threadNameRegex\": \".*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}");
-        
-        whiteboard.register(RepositoryManagementMBean.class, mbean, Map.of()); 
       
-        delayer = new SessionSaveDelayer(whiteboard);
-        long delay = delayer.delayIfNeeded(null);
-        assertEquals(300_000L, delay); 
-    }
-
-    @Test
-    public void testDelayIfNeededWithMultipleEntries() {
-        System.setProperty(ENABLED_PROP_NAME, "true");
-        System.setProperty(CONFIG_PROP_NAME, "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.1,\n" +
-                "      \"threadNameRegex\": \"non-matching\"\n" +
-                "    },\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.2,\n" +
-                "      \"threadNameRegex\": \".*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}");
-        
-        delayer = new SessionSaveDelayer(whiteboard);
-        long delay = delayer.delayIfNeeded(null);
-        assertEquals(200_000L, delay);
-    }
-
-    @Test
-    public void testDelayIfNeededWithUserDataPattern() {
-        System.setProperty(ENABLED_PROP_NAME, "true");
-        System.setProperty(CONFIG_PROP_NAME, "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.1,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": \"admin.*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}");
-        
-        delayer = new SessionSaveDelayer(whiteboard);
-        
-        // Test with matching userData
-        long delay = delayer.delayIfNeeded("admin");
-        assertEquals(100_000L, delay);
-        
-        delay = delayer.delayIfNeeded("admin123");
-        assertEquals(100_000L, delay);
-        
-        // Test with non-matching userData
-        delay = delayer.delayIfNeeded("user");
-        assertEquals(0L, delay);
-        
-        // Test with null userData
-        delay = delayer.delayIfNeeded(null);
-        assertEquals(0L, delay);
-    }
-
-    @Test
-    public void testDelayIfNeededWithUserDataPatternComplexRegex() {
-        System.setProperty(ENABLED_PROP_NAME, "true");
-        System.setProperty(CONFIG_PROP_NAME, "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.2,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": 
\"(admin|root|system)@.*\\\\.com$\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}");
-        
-        delayer = new SessionSaveDelayer(whiteboard);
-        
-        // Test with matching email patterns
-        long delay = delayer.delayIfNeeded("[email protected]");
-        assertEquals(200_000L, delay);
-        
-        delay = delayer.delayIfNeeded("[email protected]");
-        assertEquals(200_000L, delay);
-        
-        delay = delayer.delayIfNeeded("[email protected]");
-        assertEquals(200_000L, delay);
-        
-        // Test with non-matching patterns
-        delay = delayer.delayIfNeeded("[email protected]");
-        assertEquals(0L, delay);
-        
-        delay = delayer.delayIfNeeded("[email protected]");
-        assertEquals(0L, delay);
-        
-        delay = delayer.delayIfNeeded("admin");
-        assertEquals(0L, delay);
-    }
-
-    @Test
-    public void testDelayIfNeededWithUserDataPatternMultipleEntries() {
-        System.setProperty(ENABLED_PROP_NAME, "true");
-        System.setProperty(CONFIG_PROP_NAME, "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.1,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": \"admin.*\"\n" +
-                "    },\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.2,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": \"guest.*\"\n" +
-                "    },\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.3,\n" +
-                "      \"threadNameRegex\": \".*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}");
-        
-        delayer = new SessionSaveDelayer(whiteboard);
-        
-        // Test first entry matches
-        long delay = delayer.delayIfNeeded("admin");
-        assertEquals(100_000L, delay);
-        
-        delay = delayer.delayIfNeeded("admin123");
-        assertEquals(100_000L, delay);
-        
-        // Test second entry matches
-        delay = delayer.delayIfNeeded("guest123");
-        assertEquals(200_000L, delay);
-        
-        // Test third entry matches (no userData pattern)
-        delay = delayer.delayIfNeeded("normalUser");
-        assertEquals(300_000L, delay);
-        
-        // Test with null userData - should match third entry
-        delay = delayer.delayIfNeeded(null);
-        assertEquals(300_000L, delay);
-    }
-
-    @Test
-    public void testDelayIfNeededWithUserDataPatternAndJMX() {
-        System.setProperty(ENABLED_PROP_NAME, "true");
-        
-        RepositoryManagementMBean mbean = 
mock(RepositoryManagementMBean.class);
-        when(mbean.getSessionSaveDelayerConfig()).thenReturn("{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.15,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": \"privileged.*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}");
-        
-        whiteboard.register(RepositoryManagementMBean.class, mbean, Map.of());
-        delayer = new SessionSaveDelayer(whiteboard);
-        
-        // Test with matching userData
-        long delay = delayer.delayIfNeeded("privilegedUser");
-        assertEquals(150_000L, delay);
-        
-        // Test with non-matching userData
-        delay = delayer.delayIfNeeded("normalUser");
-        assertEquals(0L, delay);
-    }
-
-    @Test
-    public void testDelayIfNeededWithUserDataPatternCaseInsensitive() {
-        System.setProperty(ENABLED_PROP_NAME, "true");
-        System.setProperty(CONFIG_PROP_NAME, "{\n" +
-                "  \"entries\": [\n" +
-                "    {\n" +
-                "      \"delayMillis\": 0.1,\n" +
-                "      \"threadNameRegex\": \".*\",\n" +
-                "      \"userDataRegex\": \"(?i)ADMIN.*\"\n" +
-                "    }\n" +
-                "  ]\n" +
-                "}");
-        
-        delayer = new SessionSaveDelayer(whiteboard);
-        
-        // Test case-insensitive matching
-        long delay = delayer.delayIfNeeded("admin");
-        assertEquals(100_000L, delay);
-        
-        delay = delayer.delayIfNeeded("ADMIN");
-        assertEquals(100_000L, delay);
-        
-        delay = delayer.delayIfNeeded("Admin123");
-        assertEquals(100_000L, delay);
-        
-        delay = delayer.delayIfNeeded("administrator");
-        assertEquals(100_000L, delay);
-        
-        // Test non-matching
-        delay = delayer.delayIfNeeded("user");
-        assertEquals(0L, delay);
-    }
-
-
-
-} 
\ No newline at end of file

Reply via email to