@@ -66,6 +66,7 @@ public class HubConnection implements AutoCloseable {
66
66
private final int negotiateVersion = 1 ;
67
67
private final Logger logger = LoggerFactory .getLogger (HubConnection .class );
68
68
private ScheduledExecutorService handshakeTimeout = null ;
69
+ private Completable start ;
69
70
70
71
/**
71
72
* Sets the server timeout interval for the connection.
@@ -341,83 +342,99 @@ public void setBaseUrl(String url) {
341
342
* @return A Completable that completes when the connection has been established.
342
343
*/
343
344
public Completable start () {
344
- if (hubConnectionState != HubConnectionState .DISCONNECTED ) {
345
- return Completable .complete ();
346
- }
347
-
348
- handshakeResponseSubject = CompletableSubject .create ();
349
- handshakeReceived = false ;
350
- CompletableSubject tokenCompletable = CompletableSubject .create ();
351
- localHeaders .put (UserAgentHelper .getUserAgentName (), UserAgentHelper .createUserAgentString ());
352
- if (headers != null ) {
353
- this .localHeaders .putAll (headers );
354
- }
345
+ CompletableSubject localStart = CompletableSubject .create ();
355
346
356
- accessTokenProvider .subscribe (token -> {
357
- if (token != null && !token .isEmpty ()) {
358
- this .localHeaders .put ("Authorization" , "Bearer " + token );
347
+ hubConnectionStateLock .lock ();
348
+ try {
349
+ if (hubConnectionState != HubConnectionState .DISCONNECTED ) {
350
+ logger .debug ("The connection is in the '{}' state. Waiting for in-progress start to complete or completing this start immediately." , hubConnectionState );
351
+ return start ;
359
352
}
360
- tokenCompletable .onComplete ();
361
- }, error -> {
362
- tokenCompletable .onError (error );
363
- });
364
353
365
- stopError = null ;
366
- Single <NegotiateResponse > negotiate = null ;
367
- if (!skipNegotiate ) {
368
- negotiate = tokenCompletable .andThen (Single .defer (() -> startNegotiate (baseUrl , 0 )));
369
- } else {
370
- negotiate = tokenCompletable .andThen (Single .defer (() -> Single .just (new NegotiateResponse (baseUrl ))));
371
- }
354
+ hubConnectionState = HubConnectionState .CONNECTING ;
355
+ start = localStart ;
372
356
373
- CompletableSubject start = CompletableSubject .create ();
357
+ handshakeResponseSubject = CompletableSubject .create ();
358
+ handshakeReceived = false ;
359
+ CompletableSubject tokenCompletable = CompletableSubject .create ();
360
+ localHeaders .put (UserAgentHelper .getUserAgentName (), UserAgentHelper .createUserAgentString ());
361
+ if (headers != null ) {
362
+ this .localHeaders .putAll (headers );
363
+ }
374
364
375
- negotiate .flatMapCompletable (negotiateResponse -> {
376
- logger .debug ("Starting HubConnection." );
377
- if (transport == null ) {
378
- Single <String > tokenProvider = negotiateResponse .getAccessToken () != null ? Single .just (negotiateResponse .getAccessToken ()) : accessTokenProvider ;
379
- switch (transportEnum ) {
380
- case LONG_POLLING :
381
- transport = new LongPollingTransport (localHeaders , httpClient , tokenProvider );
382
- break ;
383
- default :
384
- transport = new WebSocketTransport (localHeaders , httpClient );
365
+ accessTokenProvider .subscribe (token -> {
366
+ if (token != null && !token .isEmpty ()) {
367
+ this .localHeaders .put ("Authorization" , "Bearer " + token );
385
368
}
369
+ tokenCompletable .onComplete ();
370
+ }, error -> {
371
+ tokenCompletable .onError (error );
372
+ });
373
+
374
+ stopError = null ;
375
+ Single <NegotiateResponse > negotiate = null ;
376
+ if (!skipNegotiate ) {
377
+ negotiate = tokenCompletable .andThen (Single .defer (() -> startNegotiate (baseUrl , 0 )));
378
+ } else {
379
+ negotiate = tokenCompletable .andThen (Single .defer (() -> Single .just (new NegotiateResponse (baseUrl ))));
386
380
}
387
381
388
- transport .setOnReceive (this .callback );
389
- transport .setOnClose ((message ) -> stopConnection (message ));
390
-
391
- return transport .start (negotiateResponse .getFinalUrl ()).andThen (Completable .defer (() -> {
392
- ByteBuffer handshake = HandshakeProtocol .createHandshakeRequestMessage (
393
- new HandshakeRequestMessage (protocol .getName (), protocol .getVersion ()));
394
-
395
- connectionState = new ConnectionState (this );
396
-
397
- return transport .send (handshake ).andThen (Completable .defer (() -> {
398
- timeoutHandshakeResponse (handshakeResponseTimeout , TimeUnit .MILLISECONDS );
399
- return handshakeResponseSubject .andThen (Completable .defer (() -> {
400
- hubConnectionStateLock .lock ();
401
- try {
402
- hubConnectionState = HubConnectionState .CONNECTED ;
403
- logger .info ("HubConnection started." );
404
- resetServerTimeout ();
405
- //Don't send pings if we're using long polling.
406
- if (transportEnum != TransportEnum .LONG_POLLING ) {
407
- activatePingTimer ();
382
+ negotiate .flatMapCompletable (negotiateResponse -> {
383
+ logger .debug ("Starting HubConnection." );
384
+ if (transport == null ) {
385
+ Single <String > tokenProvider = negotiateResponse .getAccessToken () != null ? Single .just (negotiateResponse .getAccessToken ()) : accessTokenProvider ;
386
+ switch (transportEnum ) {
387
+ case LONG_POLLING :
388
+ transport = new LongPollingTransport (localHeaders , httpClient , tokenProvider );
389
+ break ;
390
+ default :
391
+ transport = new WebSocketTransport (localHeaders , httpClient );
392
+ }
393
+ }
394
+
395
+ transport .setOnReceive (this .callback );
396
+ transport .setOnClose ((message ) -> stopConnection (message ));
397
+
398
+ return transport .start (negotiateResponse .getFinalUrl ()).andThen (Completable .defer (() -> {
399
+ ByteBuffer handshake = HandshakeProtocol .createHandshakeRequestMessage (
400
+ new HandshakeRequestMessage (protocol .getName (), protocol .getVersion ()));
401
+
402
+ connectionState = new ConnectionState (this );
403
+
404
+ return transport .send (handshake ).andThen (Completable .defer (() -> {
405
+ timeoutHandshakeResponse (handshakeResponseTimeout , TimeUnit .MILLISECONDS );
406
+ return handshakeResponseSubject .andThen (Completable .defer (() -> {
407
+ hubConnectionStateLock .lock ();
408
+ try {
409
+ hubConnectionState = HubConnectionState .CONNECTED ;
410
+ logger .info ("HubConnection started." );
411
+ resetServerTimeout ();
412
+ //Don't send pings if we're using long polling.
413
+ if (transportEnum != TransportEnum .LONG_POLLING ) {
414
+ activatePingTimer ();
415
+ }
416
+ } finally {
417
+ hubConnectionStateLock .unlock ();
408
418
}
409
- } finally {
410
- hubConnectionStateLock .unlock ();
411
- }
412
419
413
- return Completable .complete ();
420
+ return Completable .complete ();
421
+ }));
414
422
}));
415
423
}));
416
- }));
417
- // subscribe makes this a "hot" completable so this runs immediately
418
- }).subscribeWith (start );
424
+ // subscribe makes this a "hot" completable so this runs immediately
425
+ }).subscribe (() -> {
426
+ localStart .onComplete ();
427
+ }, error -> {
428
+ hubConnectionStateLock .lock ();
429
+ hubConnectionState = HubConnectionState .DISCONNECTED ;
430
+ hubConnectionStateLock .unlock ();
431
+ localStart .onError (error );
432
+ });
433
+ } finally {
434
+ hubConnectionStateLock .unlock ();
435
+ }
419
436
420
- return start ;
437
+ return localStart ;
421
438
}
422
439
423
440
private void activatePingTimer () {
@@ -445,8 +462,8 @@ public void run() {
445
462
}
446
463
447
464
private Single <NegotiateResponse > startNegotiate (String url , int negotiateAttempts ) {
448
- if (hubConnectionState != HubConnectionState .DISCONNECTED ) {
449
- return Single . just ( null );
465
+ if (hubConnectionState != HubConnectionState .CONNECTING ) {
466
+ throw new RuntimeException ( "HubConnection trying to negotiate when not in the CONNECTING state." );
450
467
}
451
468
452
469
return handleNegotiate (url ).flatMap (response -> {
0 commit comments