From c241a1480b49f6f18e7866ac31d5aa20d77bf0bd Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Fri, 13 Sep 2024 13:26:21 +0200 Subject: [PATCH] #12268 reset _iterate flag when another processing is scheduled Signed-off-by: Ludovic Orban --- .../eclipse/jetty/util/IteratingCallback.java | 10 +++++ .../jetty/util/IteratingCallbackTest.java | 43 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java index 634dc52a033..af0583187bb 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java @@ -275,6 +275,7 @@ public abstract class IteratingCallback implements Callback } case SCHEDULED: { + _iterate = false; // we won the race against the callback, so the callback has to process and we can break processing _state = State.PENDING; break processing; @@ -300,6 +301,7 @@ public abstract class IteratingCallback implements Callback { if (action != Action.SCHEDULED) throw new IllegalStateException(String.format("%s[action=%s]", this, action)); + _iterate = false; // we lost the race, so we have to keep processing _state = State.PROCESSING; continue; @@ -459,6 +461,14 @@ public abstract class IteratingCallback implements Callback onCompleteFailure(new IOException(failure)); } + boolean isPending() + { + try (AutoLock ignored = _lock.lock()) + { + return _state == State.PENDING; + } + } + /** * @return whether this callback is idle, and {@link #iterate()} needs to be called */ diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java index d588ab518bc..0ade4d4ee37 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java @@ -22,7 +22,11 @@ import org.eclipse.jetty.util.thread.Scheduler; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import static org.awaitility.Awaitility.await; +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; @@ -44,6 +48,45 @@ public class IteratingCallbackTest scheduler.stop(); } + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testIterateWhileProcessingLoopCount(boolean succeededWinsRace) + { + var icb = new IteratingCallback() + { + int counter = 0; + + @Override + protected Action process() + { + int counter = this.counter++; + if (counter == 0) + { + iterate(); + if (succeededWinsRace) + { + succeeded(); + } + else + { + new Thread(() -> + { + await().atMost(5, TimeUnit.SECONDS).until(this::isPending, is(true)); + succeeded(); + }).start(); + } + return Action.SCHEDULED; + } + return Action.IDLE; + } + }; + + icb.iterate(); + + await().atMost(10, TimeUnit.SECONDS).until(icb::isIdle, is(true)); + assertEquals(2, icb.counter); + } + @Test public void testNonWaitingProcess() throws Exception {