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

andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git

commit 3bb785b55154b386e217e04c147d932d82ce87c5
Author: Andy Seaborne <[email protected]>
AuthorDate: Tue Feb 17 18:23:09 2026 +0000

    GH-3755: Cancellation contract
---
 .../sparql/engine/iterator/QueryIteratorBase.java  | 40 +++++++++++++++-------
 1 file changed, 28 insertions(+), 12 deletions(-)

diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIteratorBase.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIteratorBase.java
index 68fd08623e..1955fee4eb 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIteratorBase.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/QueryIteratorBase.java
@@ -52,13 +52,12 @@ public abstract class QueryIteratorBase
     // === Cancellation
 
     // .cancel() can be called asynchronously with iterator execution.
-    // It causes notification to cancellation to be made, once, by calling 
.requestCancel()
+    // It causes notification of cancellation to be made, once, by calling 
.requestCancel()
     // which is called synchronously with .cancel() and asynchronously with 
iterator execution.
     private final AtomicBoolean requestingCancel;
 
-    // Used so that requestCanel is called once.
+    // cancelOnce and cancelLock are used to ensure requestCancel() is called 
once.
     private volatile boolean cancelOnce = false;
-
     private Object cancelLock = new Object();
 
     /** QueryIteratorBase with no cancellation facility */
@@ -97,10 +96,17 @@ public abstract class QueryIteratorBase
     /** Close the iterator. */
     protected abstract void closeIterator();
 
-    /** Propagates the cancellation request - called asynchronously with the 
iterator itself */
+    /**
+     * Propagates the cancellation request - called asynchronously with the 
iterator itself.
+     * Implementations of {@code requestCancel()} must be aware that
+     * the iterator maybe concurrently in-progress on the iteration thread,
+     * that is, it may be inside {@link #hasNextBinding} or {@link 
#nextBinding}.
+     *
+     */
     protected abstract void requestCancel();
 
     /* package */ boolean getRequestingCancel() {
+        // Testing only.
         return requestingCancel();
     }
 
@@ -116,8 +122,7 @@ public abstract class QueryIteratorBase
             return false;
 
         if ( cancelOnce ) {
-            // Try to close first to release resources (in case the user
-            // doesn't have a close() call in a finally block)
+            // cancel() has been called and may be in-progress.
             close();
             throw new QueryCancelledException();
         }
@@ -136,15 +141,15 @@ public abstract class QueryIteratorBase
     }
 
     /**
-     * final - autoclose and registration relies on it - implement
-     * moveToNextBinding()
+     * final - autoclose and registration relies on it -
+     * Subclasses implement {@link #moveToNextBinding()}.
      */
     @Override
     public final Binding next() {
         return nextBinding();
     }
 
-    /** final - subclasses implement moveToNextBinding() */
+    /** final - subclasses implement {@link #moveToNextBinding()}. */
     @Override
     public final Binding nextBinding() {
         try {
@@ -187,6 +192,11 @@ public abstract class QueryIteratorBase
         throw new UnsupportedOperationException(Lib.className(this) + 
".remove");
     }
 
+    /**
+     * Close the iterator.
+     * This must be called on the iteration thread.
+     * Use {@link #cancel()} to asynchronously stop an iterator.
+     */
     @Override
     public void close() {
         if ( finished )
@@ -199,7 +209,13 @@ public abstract class QueryIteratorBase
         finished = true;
     }
 
-    /** Cancel this iterator : this is called, possibly asynchronously, to 
cancel an iterator.*/
+    /**
+     * Cancel this iterator; this can be called asynchronously.
+     * <p>
+     * Once cancelled, the query iterator will stop, call {@link #close()}
+     * then throw {@link QueryCancelledException}. This happens on the thread
+     * that is iterating, where the iterator was created.
+     */
     @Override
     public final void cancel() {
         synchronized (cancelLock) {
@@ -207,8 +223,8 @@ public abstract class QueryIteratorBase
                 // Need to set the flags before allowing subclasses to handle 
requestCancel() in order
                 // to prevent a race condition. We want to be sure that calls 
to have hasNext()/nextBinding()
                 // will definitely throw a QueryCancelledException in this 
class and
-                // not allow a situation in which a subclass component thinks 
it is cancelled,
-                // while this class does not.
+                // not allow a situation in which a subclass component thinks
+                // it is cancelled, while this class does not.
                 if ( requestingCancel != null )
                     // Signalling from timeouts
                     requestingCancel.set(true);

Reply via email to