Example #1
0
        /// <summary>
        /// Main entry-point to connect to a server
        /// </summary>
        /// <param name="initialPresentation">Indicates whether it's the very first attempt to connect,
        /// or rather some retry due to a connection drop.</param>
        /// <param name="token">Propagates notification that operations should be canceled.</param>
        /// <returns>The <see cref="TcpConnection"/> useful to exchange data with the server.</returns>
        internal TcpConnection ConnectToAServer(
            bool initialPresentation,
            CancellationToken token
            )
        {
            //select an iterator against the proper server list
            IEnumerator <ServerConnectionStatus> iter;

            lock (this._locker)
            {
                if (initialPresentation)
                {
                    iter = this._servers.GetEnumerator();
                }
                else
                {
                    iter = new ServerStatusEnumerator(this);
                }
            }

            //task cancelation will raise an exception
            token.ThrowIfCancellationRequested();

            Exception lastex = null;

            while (iter.MoveNext())
            {
                ServerConnectionStatus s = iter.Current;
                try
                {
                    //connection attempt
                    var tcp = TcpConnection.Create(this._options, s, token);
                    s.DidConnect        = true;
                    s.ReconnectionCount = 0;
                    this._currentServer = s;
                    return(tcp);
                }
                catch (OperationCanceledException ex)
                {
                    throw ex;
                }
                catch (Exception ex)
                {
                    lastex = ex;
                }

                if (initialPresentation == false)
                {
                    //manage stats
                    s.ReconnectionCount++;
                    this.IncrementStatsCounter?.Invoke();

                    //waits before retrying again
                    Helpers.CancelableDelay(this._options.ReconnectWait, token);
                }
            }

            if (lastex != null)
            {
                throw lastex;
            }

            this._currentServer = null;
            return(null);
        }
        /// <summary>
        /// The "inner" worker
        /// </summary>
        /// <remarks>
        /// This method can raise any kind of exception, which will be caught by the <see cref="OuterWorker(object)"/>.
        /// </remarks>
        /// <param name="ctx"></param>
        private void InnerWorker(
            WorkerContext ctx
            )
        {
            this._info = null;

            /**
             * Initial connection to a server
             **/
            try
            {
                this._tcp = this._srvPool.ConnectToAServer(
                    this._workerStage == StageServerInitialConnection,
                    ctx.CancToken
                    );

                if (this._tcp == null)
                {
                    throw new Exception();
                }
            }
            catch (OperationCanceledException ex)
            {
                throw ex;
            }
            catch (Exception)
            {
                throw new NATSNoServersException("Unable to connect to a server.");
            }


            /**
             * Waiting for the "INFO" data issued by the server
             **/
            this._workerStage = StageWaitInfo;
            this.ReadThenParseSegment(null, null, ReadTimeout, ctx.CancToken);

            if (this._info == null)
            {
                throw new NATSConnectionException("Protocol exception, INFO not received");
            }


            /**
             * Send "CONNECT" to the server
             **/
            this._workerStage = this._options.Verbose
                ? StageSendConnectVerbose
                : StageSendConnectNormal;

            try
            {
                this._tcp.SocketSendTimeout = 0;
                this._tcp.WriteString(this.ConnectProto());
                this.SendPing();
            }
            catch (Exception ex)
            {
                throw new NATSException("Error sending connect protocol message", ex);
            }

            this.ReadThenParseSegment(
                ex => throw new NATSConnectionException("Connect read error", ex),
                null,
                ReadTimeout,
                ctx.CancToken
                );

            if (this._workerStage != StageLogicallyConnected)
            {
                throw new NATSConnectionException("Connect read protocol error");
            }


            /**
             * Main processing loop
             **/
            this._tcp.SocketReceiveTimeout = ReadTimeout;   //for safety
            this.SetStatus(ConnState.CONNECTED);
            this.PingStart();
            this._owner.RequestReplyManager.Start();

            int readCount = 0;

            while (ctx.CancToken.IsCancellationRequested == false)
            {
                if (this._writePending)
                {
                    //something to send is pending
                    lock (this._writeLocker)
                    {
                        //is PING, maybe?
                        if (this._pingSendPending)
                        {
                            this.SendPing();
                        }

                        //are one or more SUB messages, maybe?
                        if (this._subPending)
                        {
                            for (int i = 0, sublen = this._subQueue.Count; i < sublen; i++)
                            {
                                this._tcp.WriteString(this._subQueue[i]);
                            }
                            this._subQueue.Clear();
                            this._tcp.WriteFlush();
                            this._subPending = false;
                        }

                        this._writePending = false;
                    }
                }
                else if (this._isLogicalStarted)
                {
                    if (readCount == 0)
                    {
                        //no operation was perfomed: no data in, no data out.
                        //the cycle would be too tight, then.
                        //give the OS a breathe of fresh air...
                        Thread.Sleep(1);
                    }
                }
                else
                {
                    //the initial NATS handshake with the server is complete:
                    //if the start was async, then consider its task as terminated
                    this._isLogicalStarted = true;
                    ctx.TaskCompletion?.SetResult(null);
                }

                //listen to incoming data
                readCount = this.ReadThenParseSegment(
                    this.ProcessOpError,
                    this.ProcessOpError,
                    0,
                    ctx.CancToken
                    );
            }
        }