Avoid deadlock between SockJS heartbeat and XHR polling

Issue: SPR-14833
(cherry picked from commit 1c80d2a)
This commit is contained in:
Juergen Hoeller
2016-10-28 23:34:19 +02:00
parent 0d6dc5760c
commit 4ce0e6b3c3
2 changed files with 10 additions and 24 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,6 +51,7 @@ import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig;
*/
public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
private final Queue<String> messageCache;
private volatile URI uri;
@@ -64,20 +65,13 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
private volatile String acceptedProtocol;
private volatile ServerHttpResponse response;
private volatile SockJsFrameFormat frameFormat;
private volatile ServerHttpAsyncRequestControl asyncRequestControl;
private final Object responseLock = new Object();
private volatile boolean readyToSend;
private final Queue<String> messageCache;
private boolean readyToSend;
public AbstractHttpSockJsSession(String id, SockJsServiceConfig config,
@@ -205,14 +199,10 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
this.frameFormat = frameFormat;
this.asyncRequestControl = request.getAsyncRequestControl(response);
this.asyncRequestControl.start(-1);
disableShallowEtagHeaderFilter(request);
// Let "our" handler know before sending the open frame to the remote handler
delegateConnectionEstablished();
handleRequestInternal(request, response, true);
// Request might have been reset (e.g. polling sessions do after writing)
this.readyToSend = isActive();
}
@@ -248,9 +238,7 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
this.frameFormat = frameFormat;
this.asyncRequestControl = request.getAsyncRequestControl(response);
this.asyncRequestControl.start(-1);
disableShallowEtagHeaderFilter(request);
handleRequestInternal(request, response, false);
this.readyToSend = isActive();
}
@@ -322,14 +310,11 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
protected void resetRequest() {
synchronized (this.responseLock) {
ServerHttpAsyncRequestControl control = this.asyncRequestControl;
this.asyncRequestControl = null;
this.readyToSend = false;
this.response = null;
updateLastActiveTime();
if (control != null && !control.isCompleted()) {
if (control.isStarted()) {
try {

View File

@@ -30,6 +30,7 @@ import java.util.concurrent.ScheduledFuture;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.NestedCheckedException;
import org.springframework.util.Assert;
import org.springframework.web.socket.CloseStatus;
@@ -90,6 +91,8 @@ public abstract class AbstractSockJsSession implements SockJsSession {
protected final Log logger = LogFactory.getLog(getClass());
protected final Object responseLock = new Object();
private final String id;
private final SockJsServiceConfig config;
@@ -108,8 +111,6 @@ public abstract class AbstractSockJsSession implements SockJsSession {
private HeartbeatTask heartbeatTask;
private final Object heartbeatLock = new Object();
private volatile boolean heartbeatDisabled;
@@ -249,7 +250,7 @@ public abstract class AbstractSockJsSession implements SockJsSession {
}
public void sendHeartbeat() throws SockJsTransportFailureException {
synchronized (this.heartbeatLock) {
synchronized (this.responseLock) {
if (isActive() && !this.heartbeatDisabled) {
writeFrame(SockJsFrame.heartbeatFrame());
scheduleHeartbeat();
@@ -261,7 +262,7 @@ public abstract class AbstractSockJsSession implements SockJsSession {
if (this.heartbeatDisabled) {
return;
}
synchronized (this.heartbeatLock) {
synchronized (this.responseLock) {
cancelHeartbeat();
if (!isActive()) {
return;
@@ -276,7 +277,7 @@ public abstract class AbstractSockJsSession implements SockJsSession {
}
protected void cancelHeartbeat() {
synchronized (this.heartbeatLock) {
synchronized (this.responseLock) {
if (this.heartbeatFuture != null) {
if (logger.isTraceEnabled()) {
logger.trace("Cancelling heartbeat in session " + getId());
@@ -444,7 +445,7 @@ public abstract class AbstractSockJsSession implements SockJsSession {
@Override
public void run() {
synchronized (heartbeatLock) {
synchronized (responseLock) {
if (!this.expired) {
try {
sendHeartbeat();