@@ -66,6 +66,7 @@ public class HubConnection implements AutoCloseable {
6666 private final int negotiateVersion = 1 ;
6767 private final Logger logger = LoggerFactory .getLogger (HubConnection .class );
6868 private ScheduledExecutorService handshakeTimeout = null ;
69+ private Completable start ;
6970
7071 /**
7172 * Sets the server timeout interval for the connection.
@@ -341,83 +342,99 @@ public void setBaseUrl(String url) {
341342 * @return A Completable that completes when the connection has been established.
342343 */
343344 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 ();
355346
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 ;
359352 }
360- tokenCompletable .onComplete ();
361- }, error -> {
362- tokenCompletable .onError (error );
363- });
364353
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 ;
372356
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+ }
374364
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 );
385368 }
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 ))));
386380 }
387381
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 ();
408418 }
409- } finally {
410- hubConnectionStateLock .unlock ();
411- }
412419
413- return Completable .complete ();
420+ return Completable .complete ();
421+ }));
414422 }));
415423 }));
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+ }
419436
420- return start ;
437+ return localStart ;
421438 }
422439
423440 private void activatePingTimer () {
@@ -445,8 +462,8 @@ public void run() {
445462 }
446463
447464 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." );
450467 }
451468
452469 return handleNegotiate (url ).flatMap (response -> {
0 commit comments