Skip to content

Commit 7b15d87

Browse files
ARTEMIS-TBD Closing session after a configured timeout
1 parent e7bd57b commit 7b15d87

12 files changed

Lines changed: 303 additions & 18 deletions

File tree

artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ThreadDumpUtil.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
public final class ThreadDumpUtil {
3636
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
3737

38+
public static String threadDump() {
39+
return threadDump("");
40+
}
41+
3842
public static String threadDump(final String msg) {
3943

4044
try (

artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,8 @@ public static String getDefaultHapolicyBackupStrategy() {
724724

725725
private static final int DEFAULT_CLUSTER_TOPOLOGY_SCANNER_ATTEMPTS = 30;
726726

727+
private static final int DEFAULT_CLOSE_TIMEOUT = 30_000;
728+
727729
/**
728730
* If {@code true} then the ActiveMQ Artemis Server will make use of any Protocol Managers that are in available on
729731
* the classpath. If false then only the core protocol will be available, unless in Embedded mode where users can
@@ -2034,4 +2036,8 @@ public static int getInitialQueueBufferSize() {
20342036
public static int getClusterTopologyScannerAttempts() {
20352037
return DEFAULT_CLUSTER_TOPOLOGY_SCANNER_ATTEMPTS;
20362038
}
2039+
2040+
public static int getCloseTimeout() {
2041+
return DEFAULT_CLOSE_TIMEOUT;
2042+
}
20372043
}

artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,8 @@ public class TransportConstants {
378378

379379
public static final String SHUTDOWN_TIMEOUT = "shutdownTimeout";
380380

381+
public static final String SESSION_CLOSE_TIMEOUT = "sessionCloseTimeout";
382+
381383
/**
382384
* We let this to be defined as a System Variable, as we need a different timeout over our testsuite. When running on
383385
* a real server, this is the default we want. When running on a test suite, we need it to be 0, You should see a
@@ -484,6 +486,7 @@ private static int parseDefaultVariable(String variableName, int defaultValue) {
484486
allowableAcceptorKeys.add(TransportConstants.AUTO_START);
485487
allowableAcceptorKeys.add(TransportConstants.ROUTER);
486488
allowableAcceptorKeys.add(TransportConstants.PROXY_PROTOCOL_ENABLED_PROP_NAME);
489+
allowableAcceptorKeys.add(TransportConstants.SESSION_CLOSE_TIMEOUT);
487490

488491
ALLOWABLE_ACCEPTOR_KEYS = Collections.unmodifiableSet(allowableAcceptorKeys);
489492

artemis-journal/src/main/java/org/apache/activemq/artemis/core/journal/impl/JournalImpl.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,9 +1467,6 @@ public void appendCommitRecord(final long txID,
14671467
final IOCompletion callback,
14681468
final boolean lineUpContext) throws Exception {
14691469
checkJournalIsLoaded();
1470-
if (lineUpContext) {
1471-
lineUpContext(callback);
1472-
}
14731470

14741471
if (logger.isTraceEnabled()) {
14751472
logger.trace("scheduling appendCommitRecord::txID={}", txID);
@@ -1480,6 +1477,9 @@ public void appendCommitRecord(final long txID,
14801477
txcheck.checkErrorCondition();
14811478
}
14821479

1480+
if (lineUpContext) {
1481+
lineUpContext(callback);
1482+
}
14831483

14841484
final SimpleFuture<JournalTransaction> result = newSyncAndCallbackResult(sync, callback);
14851485

@@ -3523,4 +3523,9 @@ public void testCompact() {
35233523
public int getCompactCount() {
35243524
return compactCount;
35253525
}
3526+
3527+
public void markTXError(long txID, Throwable t) {
3528+
JournalTransaction tx = transactions.get(txID);
3529+
tx.onError(-1, t.getMessage());
3530+
}
35263531
}

artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1538,4 +1538,9 @@ default String resolvePropertiesSources(String propertiesFileUrl) {
15381538
default boolean isUsingDatabasePersistence() {
15391539
return getStoreConfiguration() != null && getStoreConfiguration().getStoreType() == StoreConfiguration.StoreType.DATABASE;
15401540
}
1541+
1542+
/** Timeout applied when closing sessions or connections. */
1543+
Configuration setCloseTimeout(int timeout);
1544+
1545+
int getCloseTimeout();
15411546
}

artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,8 @@ public class ConfigurationImpl implements Configuration, Serializable {
474474

475475
private boolean purgePageFolders = ActiveMQDefaultConfiguration.getPurgePageFolders();
476476

477+
private int closeTimeout = ActiveMQDefaultConfiguration.getCloseTimeout();
478+
477479
/**
478480
* Parent folder for all data folders.
479481
*/
@@ -3458,6 +3460,17 @@ public Configuration setMirrorPageTransaction(boolean ignorePageTransactions) {
34583460
return this;
34593461
}
34603462

3463+
@Override
3464+
public int getCloseTimeout() {
3465+
return closeTimeout;
3466+
}
3467+
3468+
@Override
3469+
public ConfigurationImpl setCloseTimeout(int closeTimeout) {
3470+
this.closeTimeout = closeTimeout;
3471+
return this;
3472+
}
3473+
34613474
// extend property utils with ability to auto-fill and locate from collections
34623475
// collection entries are identified by the name() property
34633476
private static class CollectionAutoFillPropertiesUtil extends PropertyUtilsBean {

artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/invm/TransportConstants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ public final class TransportConstants {
3434

3535
public static final String DIRECT_DELIVER = "directDeliver";
3636

37+
public static final String SESSION_CLOSE_TIMEOUT = "sessionCloseTimeout";
38+
39+
public static final int DEFAULT_SESSION_CLOSE_TIMEOUT = 30_000;
40+
3741
private TransportConstants() {
3842
// Utility class
3943
}

artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,4 +1519,8 @@ void slowConsumerDetected(String sessionID,
15191519

15201520
@LogMessage(id = 224152, value = "Closing connection from {} for PROXY Protocol violation. Acceptor {} uses proxyProtocolEnabled={}, but connection {} PROXY Protocol.", level = LogMessage.Level.WARN)
15211521
void proxyProtocolViolation(String remoteAddress, String acceptorName, boolean proxyProtocolEnabled, String actualUsage);
1522+
1523+
//Closing session <this.name> with <conumsers.size()> consumers and transaction <tx> after <timeoutSessionClose> milliseconds
1524+
@LogMessage(id = 224153, value = "Timed out closing ServerSession {} after {} milliseconds with {} consumers, txID={}, connection={},\n{}", level = LogMessage.Level.WARN)
1525+
void sessionCloseTimeout(String name, long timeout, int consumers, long txID, Object connection, String threadDump);
15221526
}

artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerSessionImpl.java

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import java.util.Set;
3333
import java.util.concurrent.ConcurrentHashMap;
3434
import java.util.concurrent.Executor;
35+
import java.util.concurrent.ScheduledFuture;
36+
import java.util.concurrent.TimeUnit;
3537

3638
import org.apache.activemq.artemis.Closeable;
3739
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
@@ -107,6 +109,7 @@
107109
import org.apache.activemq.artemis.utils.CompositeAddress;
108110
import org.apache.activemq.artemis.utils.JsonLoader;
109111
import org.apache.activemq.artemis.utils.PrefixUtil;
112+
import org.apache.activemq.artemis.utils.ThreadDumpUtil;
110113
import org.apache.activemq.artemis.utils.collections.TypedProperties;
111114
import org.apache.activemq.artemis.utils.runnables.AtomicRunnable;
112115
import org.apache.activemq.artemis.utils.runnables.RunnableList;
@@ -202,6 +205,9 @@ public class ServerSessionImpl implements ServerSession, FailureListener {
202205
// server. Both the request and failure listener will
203206
// try to close one session from different threads
204207
// concurrently.
208+
private volatile boolean closing = false;
209+
210+
// When the doClose is called, we will make it actually closed
205211
private volatile boolean closed = false;
206212

207213
private boolean prefixEnabled = false;
@@ -339,7 +345,7 @@ public void disableSecurity() {
339345

340346
@Override
341347
public boolean isClosed() {
342-
return closed;
348+
return closing;
343349
}
344350

345351
@Override
@@ -404,6 +410,10 @@ protected void doClose(final boolean failed) throws Exception {
404410
callback.close(failed);
405411
}
406412
synchronized (this) {
413+
if (closed) {
414+
return;
415+
}
416+
closed = true;
407417
if (server.hasBrokerSessionPlugins()) {
408418
server.callBrokerSessionPlugins(plugin -> plugin.beforeCloseSession(this, failed));
409419
}
@@ -609,7 +619,7 @@ public ServerConsumer createConsumer(final long consumerID,
609619

610620
ServerConsumer consumer;
611621
synchronized (this) {
612-
if (closed) {
622+
if (closing) {
613623
throw ActiveMQMessageBundle.BUNDLE.cannotCreateConsumerOnClosedSession(queueName);
614624
}
615625
consumer = new ServerConsumerImpl(consumerID, this, (QueueBinding) binding, filter, priority, started, browseOnly, storageManager, callback, preAcknowledge, strictUpdateDeliveryCount, managementService, supportLargeMessage, credits, server);
@@ -1777,37 +1787,47 @@ public void close(final boolean failed) {
17771787
@Override
17781788
public void close(final boolean failed, final boolean force) {
17791789
synchronized (this) {
1780-
if (closed) {
1790+
if (closing) {
17811791
return;
17821792
}
1783-
closed = true;
1793+
closing = true;
17841794
}
17851795

17861796
if (force) {
17871797
context.reset();
17881798
}
17891799

1800+
final ScheduledFuture<?> scheduledFuture = server.getScheduledPool().schedule(() -> {
1801+
long txID = tx != null ? tx.getID() : -1L;
1802+
1803+
ActiveMQServerLogger.LOGGER.sessionCloseTimeout(name, server.getConfiguration().getCloseTimeout(), consumers.size(), txID, remotingConnection, ThreadDumpUtil.threadDump());
1804+
1805+
callDoClose(failed);
1806+
}, server.getConfiguration().getCloseTimeout(), TimeUnit.MILLISECONDS);
1807+
17901808
context.executeOnCompletion(new IOCallback() {
17911809
@Override
17921810
public void onError(int errorCode, String errorMessage) {
1793-
callDoClose();
1811+
scheduledFuture.cancel(true);
1812+
callDoClose(failed);
17941813
}
17951814

17961815
@Override
17971816
public void done() {
1798-
callDoClose();
1799-
}
1800-
1801-
private void callDoClose() {
1802-
try {
1803-
doClose(failed);
1804-
} catch (Exception e) {
1805-
ActiveMQServerLogger.LOGGER.errorClosingSession(e);
1806-
}
1817+
scheduledFuture.cancel(true);
1818+
callDoClose(failed);
18071819
}
18081820
});
18091821
}
18101822

1823+
private void callDoClose(boolean failed) {
1824+
try {
1825+
doClose(failed);
1826+
} catch (Exception e) {
1827+
ActiveMQServerLogger.LOGGER.errorClosingSession(e);
1828+
}
1829+
}
1830+
18111831
@Override
18121832
public void closeConsumer(final long consumerID) throws Exception {
18131833
final ServerConsumer consumer = locateConsumer(consumerID);
@@ -2220,7 +2240,7 @@ public void connectionFailed(final ActiveMQException me, boolean failedOver) {
22202240
* This can be invoked from Netty (via channelInactive) when the connection has already been closed causing
22212241
* spurious logging about clearing up resources for failed client connections.
22222242
*/
2223-
if (closed)
2243+
if (closing)
22242244
return;
22252245

22262246
try {

tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/journal/NIOJournalCompactTest.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,47 @@ public void testCompactPrepareRestart() throws Exception {
496496
loadAndCheck();
497497
}
498498

499+
@Test
500+
public void testCommitOnRolledBack() throws Exception {
501+
502+
ExecutorService executorService = Executors.newSingleThreadExecutor();
503+
runAfter(executorService::shutdownNow);
504+
505+
setup(2, 60 * 1024, false);
506+
507+
createJournal();
508+
509+
startJournal();
510+
511+
load();
512+
513+
addTx(1, 2);
514+
rollback(1);
515+
startCompact();
516+
517+
OperationContextImpl context = new OperationContextImpl(executorService);
518+
journal.appendCommitRecord(1, false, context);
519+
CountDownLatch latch = new CountDownLatch(1);
520+
context.executeOnCompletion(new IOCallback() {
521+
@Override
522+
public void done() {
523+
latch.countDown();
524+
}
525+
526+
@Override
527+
public void onError(int errorCode, String errorMessage) {
528+
latch.countDown();
529+
}
530+
});
531+
532+
assertTrue(latch.await(10, TimeUnit.SECONDS));
533+
534+
finishCompact();
535+
536+
stopJournal();
537+
}
538+
539+
499540
@Test
500541
public void testCompactPrepareRestart2() throws Exception {
501542
setup(2, 60 * 1024, false);

0 commit comments

Comments
 (0)