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

ibessonov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 9b64914eee9 IGNITE-26995 Introduce Locker.shouldRelease method (#6973)
9b64914eee9 is described below

commit 9b64914eee90e5e8c89d6e40c36ab66662a56db7
Author: Viacheslav Blinov <[email protected]>
AuthorDate: Mon Nov 17 13:16:59 2025 +0300

    IGNITE-26995 Introduce Locker.shouldRelease method (#6973)
---
 .../checkpoint/CheckpointReadWriteLock.java        |  7 +++
 .../checkpoint/CheckpointTimeoutLock.java          | 11 ++++
 .../ReentrantReadWriteLockWithTracking.java        | 14 ++++++
 .../internal/storage/MvPartitionStorage.java       | 15 ++++++
 .../ignite/internal/storage/util/LocalLocker.java  |  5 ++
 modules/storage-page-memory/build.gradle           |  1 +
 .../mv/PersistentPageMemoryMvPartitionStorage.java | 13 ++++-
 ...PersistentPageMemoryMvPartitionStorageTest.java | 58 ++++++++++++++++++++++
 8 files changed, 123 insertions(+), 1 deletion(-)

diff --git 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/checkpoint/CheckpointReadWriteLock.java
 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/checkpoint/CheckpointReadWriteLock.java
index 9f609efcf8f..eab912f45f2 100644
--- 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/checkpoint/CheckpointReadWriteLock.java
+++ 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/checkpoint/CheckpointReadWriteLock.java
@@ -174,6 +174,13 @@ public class CheckpointReadWriteLock {
         return checkpointLock.getReadHoldCount();
     }
 
+    /**
+     * Returns {@code true} if there are threads waiting to acquire the write 
lock.
+     */
+    public boolean hasQueuedWriters() {
+        return checkpointLock.hasQueuedWriters();
+    }
+
     private void onReadLock(long start, boolean taken) {
         long elapsed = coarseCurrentTimeMillis() - start;
 
diff --git 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/checkpoint/CheckpointTimeoutLock.java
 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/checkpoint/CheckpointTimeoutLock.java
index 58c06b73374..5bbfea65ea0 100644
--- 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/checkpoint/CheckpointTimeoutLock.java
+++ 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/checkpoint/CheckpointTimeoutLock.java
@@ -244,6 +244,17 @@ public class CheckpointTimeoutLock {
         return checkpointReadWriteLock.checkpointLockIsHeldByThread();
     }
 
+    /**
+     * Returns {@code true} if there are threads waiting to acquire the 
checkpoint write lock.
+     * This can be used by user code to determine if it should stop execution 
preemptively
+     * to allow checkpointing to proceed.
+     *
+     * @return {@code true} if checkpoint is waiting for the write lock, 
{@code false} otherwise.
+     */
+    public boolean shouldReleaseReadLock() {
+        return checkpointReadWriteLock.hasQueuedWriters();
+    }
+
     private void failCheckpointReadLock() throws 
CheckpointReadLockTimeoutException {
         String msg = "Checkpoint read lock acquisition has been timed out.";
 
diff --git 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/checkpoint/ReentrantReadWriteLockWithTracking.java
 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/checkpoint/ReentrantReadWriteLockWithTracking.java
index 4b906908172..bbc7b602b0e 100644
--- 
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/checkpoint/ReentrantReadWriteLockWithTracking.java
+++ 
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/checkpoint/ReentrantReadWriteLockWithTracking.java
@@ -95,6 +95,20 @@ public class ReentrantReadWriteLockWithTracking implements 
ReadWriteLock {
         return delegate.getReadLockCount();
     }
 
+    /**
+     * Returns {@code true} if there are threads waiting to acquire the write 
lock.
+     * This method is designed for use in monitoring system state, not for 
synchronization control.
+     *
+     * @return {@code true} if there may be threads waiting to acquire the 
write lock, {@code false} otherwise.
+     */
+    public boolean hasQueuedWriters() {
+        // ReentrantReadWriteLock doesn't expose a direct method to check for 
queued writers only.
+        // We use hasQueuedThreads() as an approximation - it returns true if 
there are
+        // ANY queued threads, including threads waiting on read lock.
+        // This is acceptable for our use case since readers won't queue each 
other.
+        return delegate.hasQueuedThreads();
+    }
+
     /**
      * Tracks long read lock holders.
      */
diff --git 
a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
 
b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
index ebca93ce275..7a9d70d0e87 100644
--- 
a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
+++ 
b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
@@ -87,6 +87,21 @@ public interface MvPartitionStorage extends 
ManuallyCloseable {
          *      {@code false} if lock is not held by the current thread and 
the attempt to acquire it has failed.
          */
         boolean tryLock(RowId rowId);
+
+        /**
+         * Returns {@code true} if the engine needs resources and the user 
should consider stopping the execution preemptively.
+         *
+         * <p>This method is intended to prevent stalling critical engine 
operations (such as checkpointing) when user code
+         * is performing long-running work inside {@link WriteClosure}. User 
code should check this method periodically
+         * and stop the execution if it returns {@code true}.
+         *
+         * <p>For most storage engines, this method always returns {@code 
false}. Only engines that require exclusive access
+         * to resources (like {@code aipersist} waiting for checkpoint write 
lock) will return {@code true} when they need
+         * the resources.
+         *
+         * @return {@code true} if the engine needs resources and the user 
should release the lock, {@code false} otherwise.
+         */
+        boolean shouldRelease();
     }
 
     /**
diff --git 
a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/util/LocalLocker.java
 
b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/util/LocalLocker.java
index a8a2f7b180e..ae7fe97039d 100644
--- 
a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/util/LocalLocker.java
+++ 
b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/util/LocalLocker.java
@@ -60,6 +60,11 @@ public class LocalLocker implements Locker {
         return false;
     }
 
+    @Override
+    public boolean shouldRelease() {
+        return false;
+    }
+
     /**
      * Returns {@code true} if passed row ID is currently locked.
      */
diff --git a/modules/storage-page-memory/build.gradle 
b/modules/storage-page-memory/build.gradle
index e79da8d1c42..438f2a0af33 100644
--- a/modules/storage-page-memory/build.gradle
+++ b/modules/storage-page-memory/build.gradle
@@ -53,6 +53,7 @@ dependencies {
     testImplementation testFixtures(project(':ignite-schema'))
     testImplementation testFixtures(project(':ignite-page-memory'))
     testImplementation testFixtures(project(':ignite-metrics'))
+    testImplementation libs.awaitility
     testImplementation libs.jmh.core
 
     testFixturesAnnotationProcessor 
project(':ignite-configuration-annotation-processor')
diff --git 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorage.java
 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorage.java
index 4d18553fde2..7098dd5b040 100644
--- 
a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorage.java
+++ 
b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorage.java
@@ -170,7 +170,7 @@ public class PersistentPageMemoryMvPartitionStorage extends 
AbstractPageMemoryMv
             return busy(() -> {
                 
throwExceptionIfStorageNotInRunnableOrRebalanceState(state.get(), 
this::createStorageInfo);
 
-                LocalLocker locker0 = new LocalLocker(lockByRowId);
+                LocalLocker locker0 = new PersistentPageMemoryLocker();
 
                 checkpointTimeoutLock.checkpointReadLock();
 
@@ -575,4 +575,15 @@ public class PersistentPageMemoryMvPartitionStorage 
extends AbstractPageMemoryMv
     public int emptyDataPageCountInFreeList() {
         return renewableState.freeList().emptyDataPages();
     }
+
+    private class PersistentPageMemoryLocker extends LocalLocker {
+        private PersistentPageMemoryLocker() {
+            super(lockByRowId);
+        }
+
+        @Override
+        public boolean shouldRelease() {
+            return checkpointTimeoutLock.shouldReleaseReadLock();
+        }
+    }
 }
diff --git 
a/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorageTest.java
 
b/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorageTest.java
index 5632d90f6c6..a9472607573 100644
--- 
a/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorageTest.java
+++ 
b/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorageTest.java
@@ -22,14 +22,19 @@ import static 
org.apache.ignite.internal.catalog.commands.CatalogUtils.DEFAULT_P
 import static 
org.apache.ignite.internal.pagememory.persistence.checkpoint.CheckpointState.FINISHED;
 import static org.apache.ignite.internal.schema.BinaryRowMatcher.isRow;
 import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.awaitility.Awaitility.await;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 
 import java.nio.file.Path;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicBoolean;
 import org.apache.ignite.internal.components.LogSyncer;
 import 
org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
 import org.apache.ignite.internal.failure.FailureManager;
@@ -203,4 +208,57 @@ class PersistentPageMemoryMvPartitionStorageTest extends 
AbstractPageMemoryMvPar
 
         assertThat(readConfig, is(equalTo(configWhichFitsInOnePage)));
     }
+
+    @Test
+    void testShouldReleaseReturnsFalseWhenNoCheckpointWaiting() throws 
Exception {
+        AtomicBoolean shouldReleaseValue = new AtomicBoolean(false);
+
+        storage.runConsistently(locker -> {
+            // Initially, no checkpoint is waiting, so shouldRelease should 
return false
+            shouldReleaseValue.set(locker.shouldRelease());
+            return null;
+        });
+
+        assertFalse(shouldReleaseValue.get(), "Locker shouldRelease must 
return false when no checkpoint is waiting");
+    }
+
+    @Test
+    void testNestedRunConsistentlyInheritsLocker() throws Exception {
+        AtomicBoolean outerShouldRelease = new AtomicBoolean(false);
+        AtomicBoolean innerShouldRelease = new AtomicBoolean(false);
+
+        storage.runConsistently(outerLocker -> {
+            // Nested runConsistently should reuse the same locker
+            storage.runConsistently(innerLocker -> {
+                // Both lockers should have the same behavior
+                outerShouldRelease.set(outerLocker.shouldRelease());
+                innerShouldRelease.set(innerLocker.shouldRelease());
+
+                return null;
+            });
+
+            return null;
+        });
+
+        assertEquals(
+                outerShouldRelease.get(),
+                innerShouldRelease.get(),
+                "Inner lockers view of shouldRelease must match"
+        );
+    }
+
+    @Test
+    void testShouldReleaseReturnsTrueWhenWriterIsWaiting() throws Exception {
+        AtomicBoolean shouldReleaseValue = new AtomicBoolean(false);
+
+        storage.runConsistently(locker -> {
+            engine.checkpointManager().scheduleCheckpoint(0, "I want a 
checkpoint");
+            // Initially, no checkpoint is waiting, so shouldRelease should 
return false
+            await().pollInSameThread().until(locker::shouldRelease);
+            shouldReleaseValue.set(locker.shouldRelease());
+            return null;
+        });
+
+        assertTrue(shouldReleaseValue.get(), "Locker shouldRelease must return 
true when checkpoint is scheduled now");
+    }
 }

Reply via email to