From 8f19650fd7bb1c35a5cef320943a9f92d23082f1 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 23 Nov 2016 20:49:24 -0500 Subject: [PATCH] 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 --- .../transport/session/AbstractSockJsSession.java | 9 +++++++-- .../transport/session/SockJsSessionTests.java | 14 +++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java index 7211c97049a..515f55333f4 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java @@ -396,7 +396,12 @@ public abstract class AbstractSockJsSession implements SockJsSession { if (!isClosed()) { try { updateLastActiveTime(); - cancelHeartbeat(); + // Avoid cancelHeartbeat() and responseLock within server "close" callback + ScheduledFuture future = this.heartbeatFuture; + if (future != null) { + this.heartbeatFuture = null; + future.cancel(false); + } } finally { this.state = State.CLOSED; @@ -446,7 +451,7 @@ public abstract class AbstractSockJsSession implements SockJsSession { @Override public void run() { synchronized (responseLock) { - if (!this.expired) { + if (!this.expired && !isClosed()) { try { sendHeartbeat(); } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/SockJsSessionTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/SockJsSessionTests.java index 46433ce7715..1d5e3f03520 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/SockJsSessionTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/SockJsSessionTests.java @@ -31,8 +31,17 @@ import org.springframework.web.socket.sockjs.SockJsMessageDeliveryException; import org.springframework.web.socket.sockjs.SockJsTransportFailureException; import org.springframework.web.socket.sockjs.frame.SockJsFrame; -import static org.junit.Assert.*; -import static org.mockito.BDDMockito.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.BDDMockito.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.mock; +import static org.mockito.BDDMockito.verify; +import static org.mockito.BDDMockito.verifyNoMoreInteractions; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.BDDMockito.willThrow; /** * Test fixture for {@link AbstractSockJsSession}. @@ -130,7 +139,6 @@ public class SockJsSessionTests extends AbstractSockJsSessionTests