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..4d7e3855ca --- /dev/null +++ b/donkey/src/test/java/com/mirth/connect/donkey/server/channel/PollConnectorHaltTest.java @@ -0,0 +1,92 @@ +// 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(); + } + + /** 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 { + String channelId = "poll-stop-redeploy-test"; + + PollConnector first = connector(channelId); + first.start(); + first.stop(); + + PollConnector second = connector(channelId); + second.start(); + second.stop(); + } +}