Skip to content

Commit 82557f9

Browse files
committed
QPIDJMS-602: fix potential for session shutdown NPE during competing local and remote closures
Make the ProviderFuture creation safe from NPE, validate a future is returned and noop the completion wait if not since the provider ref is gone already. Add additional try-finally to ensure executor shutdown occurs.
1 parent 06e587b commit 82557f9

File tree

2 files changed

+39
-28
lines changed

2 files changed

+39
-28
lines changed

qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsConnection.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,12 @@ ProviderFuture newProviderFuture() {
921921
}
922922

923923
ProviderFuture newProviderFuture(ProviderSynchronization synchronization) {
924-
return provider.newProviderFuture(synchronization);
924+
Provider localProvider = provider;
925+
if (localProvider != null) {
926+
return localProvider.newProviderFuture(synchronization);
927+
} else {
928+
return null;
929+
}
925930
}
926931

927932
//----- Property setters and getters -------------------------------------//

qpid-jms-client/src/main/java/org/apache/qpid/jms/JmsSession.java

+33-27
Original file line numberDiff line numberDiff line change
@@ -380,39 +380,45 @@ protected boolean shutdown(Throwable cause) throws JMSException {
380380
// Ensure that no asynchronous completion sends remain blocked after close but wait
381381
// using the close timeout for the asynchronous sends to complete normally.
382382
final ExecutorService completionExecutor = getCompletionExecutor();
383+
try {
384+
synchronized (sessionInfo) {
385+
// Producers are now quiesced and we can await completion of asynchronous sends
386+
// that are still pending a result or timeout once we've done a quick check to
387+
// see if any are actually pending or have completed already.
388+
asyncSendsCompletion = connection.newProviderFuture();
389+
390+
if (asyncSendsCompletion != null) {
391+
completionExecutor.execute(() -> {
392+
if (asyncSendQueue.isEmpty()) {
393+
asyncSendsCompletion.onSuccess();
394+
}
395+
});
396+
}
397+
}
383398

384-
synchronized (sessionInfo) {
385-
// Producers are now quiesced and we can await completion of asynchronous sends
386-
// that are still pending a result or timeout once we've done a quick check to
387-
// see if any are actually pending or have completed already.
388-
asyncSendsCompletion = connection.newProviderFuture();
389-
390-
completionExecutor.execute(() -> {
391-
if (asyncSendQueue.isEmpty()) {
392-
asyncSendsCompletion.onSuccess();
399+
try {
400+
if (asyncSendsCompletion != null) {
401+
asyncSendsCompletion.sync(connection.getCloseTimeout(), TimeUnit.MILLISECONDS);
402+
}
403+
} catch (Exception ex) {
404+
LOG.trace("Exception during wait for asynchronous sends to complete", ex);
405+
} finally {
406+
if (cause == null) {
407+
cause = new JMSException("Session closed remotely before message transfer result was notified");
393408
}
394-
});
395-
}
396409

397-
try {
398-
asyncSendsCompletion.sync(connection.getCloseTimeout(), TimeUnit.MILLISECONDS);
399-
} catch (Exception ex) {
400-
LOG.trace("Exception during wait for asynchronous sends to complete", ex);
401-
} finally {
402-
if (cause == null) {
403-
cause = new JMSException("Session closed remotely before message transfer result was notified");
410+
// as a last task we want to fail any stragglers in the asynchronous send queue and then
411+
// shutdown the queue to prevent any more submissions while the cleanup goes on.
412+
completionExecutor.execute(new FailOrCompleteAsyncCompletionsTask(JmsExceptionSupport.create(cause)));
404413
}
405-
406-
// as a last task we want to fail any stragglers in the asynchronous send queue and then
407-
// shutdown the queue to prevent any more submissions while the cleanup goes on.
408-
completionExecutor.execute(new FailOrCompleteAsyncCompletionsTask(JmsExceptionSupport.create(cause)));
414+
} finally {
409415
completionExecutor.shutdown();
410-
}
411416

412-
try {
413-
completionExecutor.awaitTermination(connection.getCloseTimeout(), TimeUnit.MILLISECONDS);
414-
} catch (InterruptedException e) {
415-
LOG.trace("Session close awaiting send completions was interrupted");
417+
try {
418+
completionExecutor.awaitTermination(connection.getCloseTimeout(), TimeUnit.MILLISECONDS);
419+
} catch (InterruptedException e) {
420+
LOG.trace("Session close awaiting send completions was interrupted");
421+
}
416422
}
417423

418424
if (shutdownError != null) {

0 commit comments

Comments
 (0)