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");
+ }
}