From 11fc55cc24243e78dc7917f2d4c0b7ee09696e92 Mon Sep 17 00:00:00 2001 From: BenCodez <17074231+BenCodez@users.noreply.github.com> Date: Tue, 13 Oct 2026 19:18:01 -0600 Subject: [PATCH] Avoid redundant deferred HTTP runtime replacement --- .../votingplugin/proxy/VotingPluginProxy.java | 5 +++ .../tests/VotingPluginProxyTest.java | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/proxy/VotingPluginProxy.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/proxy/VotingPluginProxy.java index 0e27ab158..bee05e729 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/proxy/VotingPluginProxy.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/proxy/VotingPluginProxy.java @@ -4984,6 +4984,11 @@ private void reconcileDeferredHttpTransport(long generation) { if (configuredMethod == null) configuredMethod = BungeeMethod.PLUGINMESSAGING; retainHttpForPendingDeliveries(configuredMethod); stillPending = deferredHttpTransportReconciliation; + // A cancelled transport change may restore the live HTTP configuration + // before the manual reload enters this monitor. Nothing needs replacing, + // and the existing listener must retain ownership of its pending queue. + if (!stillPending && configuredMethod == BungeeMethod.HTTP && httpTransportServer != null + && !hasChangedLiveHttpConfiguration()) return; if (!stillPending) httpTransportReconciliationRunning = true; } if (stillPending) { diff --git a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/VotingPluginProxyTest.java b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/VotingPluginProxyTest.java index a96a80b31..b5adc7ca1 100644 --- a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/VotingPluginProxyTest.java +++ b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/VotingPluginProxyTest.java @@ -2646,6 +2646,37 @@ void deferredHttpTransportChangePollsWhenAckPrecedesQueueRemoval() throws Except } } + @Test + void restoringCurrentHttpConfigurationBeforeManualReloadDoesNotReplaceRuntime() throws Exception { + HttpProxyTransportServer transport = Mockito.mock(HttpProxyTransportServer.class); + setProxyField(votingPluginProxy, "httpTransportServer", transport); + var scheduler = Mockito.mock(java.util.concurrent.ScheduledExecutorService.class); + var queued = new java.util.ArrayDeque(); + Mockito.doAnswer(call -> { + queued.add(call.getArgument(0)); + return Mockito.mock(java.util.concurrent.ScheduledFuture.class); + }).when(scheduler).schedule(Mockito.any(Runnable.class), Mockito.anyLong(), Mockito.any(java.util.concurrent.TimeUnit.class)); + votingPluginProxy.setSchedulerForTest(scheduler); + votingPluginProxy.setPendingHttpTransportDeliveries(true); + votingPluginProxy.setMethod(BungeeMethod.HTTP); + Mockito.when(votingPluginProxy.getConfig().getBungeeMethod()).thenReturn("REDIS"); + votingPluginProxy.reloadFromControl(); + assertEquals(1, queued.size()); + + // Configuration publication can precede entry into the manual reload. + // Run the old callback in that exact window, without a timing assumption. + Mockito.when(votingPluginProxy.getConfig().getBungeeMethod()).thenReturn("HTTP"); + queued.remove().run(); + assertEquals(0, votingPluginProxy.getReloadCoreCalls(), + "an unchanged live HTTP listener needs no deferred runtime replacement"); + assertEquals(BungeeMethod.HTTP, votingPluginProxy.getMethod()); + assertFalse(votingPluginProxy.isRetainingHttpTransportForDeferredReconciliation()); + assertTrue(queued.isEmpty(), "a cancelled transport change must not keep polling"); + Mockito.verify(transport, Mockito.never()).close(); + votingPluginProxy.reloadFromControl(); + assertEquals(0, votingPluginProxy.getReloadCoreCalls()); + } + @Test void concurrentManualReloadInvalidatesQueuedDeferredHttpReload() throws Exception { HttpProxyTransportServer transport = Mockito.mock(HttpProxyTransportServer.class);