diff --git a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamTask.java b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamTask.java index 64cc2de558524..8ddd6ba50ed1c 100644 --- a/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamTask.java +++ b/streams/src/main/java/org/apache/kafka/streams/processor/internals/StreamTask.java @@ -397,6 +397,9 @@ boolean isProcessable(final long now) { return false; } } else { + // there's no data in any of the topics; we should reset the enforced + // processing timer + idleStartTime = RecordQueue.UNKNOWN; return false; } } diff --git a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamTaskTest.java b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamTaskTest.java index e0be4f683810a..f7aeb511abb03 100644 --- a/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamTaskTest.java +++ b/streams/src/test/java/org/apache/kafka/streams/processor/internals/StreamTaskTest.java @@ -848,7 +848,7 @@ public void shouldBeProcessableIfWaitedForTooLong() { assertFalse(task.isProcessable(time.milliseconds())); - assertFalse(task.isProcessable(time.milliseconds() + 50L)); + assertFalse(task.isProcessable(time.milliseconds() + 99L)); assertTrue(task.isProcessable(time.milliseconds() + 100L)); assertEquals(1.0, metrics.metric(enforcedProcessMetric).metricValue()); @@ -875,6 +875,52 @@ public void shouldBeProcessableIfWaitedForTooLong() { assertEquals(3.0, metrics.metric(enforcedProcessMetric).metricValue()); } + @Test + public void shouldNotBeProcessableIfNoDataAvailble() { + task = createStatelessTask(createConfig(false), StreamsConfig.METRICS_LATEST); + task.initializeStateStores(); + task.initializeTopology(); + + final MetricName enforcedProcessMetric = metrics.metricName( + "enforced-processing-total", + "stream-task-metrics", + mkMap(mkEntry("thread-id", Thread.currentThread().getName()), mkEntry("task-id", taskId00.toString())) + ); + + assertFalse(task.isProcessable(0L)); + assertEquals(0.0, metrics.metric(enforcedProcessMetric).metricValue()); + + final byte[] bytes = ByteBuffer.allocate(4).putInt(1).array(); + + task.addRecords(partition1, Collections.singleton(new ConsumerRecord<>(topic1, 1, 0, bytes, bytes))); + + assertFalse(task.isProcessable(time.milliseconds())); + + assertFalse(task.isProcessable(time.milliseconds() + 99L)); + + assertTrue(task.isProcessable(time.milliseconds() + 100L)); + assertEquals(1.0, metrics.metric(enforcedProcessMetric).metricValue()); + + // once the buffer is drained and no new records coming, the timer should be reset + task.process(); + + assertFalse(task.isProcessable(time.milliseconds() + 110L)); + assertEquals(1.0, metrics.metric(enforcedProcessMetric).metricValue()); + + // check that after time is reset, we only falls into enforced processing after the + // whole timeout has elapsed again + task.addRecords(partition1, Collections.singleton(new ConsumerRecord<>(topic1, 1, 0, bytes, bytes))); + + assertFalse(task.isProcessable(time.milliseconds() + 150L)); + assertEquals(1.0, metrics.metric(enforcedProcessMetric).metricValue()); + + assertFalse(task.isProcessable(time.milliseconds() + 249L)); + assertEquals(1.0, metrics.metric(enforcedProcessMetric).metricValue()); + + assertTrue(task.isProcessable(time.milliseconds() + 250L)); + assertEquals(2.0, metrics.metric(enforcedProcessMetric).metricValue()); + } + @Test public void shouldPunctuateSystemTimeWhenIntervalElapsed() {