This is an automated email from the ASF dual-hosted git repository.
pvillard31 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new c8197c24020 NIFI-16468: Return rolled-back FlowFiles to the active
queue (#11815)
c8197c24020 is described below
commit c8197c2402046f78e8ec31e70d0f363f4aadd42a
Author: Mark Payne <[email protected]>
AuthorDate: Fri Oct 23 11:52:38 2026 -0400
NIFI-16468: Return rolled-back FlowFiles to the active queue (#11815)
* NIFI-16468: Return rolled-back FlowFiles to the active queue
Session rollback used FlowFileQueue.put(), which places a FlowFile on the
swap queue when the queue is swapping. That puts it behind FlowFiles that were
already in the active queue.
---
.../nifi/controller/queue/FlowFileQueue.java | 10 ++++
.../repository/StandardProcessSession.java | 9 ++--
.../controller/queue/StandardFlowFileQueue.java | 5 ++
.../controller/queue/SwappablePriorityQueue.java | 20 +++++++
.../clustered/SocketLoadBalancedFlowFileQueue.java | 14 +++++
.../queue/clustered/partition/QueuePartition.java | 7 +++
.../clustered/partition/RemoteQueuePartition.java | 5 ++
.../partition/StandardRebalancingPartition.java | 6 +++
.../SwappablePriorityQueueLocalPartition.java | 5 ++
.../nifi/controller/tasks/StatelessFlowTask.java | 9 ++--
.../clustered/TestSwappablePriorityQueue.java | 13 +++++
.../repository/StandardProcessSessionIT.java | 23 ++++++++
.../TestWriteAheadFlowFileRepository.java | 4 ++
.../controller/tasks/TestStatelessFlowTask.java | 63 ++++++++++++++++++++++
.../stateless/queue/StatelessFlowFileQueue.java | 6 +++
15 files changed, 191 insertions(+), 8 deletions(-)
diff --git
a/nifi-framework-api/src/main/java/org/apache/nifi/controller/queue/FlowFileQueue.java
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/queue/FlowFileQueue.java
index d6cd4811539..10f7290f813 100644
---
a/nifi-framework-api/src/main/java/org/apache/nifi/controller/queue/FlowFileQueue.java
+++
b/nifi-framework-api/src/main/java/org/apache/nifi/controller/queue/FlowFileQueue.java
@@ -161,6 +161,16 @@ public interface FlowFileQueue {
*/
void put(FlowFileRecord file);
+ /**
+ * Places the given FlowFile onto the active portion of the queue, never
onto the swap queue, even if the queue is
+ * currently swapping FlowFiles out. This is intended for FlowFiles that
were previously polled from this queue and are
+ * being returned to it, such as on session rollback. Because such a
FlowFile was polled from the active queue, it was
+ * ahead of any FlowFiles that are swapped out, and placing it back onto
the active queue preserves that ordering.
+ *
+ * @param file the FlowFile to place onto the active queue
+ */
+ void putToActive(FlowFileRecord file);
+
/**
* places the given files into the queue
*
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
index b1db067a05d..0a6d214fa06 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/repository/StandardProcessSession.java
@@ -1441,7 +1441,8 @@ public class StandardProcessSession implements
ProcessSession, ProvenanceEventEn
/**
* Rolls back the Record in a manner that is appropriate for the context.
The default implementation
- * is to place the queue back on its original queue, if it exists, or just
ignore it if it has no original queue.
+ * is to place the FlowFile back on the active portion of its original
queue, if it exists, so that it stays ahead of
+ * any FlowFiles that have been swapped out, or just ignore it if it has
no original queue.
* However, subclasses may wish to change the behavior for how Records are
handled when a rollback occurs.
* @param record the Record that is to be rolled back
* @param penalize whether or not the Record should be penalized
@@ -1453,9 +1454,9 @@ public class StandardProcessSession implements
ProcessSession, ProvenanceEventEn
if (penalize) {
final long expirationEpochMillis =
System.currentTimeMillis() +
context.getConnectable().getPenalizationPeriod(TimeUnit.MILLISECONDS);
final FlowFileRecord newFile = new
StandardFlowFileRecord.Builder().fromFlowFile(record.getOriginal()).penaltyExpirationTime(expirationEpochMillis).build();
- originalQueue.put(newFile);
+ originalQueue.putToActive(newFile);
} else {
- originalQueue.put(record.getOriginal());
+ originalQueue.putToActive(record.getOriginal());
}
}
}
@@ -2014,7 +2015,7 @@ public class StandardProcessSession implements
ProcessSession, ProvenanceEventEn
LOG.error("Attempted to pull {} from {} but the Session already has a
FlowFile with the same ID ({}): {}, which was pulled from {}. This means that
the system has two FlowFiles with the" +
" same ID, which should not happen.", flowFile, connection,
flowFile.getId(), conflict.getCurrent(), conflict.getOriginalQueue());
- connection.getFlowFileQueue().put(flowFile);
+ connection.getFlowFileQueue().putToActive(flowFile);
rollback(true, false);
throw new FlowFileAccessException("Attempted to pull a FlowFile with
ID " + flowFile.getId() + " from Connection "
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/StandardFlowFileQueue.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/StandardFlowFileQueue.java
index cbfe04cab2d..c9759a6907c 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/StandardFlowFileQueue.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/StandardFlowFileQueue.java
@@ -119,6 +119,11 @@ public class StandardFlowFileQueue extends
AbstractFlowFileQueue implements Flow
queue.put(file);
}
+ @Override
+ public void putToActive(final FlowFileRecord file) {
+ queue.putToActive(file);
+ }
+
@Override
public void putAll(final Collection<FlowFileRecord> files) {
queue.putAll(files);
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/SwappablePriorityQueue.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/SwappablePriorityQueue.java
index 8778683713e..171113afb4a 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/SwappablePriorityQueue.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/SwappablePriorityQueue.java
@@ -547,6 +547,26 @@ public class SwappablePriorityQueue {
}
}
+ /**
+ * Adds the given FlowFile to the active queue regardless of whether this
queue is in swap mode. This is intended only for FlowFiles that
+ * were previously polled from the active queue and are being returned.
Such a FlowFile was ahead of everything in the swap queue and swap files,
+ * so adding it back to the active queue keeps it ahead of them. This may
cause the active queue to temporarily exceed the swap threshold.
+ *
+ * @param flowFile the FlowFile to add to the active queue
+ */
+ public void putToActive(final FlowFileRecord flowFile) {
+ writeLock.lock();
+ try {
+ incrementActiveQueueSize(1, flowFile.getSize());
+ activeQueue.add(flowFile);
+
+ updateTopPenaltyExpiration();
+ logger.trace("{} put to active queue of {}", flowFile, this);
+ } finally {
+ writeLock.unlock("putToActive");
+ }
+ }
+
public void putAll(final Collection<FlowFileRecord> flowFiles) {
final int numFiles = flowFiles.size();
long bytes = 0L;
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/SocketLoadBalancedFlowFileQueue.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/SocketLoadBalancedFlowFileQueue.java
index dde99caaabd..db05a350d8a 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/SocketLoadBalancedFlowFileQueue.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/SocketLoadBalancedFlowFileQueue.java
@@ -864,6 +864,20 @@ public class SocketLoadBalancedFlowFileQueue extends
AbstractFlowFileQueue imple
return partition;
}
+ @Override
+ public void putToActive(final FlowFileRecord flowFile) {
+ partitionReadLock.lock();
+ try {
+ // As with put(), the size must be adjusted before the FlowFile is
made available on a partition
+ adjustSize(1, flowFile.getSize());
+
+ final QueuePartition partition = getPartition(flowFile);
+ partition.putToActive(flowFile);
+ } finally {
+ partitionReadLock.unlock();
+ }
+ }
+
@Override
public void receiveFromPeer(final Collection<FlowFileRecord> flowFiles)
throws IllegalClusterStateException {
partitionReadLock.lock();
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/QueuePartition.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/QueuePartition.java
index e60401c89d4..683dc0e4b78 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/QueuePartition.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/QueuePartition.java
@@ -60,6 +60,13 @@ public interface QueuePartition {
*/
void put(FlowFileRecord flowFile);
+ /**
+ * Adds the given FlowFile to the active portion of this partition, even
if the partition is currently swapping FlowFiles out.
+ * This is intended for FlowFiles that were previously polled and are
being returned, so that they stay ahead of any swapped-out FlowFiles.
+ * @param flowFile the FlowFile to add
+ */
+ void putToActive(FlowFileRecord flowFile);
+
/**
* Adds the given FlowFiles to this partition
* @param flowFiles the FlowFiles to add
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/RemoteQueuePartition.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/RemoteQueuePartition.java
index c0829a5ab83..71b52193150 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/RemoteQueuePartition.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/RemoteQueuePartition.java
@@ -139,6 +139,11 @@ public class RemoteQueuePartition implements
QueuePartition {
priorityQueue.put(flowFile);
}
+ @Override
+ public void putToActive(final FlowFileRecord flowFile) {
+ priorityQueue.putToActive(flowFile);
+ }
+
@Override
public void putAll(final Collection<FlowFileRecord> flowFiles) {
priorityQueue.putAll(flowFiles);
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/StandardRebalancingPartition.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/StandardRebalancingPartition.java
index 5c819f5fcdf..2b45c5a0a9f 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/StandardRebalancingPartition.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/StandardRebalancingPartition.java
@@ -155,6 +155,12 @@ public class StandardRebalancingPartition implements
RebalancingPartition {
startRebalanceTask();
}
+ @Override
+ public void putToActive(final FlowFileRecord flowFile) {
+ queue.putToActive(flowFile);
+ startRebalanceTask();
+ }
+
@Override
public void putAll(final Collection<FlowFileRecord> flowFiles) {
queue.putAll(flowFiles);
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/SwappablePriorityQueueLocalPartition.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/SwappablePriorityQueueLocalPartition.java
index e6a57bca32f..8d1b13dc6be 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/SwappablePriorityQueueLocalPartition.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/partition/SwappablePriorityQueueLocalPartition.java
@@ -97,6 +97,11 @@ public class SwappablePriorityQueueLocalPartition implements
LocalQueuePartition
priorityQueue.put(flowFile);
}
+ @Override
+ public void putToActive(final FlowFileRecord flowFile) {
+ priorityQueue.putToActive(flowFile);
+ }
+
@Override
public void putAll(final Collection<FlowFileRecord> flowFiles) {
priorityQueue.putAll(flowFiles);
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/tasks/StatelessFlowTask.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/tasks/StatelessFlowTask.java
index b133bf74c02..deae10e4ff2 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/tasks/StatelessFlowTask.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/tasks/StatelessFlowTask.java
@@ -390,18 +390,19 @@ public class StatelessFlowTask {
// this.outputRepositoryRecords and this.cloneResults
if (!inputFlowFiles.isEmpty()) {
if (destinationPort == null) {
- // There is no destination port. Create a penalized FlowFile
and place it back on its original queue.
- // Do not penalize FlowFiles if the group was stopped, though.
That is not a true failure.
+ // There is no destination port. Create a penalized FlowFile
and place it back on the active portion of its original queue,
+ // so that it stays ahead of any FlowFiles that have been
swapped out. Do not penalize FlowFiles if the group was stopped, though.
+ // That is not a true failure.
for (final PolledFlowFile polledFlowFile : inputFlowFiles) {
if (stopped) {
-
polledFlowFile.getOriginalQueue().put(polledFlowFile.getInputFlowFile());
+
polledFlowFile.getOriginalQueue().putToActive(polledFlowFile.getInputFlowFile());
} else {
final FlowFileRecord newFile = new
StandardFlowFileRecord.Builder()
.fromFlowFile(polledFlowFile.getInputFlowFile())
.penaltyExpirationTime(System.currentTimeMillis()
+ 30_000L)
.build();
- polledFlowFile.getOriginalQueue().put(newFile);
+ polledFlowFile.getOriginalQueue().putToActive(newFile);
}
}
} else {
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/TestSwappablePriorityQueue.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/TestSwappablePriorityQueue.java
index 1b0514ef64e..da91edd8ce6 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/TestSwappablePriorityQueue.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/TestSwappablePriorityQueue.java
@@ -22,6 +22,7 @@ import org.apache.nifi.controller.MockSwapManager;
import org.apache.nifi.controller.queue.DropFlowFileAction;
import org.apache.nifi.controller.queue.DropFlowFileRequest;
import org.apache.nifi.controller.queue.FlowFileQueue;
+import org.apache.nifi.controller.queue.FlowFileQueueSize;
import org.apache.nifi.controller.queue.FlowFileQueueSnapshot;
import org.apache.nifi.controller.queue.PollStrategy;
import org.apache.nifi.controller.queue.QueueSize;
@@ -174,6 +175,18 @@ public class TestSwappablePriorityQueue {
queue.put(swapQueueFlowFile1);
}
+ // Poll the highest priority FlowFile from the active queue and return
it, as a session rollback would. It must go back to the
+ // active queue, ahead of the FlowFiles on the swap queue, even though
the queue is in swap mode.
+ final FlowFileRecord returnedFlowFile =
queue.poll(Collections.emptySet(), 0);
+ assertEquals("0", returnedFlowFile.getAttribute("i"));
+ queue.putToActive(returnedFlowFile);
+ queue.acknowledge(returnedFlowFile);
+
+ final FlowFileQueueSize queueSize = queue.getFlowFileQueueSize();
+ assertEquals(iterations, queueSize.getActiveCount());
+ assertEquals(3, queueSize.getSwappedCount());
+ assertEquals(0, queueSize.getUnacknowledgedCount());
+
// The first 10,000 should be ordered. Then all FlowFiles on the swap
queue should be transferred over, as a single unit, just as they would be in a
swap file.
for (int i = 0; i < iterations; i++) {
final FlowFileRecord flowFile = queue.poll(Collections.emptySet(),
0);
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/StandardProcessSessionIT.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/StandardProcessSessionIT.java
index 6baab772b82..3a04fc386ff 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/StandardProcessSessionIT.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/StandardProcessSessionIT.java
@@ -1919,6 +1919,29 @@ public class StandardProcessSessionIT {
assertFalse(flowFileQueue.isActiveQueueEmpty());
}
+ @Test
+ public void testRollbackReturnsFlowFileToFrontOfSwappingQueue() {
+ // The queue's swap threshold is 10,000, so the last FlowFile queued
goes to the swap queue and the queue enters swap mode
+ final int flowFileCount = 10_001;
+ for (int i = 0; i < flowFileCount; i++) {
+ final FlowFileRecord flowFileRecord = new
StandardFlowFileRecord.Builder()
+ .id(i)
+ .addAttribute("uuid",
String.format("00000000-0000-0000-0000-%012d", i))
+ .entryDate(System.currentTimeMillis())
+ .size(0L)
+ .build();
+ flowFileQueue.put(flowFileRecord);
+ }
+
+ final FlowFile firstFlowFile = session.get();
+ assertEquals(0, firstFlowFile.getId());
+ session.rollback();
+
+ final FlowFile flowFileAfterRollback = session.get();
+ assertEquals(0, flowFileAfterRollback.getId());
+ assertEquals(flowFileCount, flowFileQueue.size().getObjectCount());
+ }
+
@Test
public void testCreateEmitted() throws IOException {
final FlowFile newFlowFile = session.create();
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestWriteAheadFlowFileRepository.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestWriteAheadFlowFileRepository.java
index 7033e93dc52..fee21a802d7 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestWriteAheadFlowFileRepository.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestWriteAheadFlowFileRepository.java
@@ -238,6 +238,10 @@ public class TestWriteAheadFlowFileRepository {
public void put(FlowFileRecord file) {
}
+ @Override
+ public void putToActive(FlowFileRecord file) {
+ }
+
@Override
public void putAll(Collection<FlowFileRecord> files) {
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/tasks/TestStatelessFlowTask.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/tasks/TestStatelessFlowTask.java
index 59d3292c9d4..a66e726da53 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/tasks/TestStatelessFlowTask.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/tasks/TestStatelessFlowTask.java
@@ -24,13 +24,18 @@ import org.apache.nifi.connectable.FlowFileActivity;
import org.apache.nifi.connectable.FlowFileTransferCounts;
import org.apache.nifi.connectable.Port;
import org.apache.nifi.controller.MockFlowFileRecord;
+import org.apache.nifi.controller.ProcessScheduler;
+import org.apache.nifi.controller.ScheduledState;
import org.apache.nifi.controller.metrics.ProcessSessionEvent;
import org.apache.nifi.controller.queue.FlowFileQueue;
+import org.apache.nifi.controller.queue.LocalQueuePartitionDiagnostics;
+import org.apache.nifi.controller.queue.StandardFlowFileQueue;
import org.apache.nifi.controller.repository.ContentRepository;
import
org.apache.nifi.controller.repository.DeferredStatelessContentRepository;
import org.apache.nifi.controller.repository.FlowFileEventRepository;
import org.apache.nifi.controller.repository.FlowFileRecord;
import org.apache.nifi.controller.repository.FlowFileRepository;
+import org.apache.nifi.controller.repository.FlowFileSwapManager;
import org.apache.nifi.controller.repository.RepositoryRecord;
import org.apache.nifi.controller.repository.RepositoryRecordType;
import org.apache.nifi.controller.repository.StandardContentRepositoryContext;
@@ -50,10 +55,13 @@ import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.groups.StatelessGroupNode;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.mock.MockComponentLogger;
+import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.provenance.ProvenanceEventRecord;
import org.apache.nifi.provenance.ProvenanceEventRepository;
import org.apache.nifi.provenance.ProvenanceEventType;
import org.apache.nifi.provenance.StandardProvenanceEventRecord;
+import org.apache.nifi.stateless.flow.DataflowTrigger;
+import org.apache.nifi.stateless.flow.DataflowTriggerContext;
import org.apache.nifi.stateless.flow.StatelessDataflow;
import org.apache.nifi.stateless.flow.TriggerResult;
import org.apache.nifi.stateless.repository.ByteArrayContentRepository;
@@ -72,8 +80,11 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.OptionalLong;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -494,6 +505,58 @@ public class TestStatelessFlowTask {
assertEquals(2,
resourceClaimManager.getClaimantCount(flowFile1.getContentClaim().getResourceClaim()));
}
+ @Test
+ public void testFailureReturnsInputFlowFileToFrontOfSwappingQueue() throws
InterruptedException {
+ // The source queue's swap threshold is 3, so the fourth FlowFile goes
to the swap queue and the queue enters swap mode
+ final StandardFlowFileQueue sourceQueue = new
StandardFlowFileQueue("source-queue", flowFileRepository, provenanceRepository,
mock(ProcessScheduler.class),
+ mock(FlowFileSwapManager.class), null, 3, "0 sec", 0L, "0 B");
+ final List<FlowFileRecord> queuedFlowFiles = new ArrayList<>();
+ for (int i = 0; i < 4; i++) {
+ final FlowFileRecord flowFile = new MockFlowFileRecord();
+ queuedFlowFiles.add(flowFile);
+ sourceQueue.put(flowFile);
+ }
+
+ final Connection sourceConnection = mock(Connection.class);
+ when(sourceConnection.getFlowFileQueue()).thenReturn(sourceQueue);
+
when(inputPort1.getIncomingConnections()).thenReturn(List.of(sourceConnection));
+
+ final AtomicReference<ScheduledState> desiredState = new
AtomicReference<>();
+ when(statelessGroupNode.getDesiredState()).thenAnswer(invocation ->
desiredState.get());
+
+ // The group is stopped while the flow is running, so the input
FlowFile is returned to the source queue without a penalty
+ final TriggerResult canceledResult = mock(TriggerResult.class);
+ when(canceledResult.isCanceled()).thenReturn(true);
+ triggerFailingFlow(canceledResult, desiredState,
ScheduledState.STOPPED);
+
+ final FlowFileRecord flowFileAfterCancel = sourceQueue.poll(new
HashSet<>());
+ assertEquals(queuedFlowFiles.getFirst().getId(),
flowFileAfterCancel.getId());
+ sourceQueue.acknowledge(flowFileAfterCancel);
+
+ // The flow fails, so the input FlowFile is penalized and returned to
the active queue, ahead of the FlowFile on the swap queue
+ final TriggerResult failedResult = mock(TriggerResult.class);
+ when(failedResult.getFailureCause()).thenReturn(Optional.of(new
ProcessException("Intentional failure")));
+ triggerFailingFlow(failedResult, desiredState, ScheduledState.RUNNING);
+
+ final LocalQueuePartitionDiagnostics diagnostics =
sourceQueue.getQueueDiagnostics().getLocalQueuePartitionDiagnostics();
+ assertEquals(2, diagnostics.getActiveQueueSize().getObjectCount());
+ assertEquals(1, diagnostics.getSwapQueueSize().getObjectCount());
+ }
+
+ private void triggerFailingFlow(final TriggerResult triggerResult, final
AtomicReference<ScheduledState> desiredState, final ScheduledState
stateAfterTrigger) throws InterruptedException {
+ final DataflowTrigger dataflowTrigger = mock(DataflowTrigger.class);
+ when(dataflowTrigger.getResult(anyLong(),
any(TimeUnit.class))).thenReturn(Optional.of(triggerResult));
+
when(statelessFlow.trigger(any(DataflowTriggerContext.class))).thenAnswer(invocation
-> {
+ final DataflowTriggerContext triggerContext =
invocation.getArgument(0);
+
triggerContext.getFlowFileSupplier().getFlowFile(inputPort1.getName());
+ desiredState.set(stateAfterTrigger);
+ return dataflowTrigger;
+ });
+
+ desiredState.set(ScheduledState.RUNNING);
+ task.trigger();
+ }
+
private Connection mockConnection() {
final FlowFileQueue queue = mock(FlowFileQueue.class);
final Connection connection = mock(Connection.class);
diff --git
a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/queue/StatelessFlowFileQueue.java
b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/queue/StatelessFlowFileQueue.java
index 9d6a231363b..1bb137dbdf5 100644
---
a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/queue/StatelessFlowFileQueue.java
+++
b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/queue/StatelessFlowFileQueue.java
@@ -181,6 +181,12 @@ public class StatelessFlowFileQueue implements
DrainableFlowFileQueue {
totalBytes.addAndGet(flowFile.getSize());
}
+ @Override
+ public void putToActive(final FlowFileRecord flowFile) {
+ // This queue never swaps FlowFiles out, so every FlowFile is already
on the active queue
+ put(flowFile);
+ }
+
@Override
public void putAll(final Collection<FlowFileRecord> flowFiles) {
// Order the FlowFiles in the same order they were transferred by the
Processor. This ensures that we keep the ordering provided by the Processor.