Avoid deadlock between SockJS heartbeat and XHR polling

Issue: SPR-14833
This commit is contained in:
Juergen Hoeller 2016-10-28 23:34:19 +02:00
parent bf9083d60f
commit 72e1f7e898
2 changed files with 8 additions and 23 deletions

View File

@ -51,6 +51,7 @@ import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig;
*/ */
public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession { public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
private final Queue<String> messageCache;
private volatile URI uri; private volatile URI uri;
@ -64,20 +65,13 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
private volatile String acceptedProtocol; private volatile String acceptedProtocol;
private volatile ServerHttpResponse response; private volatile ServerHttpResponse response;
private volatile SockJsFrameFormat frameFormat; private volatile SockJsFrameFormat frameFormat;
private volatile ServerHttpAsyncRequestControl asyncRequestControl; private volatile ServerHttpAsyncRequestControl asyncRequestControl;
private final Object responseLock = new Object(); private boolean readyToSend;
private volatile boolean readyToSend;
private final Queue<String> messageCache;
public AbstractHttpSockJsSession(String id, SockJsServiceConfig config, public AbstractHttpSockJsSession(String id, SockJsServiceConfig config,
@ -209,14 +203,10 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
this.frameFormat = frameFormat; this.frameFormat = frameFormat;
this.asyncRequestControl = request.getAsyncRequestControl(response); this.asyncRequestControl = request.getAsyncRequestControl(response);
this.asyncRequestControl.start(-1); this.asyncRequestControl.start(-1);
disableShallowEtagHeaderFilter(request); disableShallowEtagHeaderFilter(request);
// Let "our" handler know before sending the open frame to the remote handler // Let "our" handler know before sending the open frame to the remote handler
delegateConnectionEstablished(); delegateConnectionEstablished();
handleRequestInternal(request, response, true); handleRequestInternal(request, response, true);
// Request might have been reset (e.g. polling sessions do after writing) // Request might have been reset (e.g. polling sessions do after writing)
this.readyToSend = isActive(); this.readyToSend = isActive();
} }
@ -252,9 +242,7 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
this.frameFormat = frameFormat; this.frameFormat = frameFormat;
this.asyncRequestControl = request.getAsyncRequestControl(response); this.asyncRequestControl = request.getAsyncRequestControl(response);
this.asyncRequestControl.start(-1); this.asyncRequestControl.start(-1);
disableShallowEtagHeaderFilter(request); disableShallowEtagHeaderFilter(request);
handleRequestInternal(request, response, false); handleRequestInternal(request, response, false);
this.readyToSend = isActive(); this.readyToSend = isActive();
} }
@ -318,14 +306,11 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
protected void resetRequest() { protected void resetRequest() {
synchronized (this.responseLock) { synchronized (this.responseLock) {
ServerHttpAsyncRequestControl control = this.asyncRequestControl; ServerHttpAsyncRequestControl control = this.asyncRequestControl;
this.asyncRequestControl = null; this.asyncRequestControl = null;
this.readyToSend = false; this.readyToSend = false;
this.response = null; this.response = null;
updateLastActiveTime(); updateLastActiveTime();
if (control != null && !control.isCompleted()) { if (control != null && !control.isCompleted()) {
if (control.isStarted()) { if (control.isStarted()) {
try { try {

View File

@ -91,6 +91,8 @@ public abstract class AbstractSockJsSession implements SockJsSession {
protected final Log logger = LogFactory.getLog(getClass()); protected final Log logger = LogFactory.getLog(getClass());
protected final Object responseLock = new Object();
private final String id; private final String id;
private final SockJsServiceConfig config; private final SockJsServiceConfig config;
@ -109,8 +111,6 @@ public abstract class AbstractSockJsSession implements SockJsSession {
private HeartbeatTask heartbeatTask; private HeartbeatTask heartbeatTask;
private final Object heartbeatLock = new Object();
private volatile boolean heartbeatDisabled; private volatile boolean heartbeatDisabled;
@ -250,7 +250,7 @@ public abstract class AbstractSockJsSession implements SockJsSession {
} }
protected void sendHeartbeat() throws SockJsTransportFailureException { protected void sendHeartbeat() throws SockJsTransportFailureException {
synchronized (this.heartbeatLock) { synchronized (this.responseLock) {
if (isActive() && !this.heartbeatDisabled) { if (isActive() && !this.heartbeatDisabled) {
writeFrame(SockJsFrame.heartbeatFrame()); writeFrame(SockJsFrame.heartbeatFrame());
scheduleHeartbeat(); scheduleHeartbeat();
@ -262,7 +262,7 @@ public abstract class AbstractSockJsSession implements SockJsSession {
if (this.heartbeatDisabled) { if (this.heartbeatDisabled) {
return; return;
} }
synchronized (this.heartbeatLock) { synchronized (this.responseLock) {
cancelHeartbeat(); cancelHeartbeat();
if (!isActive()) { if (!isActive()) {
return; return;
@ -277,7 +277,7 @@ public abstract class AbstractSockJsSession implements SockJsSession {
} }
protected void cancelHeartbeat() { protected void cancelHeartbeat() {
synchronized (this.heartbeatLock) { synchronized (this.responseLock) {
if (this.heartbeatFuture != null) { if (this.heartbeatFuture != null) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Cancelling heartbeat in session " + getId()); logger.trace("Cancelling heartbeat in session " + getId());
@ -445,7 +445,7 @@ public abstract class AbstractSockJsSession implements SockJsSession {
@Override @Override
public void run() { public void run() {
synchronized (heartbeatLock) { synchronized (responseLock) {
if (!this.expired) { if (!this.expired) {
try { try {
sendHeartbeat(); sendHeartbeat();