Skip to content

Commit 73c6805

Browse files
authored
Replace TimerTask with ScheduledExecutorService (TooTallNate#878)
Replace TimerTask with ScheduledExecutorService
2 parents 8a74a87 + a911973 commit 73c6805

File tree

4 files changed

+82
-32
lines changed

4 files changed

+82
-32
lines changed

src/main/java/org/java_websocket/AbstractWebSocket.java

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@
2626
package org.java_websocket;
2727

2828
import org.java_websocket.framing.CloseFrame;
29+
import org.java_websocket.util.NamedThreadFactory;
2930
import org.slf4j.Logger;
3031
import org.slf4j.LoggerFactory;
3132

3233
import java.util.ArrayList;
3334
import java.util.Collection;
34-
import java.util.Timer;
35-
import java.util.TimerTask;
35+
import java.util.concurrent.Executors;
36+
import java.util.concurrent.ScheduledExecutorService;
37+
import java.util.concurrent.ScheduledFuture;
38+
import java.util.concurrent.TimeUnit;
3639

3740

3841
/**
@@ -60,21 +63,21 @@ public abstract class AbstractWebSocket extends WebSocketAdapter {
6063
private boolean reuseAddr;
6164

6265
/**
63-
* Attribute for a timer allowing to check for lost connections
64-
* @since 1.3.4
66+
* Attribute for a service that triggers lost connection checking
67+
* @since 1.4.1
6568
*/
66-
private Timer connectionLostTimer;
69+
private ScheduledExecutorService connectionLostCheckerService;
6770
/**
68-
* Attribute for a timertask allowing to check for lost connections
69-
* @since 1.3.4
71+
* Attribute for a task that checks for lost connections
72+
* @since 1.4.1
7073
*/
71-
private TimerTask connectionLostTimerTask;
74+
private ScheduledFuture connectionLostCheckerFuture;
7275

7376
/**
74-
* Attribute for the lost connection check interval
77+
* Attribute for the lost connection check interval in nanoseconds
7578
* @since 1.3.4
7679
*/
77-
private int connectionLostTimeout = 60;
80+
private long connectionLostTimeout = TimeUnit.SECONDS.toNanos(60);
7881

7982
/**
8083
* Attribute to keep track if the WebSocket Server/Client is running/connected
@@ -89,12 +92,12 @@ public abstract class AbstractWebSocket extends WebSocketAdapter {
8992
/**
9093
* Get the interval checking for lost connections
9194
* Default is 60 seconds
92-
* @return the interval
95+
* @return the interval in seconds
9396
* @since 1.3.4
9497
*/
9598
public int getConnectionLostTimeout() {
9699
synchronized (syncConnectionLost) {
97-
return connectionLostTimeout;
100+
return (int) TimeUnit.NANOSECONDS.toSeconds(connectionLostTimeout);
98101
}
99102
}
100103

@@ -107,7 +110,7 @@ public int getConnectionLostTimeout() {
107110
*/
108111
public void setConnectionLostTimeout( int connectionLostTimeout ) {
109112
synchronized (syncConnectionLost) {
110-
this.connectionLostTimeout = connectionLostTimeout;
113+
this.connectionLostTimeout = TimeUnit.SECONDS.toNanos(connectionLostTimeout);
111114
if (this.connectionLostTimeout <= 0) {
112115
log.trace("Connection lost timer stopped");
113116
cancelConnectionLostTimer();
@@ -139,7 +142,7 @@ public void setConnectionLostTimeout( int connectionLostTimeout ) {
139142
*/
140143
protected void stopConnectionLostTimer() {
141144
synchronized (syncConnectionLost) {
142-
if (connectionLostTimer != null || connectionLostTimerTask != null) {
145+
if (connectionLostCheckerService != null || connectionLostCheckerFuture != null) {
143146
this.websocketRunning = false;
144147
log.trace("Connection lost timer stopped");
145148
cancelConnectionLostTimer();
@@ -168,8 +171,8 @@ protected void startConnectionLostTimer() {
168171
*/
169172
private void restartConnectionLostTimer() {
170173
cancelConnectionLostTimer();
171-
connectionLostTimer = new Timer("WebSocketTimer");
172-
connectionLostTimerTask = new TimerTask() {
174+
connectionLostCheckerService = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("connectionLostChecker"));
175+
Runnable connectionLostChecker = new Runnable() {
173176

174177
/**
175178
* Keep the connections in a separate list to not cause deadlocks
@@ -180,31 +183,31 @@ public void run() {
180183
connections.clear();
181184
try {
182185
connections.addAll( getConnections() );
183-
long current = ( System.currentTimeMillis() - ( connectionLostTimeout * 1500 ) );
186+
long minimumPongTime = (long) (System.nanoTime() - ( connectionLostTimeout * 1.5 ));
184187
for( WebSocket conn : connections ) {
185-
executeConnectionLostDetection(conn, current);
188+
executeConnectionLostDetection(conn, minimumPongTime);
186189
}
187190
} catch ( Exception e ) {
188191
//Ignore this exception
189192
}
190193
connections.clear();
191194
}
192195
};
193-
connectionLostTimer.scheduleAtFixedRate( connectionLostTimerTask,1000L*connectionLostTimeout , 1000L*connectionLostTimeout );
194196

197+
connectionLostCheckerFuture = connectionLostCheckerService.scheduleAtFixedRate(connectionLostChecker, connectionLostTimeout, connectionLostTimeout, TimeUnit.NANOSECONDS);
195198
}
196199

197200
/**
198201
* Send a ping to the endpoint or close the connection since the other endpoint did not respond with a ping
199202
* @param webSocket the websocket instance
200-
* @param current the current time in milliseconds
203+
* @param minimumPongTime the lowest/oldest allowable last pong time (in nanoTime) before we consider the connection to be lost
201204
*/
202-
private void executeConnectionLostDetection(WebSocket webSocket, long current) {
205+
private void executeConnectionLostDetection(WebSocket webSocket, long minimumPongTime) {
203206
if (!(webSocket instanceof WebSocketImpl)) {
204207
return;
205208
}
206209
WebSocketImpl webSocketImpl = (WebSocketImpl) webSocket;
207-
if( webSocketImpl.getLastPong() < current ) {
210+
if( webSocketImpl.getLastPong() < minimumPongTime ) {
208211
log.trace("Closing connection due to no pong received: {}", webSocketImpl);
209212
webSocketImpl.closeConnection( CloseFrame.ABNORMAL_CLOSE, "The connection was closed because the other endpoint did not respond with a pong in time. For more information check: https://github.com/TooTallNate/Java-WebSocket/wiki/Lost-connection-detection" );
210213
} else {
@@ -228,13 +231,13 @@ private void executeConnectionLostDetection(WebSocket webSocket, long current) {
228231
* @since 1.3.4
229232
*/
230233
private void cancelConnectionLostTimer() {
231-
if( connectionLostTimer != null ) {
232-
connectionLostTimer.cancel();
233-
connectionLostTimer = null;
234+
if( connectionLostCheckerService != null ) {
235+
connectionLostCheckerService.shutdownNow();
236+
connectionLostCheckerService = null;
234237
}
235-
if( connectionLostTimerTask != null ) {
236-
connectionLostTimerTask.cancel();
237-
connectionLostTimerTask = null;
238+
if( connectionLostCheckerFuture != null ) {
239+
connectionLostCheckerFuture.cancel(false);
240+
connectionLostCheckerFuture = null;
238241
}
239242
}
240243

src/main/java/org/java_websocket/WebSocketImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public class WebSocketImpl implements WebSocket {
151151
/**
152152
* Attribute, when the last pong was recieved
153153
*/
154-
private long lastPong = System.currentTimeMillis();
154+
private long lastPong = System.nanoTime();
155155

156156
/**
157157
* Attribut to synchronize the write
@@ -802,7 +802,7 @@ long getLastPong() {
802802
* Update the timestamp when the last pong was received
803803
*/
804804
public void updateLastPong() {
805-
this.lastPong = System.currentTimeMillis();
805+
this.lastPong = System.nanoTime();
806806
}
807807

808808
/**
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2010-2019 Nathan Rajlich
3+
*
4+
* Permission is hereby granted, free of charge, to any person
5+
* obtaining a copy of this software and associated documentation
6+
* files (the "Software"), to deal in the Software without
7+
* restriction, including without limitation the rights to use,
8+
* copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the
10+
* Software is furnished to do so, subject to the following
11+
* conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be
14+
* included in all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18+
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20+
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21+
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23+
* OTHER DEALINGS IN THE SOFTWARE.
24+
*/
25+
26+
package org.java_websocket.util;
27+
28+
import java.util.concurrent.Executors;
29+
import java.util.concurrent.ThreadFactory;
30+
import java.util.concurrent.atomic.AtomicInteger;
31+
32+
public class NamedThreadFactory implements ThreadFactory {
33+
private final ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory();
34+
private final AtomicInteger threadNumber = new AtomicInteger(1);
35+
private final String threadPrefix;
36+
37+
public NamedThreadFactory(String threadPrefix) {
38+
this.threadPrefix = threadPrefix;
39+
}
40+
41+
@Override
42+
public Thread newThread(Runnable runnable) {
43+
Thread thread = defaultThreadFactory.newThread(runnable);
44+
thread.setName(threadPrefix + "-" + threadNumber);
45+
return thread;
46+
}
47+
}

src/test/java/org/java_websocket/issues/Issue666Test.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public void onStart() {
8080
}
8181
for( Thread thread : mapAfter.values() ) {
8282
String name = thread.getName();
83-
if( !name.startsWith( "WebSocketSelector-" ) && !name.startsWith( "WebSocketWorker-" ) && !name.equals( "WebSocketTimer" ) ) {
83+
if( !name.startsWith( "WebSocketSelector-" ) && !name.startsWith( "WebSocketWorker-" ) && !name.startsWith( "connectionLostChecker-" ) ) {
8484
Assert.fail( "Thread not correctly named! Is: " + name );
8585
}
8686
}
@@ -145,7 +145,7 @@ public void onStart() {
145145
}
146146
for( Thread thread : mapAfter.values() ) {
147147
String name = thread.getName();
148-
if( !name.equals( "WebSocketTimer" ) && !name.startsWith( "WebSocketWriteThread-" ) && !name.startsWith( "WebSocketConnectReadThread-" )) {
148+
if( !name.startsWith( "connectionLostChecker-" ) && !name.startsWith( "WebSocketWriteThread-" ) && !name.startsWith( "WebSocketConnectReadThread-" )) {
149149
Assert.fail( "Thread not correctly named! Is: " + name );
150150
}
151151
}

0 commit comments

Comments
 (0)