Skip to content

Commit ba885f3

Browse files
committed
Add heartbeat lock to SockJS server sessions
Even before this change SockJS sessions always cancelled the heartbeat task first prior to sending messages. However when the heartbeat task is already in progress, cancellation of it is not enough and we must wait until the heartbeat is sent. This commit adds a heartbeat write lock which is obtained and held during the sending of a heartbeat. Now when sessions send a message they still cancel the heartbeat task but if that fails they also wait for the heartbeat write lock. Issue: SPR-14356
1 parent 919f6c9 commit ba885f3

File tree

2 files changed

+36
-11
lines changed

2 files changed

+36
-11
lines changed

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

+32-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,8 @@
2727
import java.util.Set;
2828
import java.util.concurrent.ConcurrentHashMap;
2929
import java.util.concurrent.ScheduledFuture;
30+
import java.util.concurrent.locks.Lock;
31+
import java.util.concurrent.locks.ReentrantLock;
3032

3133
import org.apache.commons.logging.Log;
3234
import org.apache.commons.logging.LogFactory;
@@ -106,6 +108,8 @@ private enum State {NEW, OPEN, CLOSED}
106108

107109
private volatile ScheduledFuture<?> heartbeatTask;
108110

111+
private final Lock heartbeatLock = new ReentrantLock();
112+
109113
private volatile boolean heartbeatDisabled;
110114

111115

@@ -246,8 +250,15 @@ public void disableHeartbeat() {
246250

247251
public void sendHeartbeat() throws SockJsTransportFailureException {
248252
if (isActive()) {
249-
writeFrame(SockJsFrame.heartbeatFrame());
250-
scheduleHeartbeat();
253+
if (heartbeatLock.tryLock()) {
254+
try {
255+
writeFrame(SockJsFrame.heartbeatFrame());
256+
scheduleHeartbeat();
257+
}
258+
finally {
259+
heartbeatLock.unlock();
260+
}
261+
}
251262
}
252263
}
253264

@@ -282,13 +293,26 @@ protected void cancelHeartbeat() {
282293
try {
283294
ScheduledFuture<?> task = this.heartbeatTask;
284295
this.heartbeatTask = null;
296+
if (task == null || task.isCancelled()) {
297+
return;
298+
}
285299

286-
if ((task != null) && !task.isDone()) {
287-
if (logger.isTraceEnabled()) {
288-
logger.trace("Cancelling heartbeat in session " + getId());
289-
}
290-
task.cancel(false);
300+
if (logger.isTraceEnabled()) {
301+
logger.trace("Cancelling heartbeat in session " + getId());
302+
}
303+
if (task.cancel(false)) {
304+
return;
305+
}
306+
307+
if (logger.isTraceEnabled()) {
308+
logger.trace("Failed to cancel heartbeat, acquiring heartbeat write lock.");
309+
}
310+
this.heartbeatLock.lock();
311+
312+
if (logger.isTraceEnabled()) {
313+
logger.trace("Releasing heartbeat lock.");
291314
}
315+
this.heartbeatLock.unlock();
292316
}
293317
catch (Throwable ex) {
294318
logger.debug("Failure while cancelling heartbeat in session " + getId(), ex);

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -287,11 +287,12 @@ public void scheduleAndCancelHeartbeat() throws Exception {
287287
verify(this.taskScheduler).schedule(any(Runnable.class), any(Date.class));
288288
verifyNoMoreInteractions(this.taskScheduler);
289289

290-
given(task.isDone()).willReturn(false);
290+
given(task.isCancelled()).willReturn(false);
291+
given(task.cancel(false)).willReturn(true);
291292

292293
this.session.cancelHeartbeat();
293294

294-
verify(task).isDone();
295+
verify(task).isCancelled();
295296
verify(task).cancel(false);
296297
verifyNoMoreInteractions(task);
297298
}

0 commit comments

Comments
 (0)