|
|
|
@ -1,5 +1,5 @@ |
|
|
|
/* |
|
|
|
/* |
|
|
|
* Copyright 2002-2023 the original author or authors. |
|
|
|
* Copyright 2002-2024 the original author or authors. |
|
|
|
* |
|
|
|
* |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
@ -20,6 +20,9 @@ import java.util.HashSet; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.concurrent.Executor; |
|
|
|
import java.util.concurrent.Executor; |
|
|
|
|
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
|
|
|
|
|
import java.util.concurrent.locks.Lock; |
|
|
|
|
|
|
|
import java.util.concurrent.locks.ReentrantLock; |
|
|
|
|
|
|
|
|
|
|
|
import jakarta.jms.Connection; |
|
|
|
import jakarta.jms.Connection; |
|
|
|
import jakarta.jms.JMSException; |
|
|
|
import jakarta.jms.JMSException; |
|
|
|
@ -190,6 +193,8 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
@Nullable |
|
|
|
@Nullable |
|
|
|
private Executor taskExecutor; |
|
|
|
private Executor taskExecutor; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private boolean virtualThreads = false; |
|
|
|
|
|
|
|
|
|
|
|
private BackOff backOff = new FixedBackOff(DEFAULT_RECOVERY_INTERVAL, Long.MAX_VALUE); |
|
|
|
private BackOff backOff = new FixedBackOff(DEFAULT_RECOVERY_INTERVAL, Long.MAX_VALUE); |
|
|
|
|
|
|
|
|
|
|
|
private int cacheLevel = CACHE_AUTO; |
|
|
|
private int cacheLevel = CACHE_AUTO; |
|
|
|
@ -221,7 +226,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
|
|
|
|
|
|
|
|
private Object currentRecoveryMarker = new Object(); |
|
|
|
private Object currentRecoveryMarker = new Object(); |
|
|
|
|
|
|
|
|
|
|
|
private final Object recoveryMonitor = new Object(); |
|
|
|
private final Lock recoveryLock = new ReentrantLock(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -241,6 +246,25 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
this.taskExecutor = taskExecutor; |
|
|
|
this.taskExecutor = taskExecutor; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Specify whether the default {@link SimpleAsyncTaskExecutor} should be |
|
|
|
|
|
|
|
* configured to use virtual threads instead of platform threads, for |
|
|
|
|
|
|
|
* efficient blocking behavior in listener threads on Java 21 or higher. |
|
|
|
|
|
|
|
* This is off by default, setting up one platform thread per consumer. |
|
|
|
|
|
|
|
* <p>Only applicable if the internal default executor is in use rather than |
|
|
|
|
|
|
|
* an externally provided {@link #setTaskExecutor TaskExecutor} instance. |
|
|
|
|
|
|
|
* The thread name prefix for virtual threads will be derived from the |
|
|
|
|
|
|
|
* listener container's bean name, just like with default platform threads. |
|
|
|
|
|
|
|
* <p>Alternatively, pass in a virtual threads based executor through |
|
|
|
|
|
|
|
* {@link #setTaskExecutor} (with externally defined thread naming). |
|
|
|
|
|
|
|
* @since 6.2 |
|
|
|
|
|
|
|
* @see #setTaskExecutor |
|
|
|
|
|
|
|
* @see SimpleAsyncTaskExecutor#setVirtualThreads |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
public void setVirtualThreads(boolean virtualThreads) { |
|
|
|
|
|
|
|
this.virtualThreads = virtualThreads; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Specify the {@link BackOff} instance to use to compute the interval |
|
|
|
* Specify the {@link BackOff} instance to use to compute the interval |
|
|
|
* between recovery attempts. If the {@link BackOffExecution} implementation |
|
|
|
* between recovery attempts. If the {@link BackOffExecution} implementation |
|
|
|
@ -364,12 +388,16 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void setConcurrentConsumers(int concurrentConsumers) { |
|
|
|
public void setConcurrentConsumers(int concurrentConsumers) { |
|
|
|
Assert.isTrue(concurrentConsumers > 0, "'concurrentConsumers' value must be at least 1 (one)"); |
|
|
|
Assert.isTrue(concurrentConsumers > 0, "'concurrentConsumers' value must be at least 1 (one)"); |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
this.concurrentConsumers = concurrentConsumers; |
|
|
|
this.concurrentConsumers = concurrentConsumers; |
|
|
|
if (this.maxConcurrentConsumers < concurrentConsumers) { |
|
|
|
if (this.maxConcurrentConsumers < concurrentConsumers) { |
|
|
|
this.maxConcurrentConsumers = concurrentConsumers; |
|
|
|
this.maxConcurrentConsumers = concurrentConsumers; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -380,9 +408,13 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
* @see #getActiveConsumerCount() |
|
|
|
* @see #getActiveConsumerCount() |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public final int getConcurrentConsumers() { |
|
|
|
public final int getConcurrentConsumers() { |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
return this.concurrentConsumers; |
|
|
|
return this.concurrentConsumers; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -404,9 +436,13 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void setMaxConcurrentConsumers(int maxConcurrentConsumers) { |
|
|
|
public void setMaxConcurrentConsumers(int maxConcurrentConsumers) { |
|
|
|
Assert.isTrue(maxConcurrentConsumers > 0, "'maxConcurrentConsumers' value must be at least 1 (one)"); |
|
|
|
Assert.isTrue(maxConcurrentConsumers > 0, "'maxConcurrentConsumers' value must be at least 1 (one)"); |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
this.maxConcurrentConsumers = Math.max(maxConcurrentConsumers, this.concurrentConsumers); |
|
|
|
this.maxConcurrentConsumers = Math.max(maxConcurrentConsumers, this.concurrentConsumers); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -417,9 +453,13 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
* @see #getActiveConsumerCount() |
|
|
|
* @see #getActiveConsumerCount() |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public final int getMaxConcurrentConsumers() { |
|
|
|
public final int getMaxConcurrentConsumers() { |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
return this.maxConcurrentConsumers; |
|
|
|
return this.maxConcurrentConsumers; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -446,18 +486,26 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void setMaxMessagesPerTask(int maxMessagesPerTask) { |
|
|
|
public void setMaxMessagesPerTask(int maxMessagesPerTask) { |
|
|
|
Assert.isTrue(maxMessagesPerTask != 0, "'maxMessagesPerTask' must not be 0"); |
|
|
|
Assert.isTrue(maxMessagesPerTask != 0, "'maxMessagesPerTask' must not be 0"); |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
this.maxMessagesPerTask = maxMessagesPerTask; |
|
|
|
this.maxMessagesPerTask = maxMessagesPerTask; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Return the maximum number of messages to process in one task. |
|
|
|
* Return the maximum number of messages to process in one task. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public final int getMaxMessagesPerTask() { |
|
|
|
public final int getMaxMessagesPerTask() { |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
return this.maxMessagesPerTask; |
|
|
|
return this.maxMessagesPerTask; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -472,18 +520,26 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void setIdleConsumerLimit(int idleConsumerLimit) { |
|
|
|
public void setIdleConsumerLimit(int idleConsumerLimit) { |
|
|
|
Assert.isTrue(idleConsumerLimit > 0, "'idleConsumerLimit' must be 1 or higher"); |
|
|
|
Assert.isTrue(idleConsumerLimit > 0, "'idleConsumerLimit' must be 1 or higher"); |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
this.idleConsumerLimit = idleConsumerLimit; |
|
|
|
this.idleConsumerLimit = idleConsumerLimit; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Return the limit for the number of idle consumers. |
|
|
|
* Return the limit for the number of idle consumers. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public final int getIdleConsumerLimit() { |
|
|
|
public final int getIdleConsumerLimit() { |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
return this.idleConsumerLimit; |
|
|
|
return this.idleConsumerLimit; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -515,18 +571,26 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void setIdleTaskExecutionLimit(int idleTaskExecutionLimit) { |
|
|
|
public void setIdleTaskExecutionLimit(int idleTaskExecutionLimit) { |
|
|
|
Assert.isTrue(idleTaskExecutionLimit > 0, "'idleTaskExecutionLimit' must be 1 or higher"); |
|
|
|
Assert.isTrue(idleTaskExecutionLimit > 0, "'idleTaskExecutionLimit' must be 1 or higher"); |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
this.idleTaskExecutionLimit = idleTaskExecutionLimit; |
|
|
|
this.idleTaskExecutionLimit = idleTaskExecutionLimit; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Return the limit for idle executions of a consumer task. |
|
|
|
* Return the limit for idle executions of a consumer task. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public final int getIdleTaskExecutionLimit() { |
|
|
|
public final int getIdleTaskExecutionLimit() { |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
return this.idleTaskExecutionLimit; |
|
|
|
return this.idleTaskExecutionLimit; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -556,9 +620,13 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void setIdleReceivesPerTaskLimit(int idleReceivesPerTaskLimit) { |
|
|
|
public void setIdleReceivesPerTaskLimit(int idleReceivesPerTaskLimit) { |
|
|
|
Assert.isTrue(idleReceivesPerTaskLimit != 0, "'idleReceivesPerTaskLimit' must not be 0)"); |
|
|
|
Assert.isTrue(idleReceivesPerTaskLimit != 0, "'idleReceivesPerTaskLimit' must not be 0)"); |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
this.idleReceivesPerTaskLimit = idleReceivesPerTaskLimit; |
|
|
|
this.idleReceivesPerTaskLimit = idleReceivesPerTaskLimit; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -567,9 +635,13 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
* @since 5.3.5 |
|
|
|
* @since 5.3.5 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public int getIdleReceivesPerTaskLimit() { |
|
|
|
public int getIdleReceivesPerTaskLimit() { |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
return this.idleReceivesPerTaskLimit; |
|
|
|
return this.idleReceivesPerTaskLimit; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -585,7 +657,8 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Prepare taskExecutor and maxMessagesPerTask.
|
|
|
|
// Prepare taskExecutor and maxMessagesPerTask.
|
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
if (this.taskExecutor == null) { |
|
|
|
if (this.taskExecutor == null) { |
|
|
|
this.taskExecutor = createDefaultTaskExecutor(); |
|
|
|
this.taskExecutor = createDefaultTaskExecutor(); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -598,6 +671,9 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
this.maxMessagesPerTask = 10; |
|
|
|
this.maxMessagesPerTask = 10; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Proceed with actual listener initialization.
|
|
|
|
// Proceed with actual listener initialization.
|
|
|
|
super.initialize(); |
|
|
|
super.initialize(); |
|
|
|
@ -612,11 +688,15 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
protected void doInitialize() throws JMSException { |
|
|
|
protected void doInitialize() throws JMSException { |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
for (int i = 0; i < this.concurrentConsumers; i++) { |
|
|
|
for (int i = 0; i < this.concurrentConsumers; i++) { |
|
|
|
scheduleNewInvoker(); |
|
|
|
scheduleNewInvoker(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -625,44 +705,46 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
protected void doShutdown() throws JMSException { |
|
|
|
protected void doShutdown() throws JMSException { |
|
|
|
logger.debug("Waiting for shutdown of message listener invokers"); |
|
|
|
logger.debug("Waiting for shutdown of message listener invokers"); |
|
|
|
|
|
|
|
this.lifecycleLock.lock(); |
|
|
|
try { |
|
|
|
try { |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
long receiveTimeout = getReceiveTimeout(); |
|
|
|
long receiveTimeout = getReceiveTimeout(); |
|
|
|
long waitStartTime = System.currentTimeMillis(); |
|
|
|
long waitStartTime = System.currentTimeMillis(); |
|
|
|
int waitCount = 0; |
|
|
|
int waitCount = 0; |
|
|
|
while (this.activeInvokerCount > 0) { |
|
|
|
while (this.activeInvokerCount > 0) { |
|
|
|
if (waitCount > 0 && !isAcceptMessagesWhileStopping() && |
|
|
|
if (waitCount > 0 && !isAcceptMessagesWhileStopping() && |
|
|
|
System.currentTimeMillis() - waitStartTime >= receiveTimeout) { |
|
|
|
System.currentTimeMillis() - waitStartTime >= receiveTimeout) { |
|
|
|
// Unexpectedly some invokers are still active after the receive timeout period
|
|
|
|
// Unexpectedly some invokers are still active after the receive timeout period
|
|
|
|
// -> interrupt remaining receive attempts since we'd reject the messages anyway
|
|
|
|
// -> interrupt remaining receive attempts since we'd reject the messages anyway
|
|
|
|
for (AsyncMessageListenerInvoker scheduledInvoker : this.scheduledInvokers) { |
|
|
|
for (AsyncMessageListenerInvoker scheduledInvoker : this.scheduledInvokers) { |
|
|
|
scheduledInvoker.interruptIfNecessary(); |
|
|
|
scheduledInvoker.interruptIfNecessary(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
|
|
|
|
logger.debug("Still waiting for shutdown of " + this.activeInvokerCount + |
|
|
|
|
|
|
|
" message listener invokers (iteration " + waitCount + ")"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Wait for AsyncMessageListenerInvokers to deactivate themselves...
|
|
|
|
|
|
|
|
if (receiveTimeout > 0) { |
|
|
|
|
|
|
|
this.lifecycleMonitor.wait(receiveTimeout); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
this.lifecycleMonitor.wait(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
waitCount++; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
// Clear remaining scheduled invokers, possibly left over as paused tasks
|
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
for (AsyncMessageListenerInvoker scheduledInvoker : this.scheduledInvokers) { |
|
|
|
logger.debug("Still waiting for shutdown of " + this.activeInvokerCount + |
|
|
|
scheduledInvoker.clearResources(); |
|
|
|
" message listener invokers (iteration " + waitCount + ")"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Wait for AsyncMessageListenerInvokers to deactivate themselves...
|
|
|
|
|
|
|
|
if (receiveTimeout > 0) { |
|
|
|
|
|
|
|
this.lifecycleCondition.await(receiveTimeout, TimeUnit.MILLISECONDS); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
this.lifecycleCondition.await(); |
|
|
|
} |
|
|
|
} |
|
|
|
this.scheduledInvokers.clear(); |
|
|
|
waitCount++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Clear remaining scheduled invokers, possibly left over as paused tasks
|
|
|
|
|
|
|
|
for (AsyncMessageListenerInvoker scheduledInvoker : this.scheduledInvokers) { |
|
|
|
|
|
|
|
scheduledInvoker.clearResources(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
this.scheduledInvokers.clear(); |
|
|
|
} |
|
|
|
} |
|
|
|
catch (InterruptedException ex) { |
|
|
|
catch (InterruptedException ex) { |
|
|
|
// Re-interrupt current thread, to allow other threads to react.
|
|
|
|
// Re-interrupt current thread, to allow other threads to react.
|
|
|
|
Thread.currentThread().interrupt(); |
|
|
|
Thread.currentThread().interrupt(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -670,9 +752,13 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void start() throws JmsException { |
|
|
|
public void start() throws JmsException { |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
this.stopCallback = null; |
|
|
|
this.stopCallback = null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
super.start(); |
|
|
|
super.start(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -691,7 +777,8 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void stop(Runnable callback) throws JmsException { |
|
|
|
public void stop(Runnable callback) throws JmsException { |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
if (!isRunning() || this.stopCallback != null) { |
|
|
|
if (!isRunning() || this.stopCallback != null) { |
|
|
|
// Not started, already stopped, or previous stop attempt in progress
|
|
|
|
// Not started, already stopped, or previous stop attempt in progress
|
|
|
|
// -> return immediately, no stop process to control anymore.
|
|
|
|
// -> return immediately, no stop process to control anymore.
|
|
|
|
@ -700,6 +787,9 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
} |
|
|
|
} |
|
|
|
this.stopCallback = callback; |
|
|
|
this.stopCallback = callback; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
stop(); |
|
|
|
stop(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -713,9 +803,13 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
* @see #getActiveConsumerCount() |
|
|
|
* @see #getActiveConsumerCount() |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public final int getScheduledConsumerCount() { |
|
|
|
public final int getScheduledConsumerCount() { |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
return this.scheduledInvokers.size(); |
|
|
|
return this.scheduledInvokers.size(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -728,9 +822,13 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
* @see #getActiveConsumerCount() |
|
|
|
* @see #getActiveConsumerCount() |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public final int getActiveConsumerCount() { |
|
|
|
public final int getActiveConsumerCount() { |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
return this.activeInvokerCount; |
|
|
|
return this.activeInvokerCount; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -749,9 +847,13 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
* only {@link #CACHE_CONSUMER} will lead to a fixed registration. |
|
|
|
* only {@link #CACHE_CONSUMER} will lead to a fixed registration. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public boolean isRegisteredWithDestination() { |
|
|
|
public boolean isRegisteredWithDestination() { |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
return (this.registeredWithDestination > 0); |
|
|
|
return (this.registeredWithDestination > 0); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -760,11 +862,15 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
* <p>The default implementation builds a {@link org.springframework.core.task.SimpleAsyncTaskExecutor} |
|
|
|
* <p>The default implementation builds a {@link org.springframework.core.task.SimpleAsyncTaskExecutor} |
|
|
|
* with the specified bean name (or the class name, if no bean name specified) as thread name prefix. |
|
|
|
* with the specified bean name (or the class name, if no bean name specified) as thread name prefix. |
|
|
|
* @see org.springframework.core.task.SimpleAsyncTaskExecutor#SimpleAsyncTaskExecutor(String) |
|
|
|
* @see org.springframework.core.task.SimpleAsyncTaskExecutor#SimpleAsyncTaskExecutor(String) |
|
|
|
|
|
|
|
* @see #setVirtualThreads |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
protected TaskExecutor createDefaultTaskExecutor() { |
|
|
|
protected TaskExecutor createDefaultTaskExecutor() { |
|
|
|
String beanName = getBeanName(); |
|
|
|
String beanName = getBeanName(); |
|
|
|
String threadNamePrefix = (beanName != null ? beanName + "-" : DEFAULT_THREAD_NAME_PREFIX); |
|
|
|
String threadNamePrefix = (beanName != null ? beanName + "-" : DEFAULT_THREAD_NAME_PREFIX); |
|
|
|
return new SimpleAsyncTaskExecutor(threadNamePrefix); |
|
|
|
|
|
|
|
|
|
|
|
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(threadNamePrefix); |
|
|
|
|
|
|
|
executor.setVirtualThreads(this.virtualThreads); |
|
|
|
|
|
|
|
return executor; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -831,7 +937,8 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
protected void scheduleNewInvokerIfAppropriate() { |
|
|
|
protected void scheduleNewInvokerIfAppropriate() { |
|
|
|
if (isRunning()) { |
|
|
|
if (isRunning()) { |
|
|
|
resumePausedTasks(); |
|
|
|
resumePausedTasks(); |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
if (this.scheduledInvokers.size() < this.maxConcurrentConsumers && |
|
|
|
if (this.scheduledInvokers.size() < this.maxConcurrentConsumers && |
|
|
|
getIdleInvokerCount() < this.idleConsumerLimit) { |
|
|
|
getIdleInvokerCount() < this.idleConsumerLimit) { |
|
|
|
scheduleNewInvoker(); |
|
|
|
scheduleNewInvoker(); |
|
|
|
@ -840,6 +947,9 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -1072,10 +1182,9 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
else { |
|
|
|
|
|
|
|
this.lifecycleLock.lock(); |
|
|
|
try { |
|
|
|
try { |
|
|
|
synchronized (this.lifecycleMonitor) { |
|
|
|
this.lifecycleCondition.await(interval, TimeUnit.MILLISECONDS); |
|
|
|
this.lifecycleMonitor.wait(interval); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
catch (InterruptedException interEx) { |
|
|
|
catch (InterruptedException interEx) { |
|
|
|
// Re-interrupt current thread, to allow other threads to react.
|
|
|
|
// Re-interrupt current thread, to allow other threads to react.
|
|
|
|
@ -1084,6 +1193,9 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
this.interrupted = true; |
|
|
|
this.interrupted = true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
this.lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -1129,9 +1241,13 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void run() { |
|
|
|
public void run() { |
|
|
|
synchronized (lifecycleMonitor) { |
|
|
|
lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
activeInvokerCount++; |
|
|
|
activeInvokerCount++; |
|
|
|
lifecycleMonitor.notifyAll(); |
|
|
|
lifecycleCondition.signalAll(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
lifecycleLock.unlock(); |
|
|
|
} |
|
|
|
} |
|
|
|
boolean messageReceived = false; |
|
|
|
boolean messageReceived = false; |
|
|
|
try { |
|
|
|
try { |
|
|
|
@ -1161,7 +1277,8 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
} |
|
|
|
} |
|
|
|
this.lastMessageSucceeded = false; |
|
|
|
this.lastMessageSucceeded = false; |
|
|
|
boolean alreadyRecovered = false; |
|
|
|
boolean alreadyRecovered = false; |
|
|
|
synchronized (recoveryMonitor) { |
|
|
|
recoveryLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
if (this.lastRecoveryMarker == currentRecoveryMarker) { |
|
|
|
if (this.lastRecoveryMarker == currentRecoveryMarker) { |
|
|
|
handleListenerSetupFailure(ex, false); |
|
|
|
handleListenerSetupFailure(ex, false); |
|
|
|
recoverAfterListenerSetupFailure(); |
|
|
|
recoverAfterListenerSetupFailure(); |
|
|
|
@ -1171,14 +1288,21 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
alreadyRecovered = true; |
|
|
|
alreadyRecovered = true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
recoveryLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
if (alreadyRecovered) { |
|
|
|
if (alreadyRecovered) { |
|
|
|
handleListenerSetupFailure(ex, true); |
|
|
|
handleListenerSetupFailure(ex, true); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
finally { |
|
|
|
finally { |
|
|
|
synchronized (lifecycleMonitor) { |
|
|
|
lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
decreaseActiveInvokerCount(); |
|
|
|
decreaseActiveInvokerCount(); |
|
|
|
lifecycleMonitor.notifyAll(); |
|
|
|
lifecycleCondition.signalAll(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
lifecycleLock.unlock(); |
|
|
|
} |
|
|
|
} |
|
|
|
if (!messageReceived) { |
|
|
|
if (!messageReceived) { |
|
|
|
this.idleTaskExecutionCount++; |
|
|
|
this.idleTaskExecutionCount++; |
|
|
|
@ -1186,14 +1310,15 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
else { |
|
|
|
else { |
|
|
|
this.idleTaskExecutionCount = 0; |
|
|
|
this.idleTaskExecutionCount = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
synchronized (lifecycleMonitor) { |
|
|
|
lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
if (!shouldRescheduleInvoker(this.idleTaskExecutionCount) || !rescheduleTaskIfNecessary(this)) { |
|
|
|
if (!shouldRescheduleInvoker(this.idleTaskExecutionCount) || !rescheduleTaskIfNecessary(this)) { |
|
|
|
// We're shutting down completely.
|
|
|
|
// We're shutting down completely.
|
|
|
|
scheduledInvokers.remove(this); |
|
|
|
scheduledInvokers.remove(this); |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
logger.debug("Lowered scheduled invoker count: " + scheduledInvokers.size()); |
|
|
|
logger.debug("Lowered scheduled invoker count: " + scheduledInvokers.size()); |
|
|
|
} |
|
|
|
} |
|
|
|
lifecycleMonitor.notifyAll(); |
|
|
|
lifecycleCondition.signalAll(); |
|
|
|
clearResources(); |
|
|
|
clearResources(); |
|
|
|
} |
|
|
|
} |
|
|
|
else if (isRunning()) { |
|
|
|
else if (isRunning()) { |
|
|
|
@ -1209,6 +1334,9 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -1216,7 +1344,8 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
boolean messageReceived = false; |
|
|
|
boolean messageReceived = false; |
|
|
|
boolean active = true; |
|
|
|
boolean active = true; |
|
|
|
while (active) { |
|
|
|
while (active) { |
|
|
|
synchronized (lifecycleMonitor) { |
|
|
|
lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
boolean interrupted = false; |
|
|
|
boolean interrupted = false; |
|
|
|
boolean wasWaiting = false; |
|
|
|
boolean wasWaiting = false; |
|
|
|
while ((active = isActive()) && !isRunning()) { |
|
|
|
while ((active = isActive()) && !isRunning()) { |
|
|
|
@ -1229,7 +1358,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
} |
|
|
|
} |
|
|
|
wasWaiting = true; |
|
|
|
wasWaiting = true; |
|
|
|
try { |
|
|
|
try { |
|
|
|
lifecycleMonitor.wait(); |
|
|
|
lifecycleCondition.await(); |
|
|
|
} |
|
|
|
} |
|
|
|
catch (InterruptedException ex) { |
|
|
|
catch (InterruptedException ex) { |
|
|
|
// Re-interrupt current thread, to allow other threads to react.
|
|
|
|
// Re-interrupt current thread, to allow other threads to react.
|
|
|
|
@ -1244,6 +1373,9 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
active = false; |
|
|
|
active = false; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
if (active) { |
|
|
|
if (active) { |
|
|
|
messageReceived = (invokeListener() || messageReceived); |
|
|
|
messageReceived = (invokeListener() || messageReceived); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -1289,17 +1421,25 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
} |
|
|
|
} |
|
|
|
if (this.consumer == null && getCacheLevel() >= CACHE_CONSUMER) { |
|
|
|
if (this.consumer == null && getCacheLevel() >= CACHE_CONSUMER) { |
|
|
|
this.consumer = createListenerConsumer(this.session); |
|
|
|
this.consumer = createListenerConsumer(this.session); |
|
|
|
synchronized (lifecycleMonitor) { |
|
|
|
lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
registeredWithDestination++; |
|
|
|
registeredWithDestination++; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void updateRecoveryMarker() { |
|
|
|
private void updateRecoveryMarker() { |
|
|
|
synchronized (recoveryMonitor) { |
|
|
|
recoveryLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
this.lastRecoveryMarker = currentRecoveryMarker; |
|
|
|
this.lastRecoveryMarker = currentRecoveryMarker; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
recoveryLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void interruptIfNecessary() { |
|
|
|
private void interruptIfNecessary() { |
|
|
|
@ -1311,19 +1451,27 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe |
|
|
|
|
|
|
|
|
|
|
|
private void clearResources() { |
|
|
|
private void clearResources() { |
|
|
|
if (sharedConnectionEnabled()) { |
|
|
|
if (sharedConnectionEnabled()) { |
|
|
|
synchronized (sharedConnectionMonitor) { |
|
|
|
sharedConnectionLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
JmsUtils.closeMessageConsumer(this.consumer); |
|
|
|
JmsUtils.closeMessageConsumer(this.consumer); |
|
|
|
JmsUtils.closeSession(this.session); |
|
|
|
JmsUtils.closeSession(this.session); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
sharedConnectionLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
else { |
|
|
|
JmsUtils.closeMessageConsumer(this.consumer); |
|
|
|
JmsUtils.closeMessageConsumer(this.consumer); |
|
|
|
JmsUtils.closeSession(this.session); |
|
|
|
JmsUtils.closeSession(this.session); |
|
|
|
} |
|
|
|
} |
|
|
|
if (this.consumer != null) { |
|
|
|
if (this.consumer != null) { |
|
|
|
synchronized (lifecycleMonitor) { |
|
|
|
lifecycleLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
registeredWithDestination--; |
|
|
|
registeredWithDestination--; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
lifecycleLock.unlock(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
this.consumer = null; |
|
|
|
this.consumer = null; |
|
|
|
this.session = null; |
|
|
|
this.session = null; |
|
|
|
|