Skip to content

Commit 2dd22f6

Browse files
committed
Improve cancellation of read/write inactivity
The cancellation of read and write inactivity tasks was done via WebSocketHandler#afterConnectionClosed, relying on the WebSocket library to always invoke the callback. This change moves the cancellation to the `close` method instead that in turn is called from DefaultStompSession#resetConnection, in effect making the cancellation more proactive and aligned with connection cleanup in DefaultStompSession vs relying on a subsequent call from the WebSocket library after the connection is closed. Closes gh-32195
1 parent 6be0432 commit 2dd22f6

File tree

2 files changed

+38
-22
lines changed

2 files changed

+38
-22
lines changed

spring-websocket/src/main/java/org/springframework/web/socket/messaging/WebSocketStompClient.java

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.net.URI;
2121
import java.nio.ByteBuffer;
2222
import java.time.Duration;
23-
import java.util.ArrayList;
2423
import java.util.Collections;
2524
import java.util.List;
2625
import java.util.concurrent.CompletableFuture;
@@ -383,7 +382,11 @@ private class WebSocketTcpConnectionHandlerAdapter implements BiConsumer<WebSock
383382

384383
private volatile long lastWriteTime = -1;
385384

386-
private final List<ScheduledFuture<?>> inactivityTasks = new ArrayList<>(2);
385+
@Nullable
386+
private ScheduledFuture<?> readInactivityFuture;
387+
388+
@Nullable
389+
private ScheduledFuture<?> writeInactivityFuture;
387390

388391
public WebSocketTcpConnectionHandlerAdapter(TcpConnectionHandler<byte[]> stompSession) {
389392
Assert.notNull(stompSession, "TcpConnectionHandler must not be null");
@@ -430,24 +433,9 @@ public void handleTransportError(WebSocketSession session, Throwable ex) {
430433

431434
@Override
432435
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {
433-
cancelInactivityTasks();
434436
this.stompSession.afterConnectionClosed();
435437
}
436438

437-
private void cancelInactivityTasks() {
438-
for (ScheduledFuture<?> task : this.inactivityTasks) {
439-
try {
440-
task.cancel(true);
441-
}
442-
catch (Throwable ex) {
443-
// Ignore
444-
}
445-
}
446-
this.lastReadTime = -1;
447-
this.lastWriteTime = -1;
448-
this.inactivityTasks.clear();
449-
}
450-
451439
@Override
452440
public boolean supportsPartialMessages() {
453441
return false;
@@ -486,7 +474,7 @@ public void onReadInactivity(final Runnable runnable, final long duration) {
486474
Assert.state(getTaskScheduler() != null, "No TaskScheduler configured");
487475
this.lastReadTime = System.currentTimeMillis();
488476
Duration delay = Duration.ofMillis(duration / 2);
489-
this.inactivityTasks.add(getTaskScheduler().scheduleWithFixedDelay(() -> {
477+
this.readInactivityFuture = getTaskScheduler().scheduleWithFixedDelay(() -> {
490478
if (System.currentTimeMillis() - this.lastReadTime > duration) {
491479
try {
492480
runnable.run();
@@ -497,15 +485,15 @@ public void onReadInactivity(final Runnable runnable, final long duration) {
497485
}
498486
}
499487
}
500-
}, delay));
488+
}, delay);
501489
}
502490

503491
@Override
504492
public void onWriteInactivity(final Runnable runnable, final long duration) {
505493
Assert.state(getTaskScheduler() != null, "No TaskScheduler configured");
506494
this.lastWriteTime = System.currentTimeMillis();
507495
Duration delay = Duration.ofMillis(duration / 2);
508-
this.inactivityTasks.add(getTaskScheduler().scheduleWithFixedDelay(() -> {
496+
this.writeInactivityFuture = getTaskScheduler().scheduleWithFixedDelay(() -> {
509497
if (System.currentTimeMillis() - this.lastWriteTime > duration) {
510498
try {
511499
runnable.run();
@@ -516,11 +504,12 @@ public void onWriteInactivity(final Runnable runnable, final long duration) {
516504
}
517505
}
518506
}
519-
}, delay));
507+
}, delay);
520508
}
521509

522510
@Override
523511
public void close() {
512+
cancelInactivityTasks();
524513
WebSocketSession session = this.session;
525514
if (session != null) {
526515
try {
@@ -533,6 +522,31 @@ public void close() {
533522
}
534523
}
535524
}
525+
526+
private void cancelInactivityTasks() {
527+
ScheduledFuture<?> readFuture = this.readInactivityFuture;
528+
this.readInactivityFuture = null;
529+
cancelFuture(readFuture);
530+
531+
ScheduledFuture<?> writeFuture = this.writeInactivityFuture;
532+
this.writeInactivityFuture = null;
533+
cancelFuture(writeFuture);
534+
535+
this.lastReadTime = -1;
536+
this.lastWriteTime = -1;
537+
}
538+
539+
private static void cancelFuture(@Nullable ScheduledFuture<?> future) {
540+
if (future != null) {
541+
try {
542+
future.cancel(true);
543+
}
544+
catch (Throwable ex) {
545+
// Ignore
546+
}
547+
}
548+
}
549+
536550
}
537551

538552

spring-websocket/src/test/java/org/springframework/web/socket/messaging/WebSocketStompClientTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,9 @@ void cancelInactivityTasks() throws Exception {
302302
tcpConnection.onReadInactivity(mock(), 2L);
303303
tcpConnection.onWriteInactivity(mock(), 2L);
304304

305-
this.webSocketHandlerCaptor.getValue().afterConnectionClosed(this.webSocketSession, CloseStatus.NORMAL);
305+
WebSocketHandler handler = this.webSocketHandlerCaptor.getValue();
306+
TcpConnection<?> connection = (TcpConnection<?>) WebSocketHandlerDecorator.unwrap(handler);
307+
connection.close();
306308

307309
verify(future, times(2)).cancel(true);
308310
verifyNoMoreInteractions(future);

0 commit comments

Comments
 (0)