From a1d21f9375d843e10cb5068c3fe5804eaccf7d05 Mon Sep 17 00:00:00 2001 From: iacsha <122627209+iacsha@users.noreply.github.com> Date: Thu, 12 Nov 2026 13:42:14 +0000 Subject: [PATCH 1/2] Release the poll scheduler on halt so the channel can redeploy (fixes #571) PollConnector.stop() shuts its Quartz scheduler down, but halt() only interrupted the running job. StdSchedulerFactory keeps a running scheduler under its instance name, which is the channel ID, so the next deploy of the same channel got the old scheduler back with the poll job still stored, and start() failed with ObjectAlreadyExistsException. halt() now also calls scheduler.shutdown(false), which does not wait for the interrupted job. Adds PollConnectorHaltTest; the halt case fails with the reported exception without this change. Signed-off-by: iacsha <122627209+iacsha@users.noreply.github.com> --- .../donkey/server/channel/PollConnector.java | 5 ++ .../server/channel/PollConnectorHaltTest.java | 81 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 donkey/src/test/java/com/mirth/connect/donkey/server/channel/PollConnectorHaltTest.java diff --git a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/PollConnector.java b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/PollConnector.java index d279d274a0..984d80ef63 100644 --- a/donkey/src/main/java/com/mirth/connect/donkey/server/channel/PollConnector.java +++ b/donkey/src/main/java/com/mirth/connect/donkey/server/channel/PollConnector.java @@ -67,6 +67,11 @@ public void halt() throws ConnectorTaskException, InterruptedException { if (scheduler != null) { try { scheduler.interrupt(job.getKey()); + // Release the scheduler as stop() does, without waiting for the interrupted job. + // StdSchedulerFactory keeps a running scheduler under the channel ID, so the next + // deploy would get this one back with the poll job still stored and fail with + // ObjectAlreadyExistsException. See OpenIntegrationEngine/engine#571. + scheduler.shutdown(false); } catch (Exception e) { throw new ConnectorTaskException(e); } diff --git a/donkey/src/test/java/com/mirth/connect/donkey/server/channel/PollConnectorHaltTest.java b/donkey/src/test/java/com/mirth/connect/donkey/server/channel/PollConnectorHaltTest.java new file mode 100644 index 0000000000..b183510851 --- /dev/null +++ b/donkey/src/test/java/com/mirth/connect/donkey/server/channel/PollConnectorHaltTest.java @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: Open Integration Engine Contributors + +package com.mirth.connect.donkey.server.channel; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.Test; + +import com.mirth.connect.donkey.server.event.EventDispatcher; +import com.mirth.connect.donkey.test.util.TestPollConnectorProperties; + +/** + * A halted poll connector must release its Quartz scheduler, so the channel can be deployed and + * started again on the same engine. See OpenIntegrationEngine/engine#571. + */ +public class PollConnectorHaltTest { + + private static class NoopPollConnector extends PollConnector { + @Override + public void onDeploy() {} + + @Override + public void onUndeploy() {} + + @Override + public void onStart() {} + + @Override + public void onStop() {} + + @Override + public void onHalt() {} + + @Override + protected void poll() {} + + @Override + public void handleRecoveredResponse(DispatchResult dispatchResult) {} + } + + private static PollConnector connector(String channelId) { + Channel channel = mock(Channel.class); + when(channel.getEventDispatcher()).thenReturn(mock(EventDispatcher.class)); + + PollConnector connector = new NoopPollConnector(); + connector.setChannelId(channelId); + connector.setChannel(channel); + connector.setConnectorProperties(new TestPollConnectorProperties()); + return connector; + } + + /** Before the fix, the second start threw ObjectAlreadyExistsException for the poll job. */ + @Test + public void startAfterHalt_onANewConnectorForTheSameChannel() throws Exception { + String channelId = "poll-halt-redeploy-test"; + + PollConnector first = connector(channelId); + first.start(); + first.halt(); + + PollConnector second = connector(channelId); + second.start(); + second.stop(); + } + + /** Stop already released the scheduler; it keeps working. */ + @Test + public void startAfterStop_onANewConnectorForTheSameChannel() throws Exception { + String channelId = "poll-stop-redeploy-test"; + + PollConnector first = connector(channelId); + first.start(); + first.stop(); + + PollConnector second = connector(channelId); + second.start(); + second.stop(); + } +} From d41fcba9cdf73b18f151456955f1c0360e963bfa Mon Sep 17 00:00:00 2001 From: iacsha <122627209+iacsha@users.noreply.github.com> Date: Fri, 13 Nov 2026 01:17:48 +0000 Subject: [PATCH 2/2] Test Halt then Start on the same poll connector (#571) Signed-off-by: iacsha <122627209+iacsha@users.noreply.github.com> --- .../donkey/server/channel/PollConnectorHaltTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/donkey/src/test/java/com/mirth/connect/donkey/server/channel/PollConnectorHaltTest.java b/donkey/src/test/java/com/mirth/connect/donkey/server/channel/PollConnectorHaltTest.java index b183510851..4d7e3855ca 100644 --- a/donkey/src/test/java/com/mirth/connect/donkey/server/channel/PollConnectorHaltTest.java +++ b/donkey/src/test/java/com/mirth/connect/donkey/server/channel/PollConnectorHaltTest.java @@ -65,6 +65,17 @@ public void startAfterHalt_onANewConnectorForTheSameChannel() throws Exception { second.stop(); } + /** Halt then Start on the same connector, as the Dashboard does without undeploying. */ + @Test + public void startAfterHalt_onTheSameConnector() throws Exception { + PollConnector connector = connector("poll-halt-restart-test"); + connector.start(); + connector.halt(); + + connector.start(); + connector.stop(); + } + /** Stop already released the scheduler; it keeps working. */ @Test public void startAfterStop_onANewConnectorForTheSameChannel() throws Exception {