diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/PunctuationQueue.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/PunctuationQueue.java index 80eda6c9dd8ba..354c602cb3f23 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/PunctuationQueue.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/PunctuationQueue.java @@ -52,7 +52,10 @@ boolean mayPunctuate(final long timestamp, final PunctuationType type, final Pro if (!sched.isCancelled()) { processorNodePunctuator.punctuate(sched.node(), timestamp, type, sched.punctuator()); - pq.add(sched.next(timestamp)); + // sched can be cancelled from within the punctuator + if (!sched.isCancelled()) { + pq.add(sched.next(timestamp)); + } punctuated = true; } diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/PunctuationQueueTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/PunctuationQueueTest.java index 09c7a0a318344..e799688c428fc 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/PunctuationQueueTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/PunctuationQueueTest.java @@ -17,6 +17,7 @@ package org.apache.kafka.streams.processor.internals; import org.apache.kafka.streams.processor.AbstractProcessor; +import org.apache.kafka.streams.processor.Cancellable; import org.apache.kafka.streams.processor.ProcessorContext; import org.apache.kafka.streams.processor.PunctuationType; import org.apache.kafka.streams.processor.Punctuator; @@ -126,6 +127,42 @@ public void punctuate(ProcessorNode node, long time, PunctuationType type, Punct assertEquals(4, processor.punctuatedAt.size()); } + @Test + public void testPunctuationIntervalCancelFromPunctuator() { + final TestProcessor processor = new TestProcessor(); + final ProcessorNode node = new ProcessorNode<>("test", processor, null); + final PunctuationQueue queue = new PunctuationQueue(); + final Punctuator punctuator = new Punctuator() { + @Override + public void punctuate(long timestamp) { + node.processor().punctuate(timestamp); + } + }; + + final PunctuationSchedule sched = new PunctuationSchedule(node, 0L, 100L, punctuator); + final long now = sched.timestamp - 100L; + + final Cancellable cancellable = queue.schedule(sched); + + ProcessorNodePunctuator processorNodePunctuator = new ProcessorNodePunctuator() { + @Override + public void punctuate(ProcessorNode node, long time, PunctuationType type, Punctuator punctuator) { + punctuator.punctuate(time); + // simulate scheduler cancelled from within punctuator + cancellable.cancel(); + } + }; + + queue.mayPunctuate(now, PunctuationType.STREAM_TIME, processorNodePunctuator); + assertEquals(0, processor.punctuatedAt.size()); + + queue.mayPunctuate(now + 100L, PunctuationType.STREAM_TIME, processorNodePunctuator); + assertEquals(1, processor.punctuatedAt.size()); + + queue.mayPunctuate(now + 200L, PunctuationType.STREAM_TIME, processorNodePunctuator); + assertEquals(1, processor.punctuatedAt.size()); + } + private static class TestProcessor extends AbstractProcessor { public final ArrayList punctuatedAt = new ArrayList<>();