Skip to content

Commit d201a22

Browse files
committed
Avoid locking in WebSocket session "close" callback
When processing a "close" notification from the server make an effort to cancel any outstanding heartbeat but avoid going as far as acquiring the responseLock since the server itself may already hold a lock of its own leading to a potential deadlock. The heartbeat task is now also further protected with an isClosed() check in case the heartbeat does not get cancelled in a concurrent scenario. Issue: SPR-14917
1 parent 3eb2432 commit d201a22

File tree

2 files changed

+18
-5
lines changed

2 files changed

+18
-5
lines changed

spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,12 @@ public final void delegateConnectionClosed(CloseStatus status) throws Exception
396396
if (!isClosed()) {
397397
try {
398398
updateLastActiveTime();
399-
cancelHeartbeat();
399+
// Avoid cancelHeartbeat() and responseLock within server "close" callback
400+
ScheduledFuture<?> future = this.heartbeatFuture;
401+
if (future != null) {
402+
this.heartbeatFuture = null;
403+
future.cancel(false);
404+
}
400405
}
401406
finally {
402407
this.state = State.CLOSED;
@@ -446,7 +451,7 @@ private class HeartbeatTask implements Runnable {
446451
@Override
447452
public void run() {
448453
synchronized (responseLock) {
449-
if (!this.expired) {
454+
if (!this.expired && !isClosed()) {
450455
try {
451456
sendHeartbeat();
452457
}

spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/SockJsSessionTests.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,17 @@
3131
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
3232
import org.springframework.web.socket.sockjs.frame.SockJsFrame;
3333

34-
import static org.junit.Assert.*;
35-
import static org.mockito.BDDMockito.*;
34+
import static org.junit.Assert.assertEquals;
35+
import static org.junit.Assert.assertNull;
36+
import static org.junit.Assert.assertTrue;
37+
import static org.junit.Assert.fail;
38+
import static org.mockito.BDDMockito.any;
39+
import static org.mockito.BDDMockito.given;
40+
import static org.mockito.BDDMockito.mock;
41+
import static org.mockito.BDDMockito.verify;
42+
import static org.mockito.BDDMockito.verifyNoMoreInteractions;
43+
import static org.mockito.BDDMockito.willReturn;
44+
import static org.mockito.BDDMockito.willThrow;
3645

3746
/**
3847
* Test fixture for {@link AbstractSockJsSession}.
@@ -130,7 +139,6 @@ public void delegateConnectionClosed() throws Exception {
130139

131140
assertClosed();
132141
assertEquals(1, this.session.getNumberOfLastActiveTimeUpdates());
133-
assertTrue(this.session.didCancelHeartbeat());
134142
verify(this.webSocketHandler).afterConnectionClosed(this.session, CloseStatus.GOING_AWAY);
135143
}
136144

0 commit comments

Comments
 (0)