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 276c3ce1f92 NIFI-16182 Fixed revision equality check in 
WaitNotifyProtocol (#11527)
276c3ce1f92 is described below

commit 276c3ce1f92aaf2d97f352fb38db61edae7834e6
Author: Peter Turcsanyi <[email protected]>
AuthorDate: Tue Aug 11 14:18:01 2026 +0200

    NIFI-16182 Fixed revision equality check in WaitNotifyProtocol (#11527)
---
 .../processors/standard/WaitNotifyProtocol.java    |  3 +-
 .../standard/TestWaitNotifyProtocol.java           | 70 +++++++++++++++++++++-
 2 files changed, 69 insertions(+), 4 deletions(-)

diff --git 
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/WaitNotifyProtocol.java
 
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/WaitNotifyProtocol.java
index 4b5b04eeb9f..d510f8b70cb 100644
--- 
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/WaitNotifyProtocol.java
+++ 
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/WaitNotifyProtocol.java
@@ -34,6 +34,7 @@ import java.util.ConcurrentModificationException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
@@ -312,7 +313,7 @@ public class WaitNotifyProtocol {
 
         final Object expectedRevision = signal.cachedEntry != null ? 
signal.cachedEntry.getRevision().orElse(null) : null;
         final Object actualRevision = 
current.cachedEntry.getRevision().orElse(null);
-        if (expectedRevision != null && 
!expectedRevision.equals(actualRevision)) {
+        if (expectedRevision != null && !Objects.deepEquals(expectedRevision, 
actualRevision)) {
             throw new ConcurrentModificationException(String.format(
                     "Failed to complete signal [%s]: signal was concurrently 
modified (expected revision %s, found %s).",
                     signalId, expectedRevision, actualRevision));
diff --git 
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestWaitNotifyProtocol.java
 
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestWaitNotifyProtocol.java
index d2c2149f668..2c67194efc9 100644
--- 
a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestWaitNotifyProtocol.java
+++ 
b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestWaitNotifyProtocol.java
@@ -30,10 +30,12 @@ import java.io.ByteArrayOutputStream;
 import java.lang.reflect.Field;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.ConcurrentModificationException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -424,8 +426,9 @@ public class TestWaitNotifyProtocol {
 
     }
 
-    @Test
-    public void testCompleteRemovesSignalFromCache() throws Exception {
+    private void testCompleteRemovesSignalFromCache(final 
AtomicDistributedMapCacheClient<Long> cache,
+                                                    final Map<String, ? 
extends AtomicCacheEntry<String, String, ?>> cacheEntries,
+                                                    final Answer<?> 
successfulReplace) throws Exception {
         doAnswer(successfulReplace).when(cache).replace(any(), any(), any());
         doAnswer(invocation -> {
             cacheEntries.remove(invocation.getArguments()[0]);
@@ -446,7 +449,23 @@ public class TestWaitNotifyProtocol {
     }
 
     @Test
-    public void testCompleteThrowsOnConcurrentModification() throws Exception {
+    public void testCompleteRemovesSignalFromCache() throws Exception {
+        testCompleteRemovesSignalFromCache(cache, cacheEntries, 
successfulReplace);
+    }
+
+    @Test
+    public void testCompleteRemovesSignalFromCacheUsingByteArrayRevision() 
throws Exception {
+        final ByteArrayRevisionCache byteArrayRevisionCache = 
createByteArrayRevisionCache();
+
+        testCompleteRemovesSignalFromCache(
+                byteArrayRevisionCache.cache(),
+                byteArrayRevisionCache.cacheEntries(),
+                byteArrayRevisionCache.successfulReplace());
+    }
+
+    private void testCompleteThrowsOnConcurrentModification(final 
AtomicDistributedMapCacheClient<Long> cache,
+                                                            final Map<String, 
? extends AtomicCacheEntry<String, String, ?>> cacheEntries,
+                                                            final Answer<?> 
successfulReplace) throws Exception {
         doAnswer(successfulReplace).when(cache).replace(any(), any(), any());
 
         final WaitNotifyProtocol protocol = new WaitNotifyProtocol(cache);
@@ -467,6 +486,21 @@ public class TestWaitNotifyProtocol {
         assertTrue(cacheEntries.containsKey(signalId));
     }
 
+    @Test
+    public void testCompleteThrowsOnConcurrentModification() throws Exception {
+        testCompleteThrowsOnConcurrentModification(cache, cacheEntries, 
successfulReplace);
+    }
+
+    @Test
+    public void 
testCompleteThrowsOnConcurrentModificationUsingByteArrayRevision() throws 
Exception {
+        final ByteArrayRevisionCache byteArrayRevisionCache = 
createByteArrayRevisionCache();
+
+        testCompleteThrowsOnConcurrentModification(
+                byteArrayRevisionCache.cache(),
+                byteArrayRevisionCache.cacheEntries(),
+                byteArrayRevisionCache.successfulReplace());
+    }
+
     @Test
     public void testCompleteThrowsWhenAlreadyRemoved() throws Exception {
         doAnswer(successfulReplace).when(cache).replace(any(), any(), any());
@@ -488,4 +522,34 @@ public class TestWaitNotifyProtocol {
         assertEquals(mapper.readTree(expected), mapper.readTree(value));
     }
 
+    private ByteArrayRevisionCache createByteArrayRevisionCache() throws 
Exception {
+        final Map<String, AtomicCacheEntry<String, String, byte[]>> 
cacheEntries = new HashMap<>();
+
+        final Answer<?> successfulReplace = invocation -> {
+            final AtomicCacheEntry<String, String, byte[]> entry = 
invocation.getArgument(0);
+            // generate a unique, random byte sequence for the revision
+            cacheEntries.put(entry.getKey(), new 
AtomicCacheEntry<>(entry.getKey(), entry.getValue(), 
UUID.randomUUID().toString().getBytes()));
+            return true;
+        };
+
+        final AtomicDistributedMapCacheClient<Long> cache = 
mock(AtomicDistributedMapCacheClient.class);
+
+        doAnswer(invocation -> {
+            final AtomicCacheEntry<String, String, byte[]> entry = 
cacheEntries.get(invocation.getArguments()[0]);
+            if (entry == null) {
+                return null;
+            } else {
+                // copy the revision to avoid simple reference-based equality
+                return new AtomicCacheEntry<>(entry.getKey(), 
entry.getValue(), entry.getRevision().map(bytes -> Arrays.copyOf(bytes, 
bytes.length)).orElse(null));
+            }
+        }).when(cache).fetch(any(), any(), any());
+
+        return new ByteArrayRevisionCache(cache, cacheEntries, 
successfulReplace);
+    }
+
+    private record ByteArrayRevisionCache(
+            AtomicDistributedMapCacheClient<Long> cache,
+            Map<String, AtomicCacheEntry<String, String, byte[]>> cacheEntries,
+            Answer<?> successfulReplace) {
+    }
 }

Reply via email to