Denovo1998 commented on code in PR #26146:
URL: https://github.com/apache/pulsar/pull/26146#discussion_r3565550993


##########
pulsar-broker/src/test/java/org/apache/pulsar/broker/loadbalance/extensions/channel/ServiceUnitStateChannelTest.java:
##########
@@ -280,6 +281,146 @@ public void channelOwnerTest() throws Exception {
         }
     }
 
+    @Test(priority = 1)
+    public void testCompletedGetOwnerRequestDoesNotRemoveNewRequest() throws 
Exception {
+        ServiceUnitStateChannelImpl channel = (ServiceUnitStateChannelImpl) 
channel1;
+        String serviceUnit = namespaceName + "/0x10000000_0x10000001";
+        var getOwnerRequests = channel.getOwnerRequests();
+        getOwnerRequests.remove(serviceUnit);
+        CompletableFuture<String> oldRequest = 
channel.dedupeGetOwnerRequest(serviceUnit);
+        assertEquals(getOwnerRequests.get(serviceUnit), oldRequest);
+
+        CountDownLatch staleCallbackRunning = new CountDownLatch(1);
+        CountDownLatch releaseStaleCallback = new CountDownLatch(1);
+        oldRequest.whenComplete((__, ___) -> {
+            staleCallbackRunning.countDown();
+            try {
+                assertTrue(releaseStaleCallback.await(5, TimeUnit.SECONDS));
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new CompletionException(e);
+            }
+        });
+
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        try {
+            Future<?> completeOldRequest = executor.submit(() -> 
oldRequest.complete(brokerId1));
+            assertTrue(staleCallbackRunning.await(5, TimeUnit.SECONDS),
+                    "The stale get-owner completion callback did not start");
+            assertTrue(getOwnerRequests.get(serviceUnit) == oldRequest,
+                    "The stale cleanup must not run before the new request is 
installed");
+
+            // Replace the map entry before releasing the old callback.
+            CompletableFuture<String> newRequest = new CompletableFuture<>();
+            getOwnerRequests.put(serviceUnit, newRequest);
+            releaseStaleCallback.countDown();
+            completeOldRequest.get(5, TimeUnit.SECONDS);
+
+            assertTrue(getOwnerRequests.get(serviceUnit) == newRequest,
+                    "A stale get-owner cleanup must not remove a newer request 
future");
+        } finally {
+            releaseStaleCallback.countDown();
+            executor.shutdownNow();
+            getOwnerRequests.remove(serviceUnit);
+        }
+    }
+
+    @Test(priority = 1)
+    public void testSkippedEventDoesNotRemoveNewGetOwnerRequest() throws 
Exception {
+        ServiceUnitStateChannelImpl channel = (ServiceUnitStateChannelImpl) 
channel1;
+        String serviceUnit = namespaceName + "/0x10000002_0x10000003";
+        var getOwnerRequests = channel.getOwnerRequests();
+        CompletableFuture<String> oldRequest = new CompletableFuture<>();
+        try {
+            overrideTableView(channel, serviceUnit, new 
ServiceUnitStateData(Owned, brokerId1, 1));
+            getOwnerRequests.put(serviceUnit, oldRequest);
+
+            CountDownLatch staleCallbackRunning = new CountDownLatch(1);
+            CountDownLatch releaseStaleCallback = new CountDownLatch(1);
+            oldRequest.whenComplete((__, ___) -> {
+                staleCallbackRunning.countDown();
+                try {
+                    assertTrue(releaseStaleCallback.await(5, 
TimeUnit.SECONDS));
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    throw new CompletionException(e);
+                }
+            });
+
+            ExecutorService executor = Executors.newSingleThreadExecutor();
+            try {
+                Future<?> handleSkippedEvent = executor.submit(() -> 
channel.handleSkippedEvent(serviceUnit));
+                assertTrue(staleCallbackRunning.await(5, TimeUnit.SECONDS),
+                        "The stale skipped-event callback did not start");
+                assertTrue(getOwnerRequests.get(serviceUnit) == oldRequest,
+                        "The skipped-event cleanup must not run before the new 
request is installed");
+
+                // Replace the map entry before releasing the old callback.
+                CompletableFuture<String> newRequest = new 
CompletableFuture<>();
+                getOwnerRequests.put(serviceUnit, newRequest);
+                releaseStaleCallback.countDown();
+                handleSkippedEvent.get(5, TimeUnit.SECONDS);
+
+                assertEquals(oldRequest.getNow(null), brokerId1);
+                assertTrue(getOwnerRequests.get(serviceUnit) == newRequest,
+                        "A stale skipped-event cleanup must not remove a newer 
request future");
+            } finally {
+                releaseStaleCallback.countDown();
+                executor.shutdownNow();
+            }
+        } finally {
+            getOwnerRequests.remove(serviceUnit);
+            oldRequest.cancel(false);
+            overrideTableView(channel, serviceUnit, null);
+        }
+    }
+
+    @Test(priority = 1)
+    public void testCompletedCleanupJobDoesNotRemoveNewCleanupJob() throws 
Exception {
+        ServiceUnitStateChannelImpl channel = (ServiceUnitStateChannelImpl) 
channel1;
+        String broker = brokerId3;
+        var cleanupJobs = channel.getCleanupJobs();
+        cleanupJobs.remove(broker);
+        channel.scheduleCleanup(broker, 60L);
+        CompletableFuture<Void> oldJob = cleanupJobs.get(broker);
+        assertNotNull(oldJob);
+
+        CountDownLatch staleCallbackRunning = new CountDownLatch(1);
+        CountDownLatch releaseStaleCallback = new CountDownLatch(1);
+        oldJob.whenComplete((__, ___) -> {

Review Comment:
   This has the same dependent-action ordering assumption as 
testCompletedGetOwnerRequestDoesNotRemoveNewRequest: the blocking callback 
registered here must run before the cleanup callback registered by 
scheduleCleanup, but CompletableFuture does not guarantee that ordering. We can 
avoid the latches and executor by replacing the map entry with newJob before 
calling oldJob.complete(null). The old implementation will then 
deterministically remove newJob, while the conditional-remove implementation 
will preserve it.



##########
pulsar-broker/src/test/java/org/apache/pulsar/broker/loadbalance/extensions/channel/ServiceUnitStateChannelTest.java:
##########
@@ -280,6 +281,146 @@ public void channelOwnerTest() throws Exception {
         }
     }
 
+    @Test(priority = 1)
+    public void testCompletedGetOwnerRequestDoesNotRemoveNewRequest() throws 
Exception {
+        ServiceUnitStateChannelImpl channel = (ServiceUnitStateChannelImpl) 
channel1;
+        String serviceUnit = namespaceName + "/0x10000000_0x10000001";
+        var getOwnerRequests = channel.getOwnerRequests();
+        getOwnerRequests.remove(serviceUnit);
+        CompletableFuture<String> oldRequest = 
channel.dedupeGetOwnerRequest(serviceUnit);
+        assertEquals(getOwnerRequests.get(serviceUnit), oldRequest);
+
+        CountDownLatch staleCallbackRunning = new CountDownLatch(1);
+        CountDownLatch releaseStaleCallback = new CountDownLatch(1);
+        oldRequest.whenComplete((__, ___) -> {

Review Comment:
   This test assumes that the whenComplete registered here runs before the 
cleanup callback registered by dedupeGetOwnerRequest. CompletableFuture does 
not specify the execution order of dependent actions. If the production cleanup 
runs first, the patched implementation can legitimately remove the old mapping 
before staleCallbackRunning is released, causing the assertion below to fail. 
Could we make this deterministic by installing newRequest before completing 
oldRequest, then completing the old request and asserting that the new mapping 
remains? That would reproduce the stale cleanup without relying on callback 
ordering or an executor.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to