Avoid deadlock between SockJS heartbeat and XHR polling
Issue: SPR-14833
This commit is contained in:
parent
bf9083d60f
commit
72e1f7e898
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue