Skip to content

Commit 8e50db6

Browse files
Cleanup resources in Java client (#20473)
1 parent 1d56c51 commit 8e50db6

File tree

8 files changed

+105
-31
lines changed

8 files changed

+105
-31
lines changed

src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/DefaultHttpClient.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,17 @@ public DefaultHttpClient cloneWithTimeOut(int timeoutInMilliseconds) {
2929
return new DefaultHttpClient(timeoutInMilliseconds, newClient);
3030
}
3131

32+
@Override
33+
public void close() {
34+
if (this.client != null) {
35+
this.client.dispatcher().executorService().shutdown();
36+
}
37+
}
38+
3239
public DefaultHttpClient(int timeoutInMilliseconds, OkHttpClient client) {
3340
if (client != null) {
3441
this.client = client;
3542
} else {
36-
3743
OkHttpClient.Builder builder = new OkHttpClient.Builder().cookieJar(new CookieJar() {
3844
private List<Cookie> cookieList = new ArrayList<>();
3945
private Lock cookieLock = new ReentrantLock();

src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpClient.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public String getStatusText() {
7474
}
7575
}
7676

77-
abstract class HttpClient {
77+
abstract class HttpClient implements AutoCloseable {
7878
public Single<HttpResponse> get(String url) {
7979
HttpRequest request = new HttpRequest();
8080
request.setUrl(url);
@@ -127,4 +127,6 @@ public Single<HttpResponse> delete(String url, HttpRequest options) {
127127
public abstract WebSocketWrapper createWebSocket(String url, Map<String, String> headers);
128128

129129
public abstract HttpClient cloneWithTimeOut(int timeoutInMilliseconds);
130+
131+
public abstract void close();
130132
}

src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,14 @@
2424
/**
2525
* A connection used to invoke hub methods on a SignalR Server.
2626
*/
27-
public class HubConnection {
27+
public class HubConnection implements AutoCloseable {
2828
private static final String RECORD_SEPARATOR = "\u001e";
2929
private static final List<Class<?>> emptyArray = new ArrayList<>();
3030
private static final int MAX_NEGOTIATE_ATTEMPTS = 100;
3131

3232
private String baseUrl;
3333
private Transport transport;
34+
private boolean customTransport = false;
3435
private OnReceiveCallBack callback;
3536
private final CallbackMap handlers = new CallbackMap();
3637
private HubProtocol protocol;
@@ -59,6 +60,7 @@ public class HubConnection {
5960
private String connectionId;
6061
private final int negotiateVersion = 1;
6162
private final Logger logger = LoggerFactory.getLogger(HubConnection.class);
63+
private ScheduledExecutorService handshakeTimeout = null;
6264

6365
/**
6466
* Sets the server timeout interval for the connection.
@@ -111,7 +113,7 @@ void setTickRate(long tickRateInMilliseconds) {
111113
}
112114

113115
// For testing purposes
114-
Map<String,Observable> getStreamMap() {
116+
Map<String, Observable> getStreamMap() {
115117
return this.streamMap;
116118
}
117119

@@ -146,6 +148,7 @@ Transport getTransport() {
146148

147149
if (transport != null) {
148150
this.transport = transport;
151+
this.customTransport = true;
149152
} else if (transportEnum != null) {
150153
this.transportEnum = transportEnum;
151154
}
@@ -246,8 +249,8 @@ Transport getTransport() {
246249
}
247250

248251
private void timeoutHandshakeResponse(long timeout, TimeUnit unit) {
249-
ScheduledExecutorService scheduledThreadPool = Executors.newSingleThreadScheduledExecutor();
250-
scheduledThreadPool.schedule(() -> {
252+
handshakeTimeout = Executors.newSingleThreadScheduledExecutor();
253+
handshakeTimeout.schedule(() -> {
251254
// If onError is called on a completed subject the global error handler is called
252255
if (!(handshakeResponseSubject.hasComplete() || handshakeResponseSubject.hasThrowable()))
253256
{
@@ -531,6 +534,15 @@ private void stopConnection(String errorMessage) {
531534
transportEnum = TransportEnum.ALL;
532535
this.localHeaders.clear();
533536
this.streamMap.clear();
537+
538+
if (this.handshakeTimeout != null) {
539+
this.handshakeTimeout.shutdownNow();
540+
this.handshakeTimeout = null;
541+
}
542+
543+
if (this.customTransport == false) {
544+
this.transport = null;
545+
}
534546
} finally {
535547
hubConnectionStateLock.unlock();
536548
}
@@ -1097,4 +1109,16 @@ public List<Class<?>> getParameterTypes(String methodName) {
10971109
return handlers.get(0).getClasses();
10981110
}
10991111
}
1112+
1113+
@Override
1114+
public void close() {
1115+
try {
1116+
stop().blockingAwait();
1117+
} finally {
1118+
// Don't close HttpClient if it's passed in by the user
1119+
if (this.httpClient != null && this.httpClient instanceof DefaultHttpClient) {
1120+
this.httpClient.close();
1121+
}
1122+
}
1123+
}
11001124
}

src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ public Completable stop() {
160160
CompletableSubject stopCompletableSubject = CompletableSubject.create();
161161
return this.receiveLoop.andThen(Completable.defer(() -> {
162162
logger.info("LongPolling transport stopped.");
163+
this.onReceiveThread.shutdown();
164+
this.threadPool.shutdown();
163165
this.onClose.invoke(this.closeError);
164166
return Completable.complete();
165167
})).subscribeWith(stopCompletableSubject);

src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2479,7 +2479,7 @@ public void hubConnectionCanBeStartedAfterBeingStopped() {
24792479
}
24802480

24812481
@Test
2482-
public void hubConnectionCanBeStartedAfterBeingStoppedAndRedirected() {
2482+
public void hubConnectionCanBeStartedAfterBeingStoppedAndRedirected() {
24832483
MockTransport mockTransport = new MockTransport();
24842484
TestHttpClient client = new TestHttpClient()
24852485
.on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\"}")))
@@ -2519,4 +2519,30 @@ public void non200FromNegotiateThrowsError() {
25192519
() -> hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait());
25202520
assertEquals("Unexpected status code returned from negotiate: 500 Internal server error.", exception.getMessage());
25212521
}
2522+
2523+
@Test
2524+
public void hubConnectionCloseCallsStop() throws Exception {
2525+
MockTransport mockTransport = new MockTransport();
2526+
TestHttpClient client = new TestHttpClient()
2527+
.on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\"}")))
2528+
.on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\""
2529+
+ "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")));
2530+
2531+
CompletableSubject close = CompletableSubject.create();
2532+
2533+
try (HubConnection hubConnection = HubConnectionBuilder
2534+
.create("http://example.com")
2535+
.withTransportImplementation(mockTransport)
2536+
.withHttpClient(client)
2537+
.build()) {
2538+
2539+
hubConnection.onClosed(e -> {
2540+
close.onComplete();
2541+
});
2542+
hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait();
2543+
assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState());
2544+
}
2545+
2546+
close.timeout(1, TimeUnit.SECONDS).blockingGet();
2547+
}
25222548
}

src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestHttpClient.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
class TestHttpClient extends HttpClient {
1313
private TestHttpRequestHandler handler;
1414
private List<HttpRequest> sentRequests;
15+
private boolean closeCalled;
1516

1617
public TestHttpClient() {
1718
this.sentRequests = new ArrayList<>();
@@ -76,6 +77,15 @@ public HttpClient cloneWithTimeOut(int timeoutInMilliseconds) {
7677
return this;
7778
}
7879

80+
@Override
81+
public void close() {
82+
this.closeCalled = true;
83+
}
84+
85+
public boolean getCloseCalled() {
86+
return this.closeCalled;
87+
}
88+
7989
interface TestHttpRequestHandler {
8090
Single<HttpResponse> invoke(HttpRequest request);
8191
}

src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/WebSocketTransportTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ public WebSocketWrapper createWebSocket(String url, Map<String, String> headers)
5555
public HttpClient cloneWithTimeOut(int timeoutInMilliseconds) {
5656
return null;
5757
}
58+
59+
@Override
60+
public void close() {
61+
}
5862
}
5963

6064
class TestWrapper extends WebSocketWrapper {

src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/sample/Chat.java

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,33 @@
1010

1111

1212
public class Chat {
13-
public static void main(String[] args) {
13+
public static void main(final String[] args) throws Exception {
1414
System.out.println("Enter the URL of the SignalR Chat you want to join");
15-
Scanner reader = new Scanner(System.in); // Reading from System.in
16-
String input = reader.nextLine();
17-
18-
HubConnection hubConnection = HubConnectionBuilder.create(input).build();
19-
20-
hubConnection.on("Send", (message) -> {
21-
System.out.println(message);
22-
}, String.class);
23-
24-
hubConnection.onClosed((ex) -> {
25-
if (ex != null) {
26-
System.out.printf("There was an error: %s", ex.getMessage());
15+
final Scanner reader = new Scanner(System.in); // Reading from System.in
16+
final String input = reader.nextLine();
17+
18+
try (HubConnection hubConnection = HubConnectionBuilder.create(input).build()) {
19+
hubConnection.on("Send", (message) -> {
20+
System.out.println(message);
21+
}, String.class);
22+
23+
hubConnection.onClosed((ex) -> {
24+
if (ex != null) {
25+
System.out.printf("There was an error: %s", ex.getMessage());
26+
}
27+
});
28+
29+
//This is a blocking call
30+
hubConnection.start().blockingAwait();
31+
32+
String message = "";
33+
while (!message.equals("leave")) {
34+
// Scans the next token of the input as an int.
35+
message = reader.nextLine();
36+
hubConnection.send("Send", message);
2737
}
28-
});
2938

30-
//This is a blocking call
31-
hubConnection.start().blockingAwait();
32-
33-
String message = "";
34-
while (!message.equals("leave")) {
35-
// Scans the next token of the input as an int.
36-
message = reader.nextLine();
37-
hubConnection.send("Send", message);
39+
hubConnection.stop().blockingAwait();
3840
}
39-
40-
hubConnection.stop().blockingAwait();
4141
}
4242
}

0 commit comments

Comments
 (0)