private void StateVerificationTimerCallback()
        {
            if (_inStateVerification)
            {
                return;
            }

            _inStateVerification = true;
            try
            {
                var connection = new MongoConnection(this);
                try
                {
                    Ping(connection);
                    LookupServerInformation(connection);
                    ThreadPool.QueueUserWorkItem(o => _connectionPool.MaintainPoolSize());
                    SetState(MongoServerState.Connected);
                }
                finally
                {
                    connection.Close();
                }
            }
            catch { } // this is called in a timer thread and we don't want any exceptions escaping
            finally
            {
                _inStateVerification = false;
            }
        }
        /// <summary>
        /// Checks whether the server is alive (throws an exception if not).
        /// </summary>
        public void Ping()
        {
            // use a new connection instead of one from the connection pool
            var connection = new MongoConnection(this);

            try
            {
                Ping(connection);
            }
            finally
            {
                connection.Close();
            }
        }
        /// <summary>
        /// Verifies the state of the server instance.
        /// </summary>
        public void VerifyState()
        {
            lock (_serverInstanceLock)
            {
                // use a new connection instead of one from the connection pool
                var connection = new MongoConnection(this);
                try
                {
                    // Console.WriteLine("MongoServerInstance[{0}]: VerifyState called.", sequentialId);
                    // if ping fails assume all connections in the connection pool are doomed
                    try
                    {
                        Ping(connection);
                    }
                    catch
                    {
                        // Console.WriteLine("MongoServerInstance[{0}]: Ping failed: {1}.", sequentialId, ex.Message);
                        _connectionPool.Clear();
                    }

                    var previousState = _state;
                    try
                    {
                        VerifyState(connection);
                    }
                    catch
                    {
                        // ignore exceptions (if any occured state will already be set to Disconnected)
                        // Console.WriteLine("MongoServerInstance[{0}]: VerifyState failed: {1}.", sequentialId, ex.Message);
                    }
                    if (_state != previousState && _state == MongoServerState.Disconnected)
                    {
                        _connectionPool.Clear();
                    }
                }
                finally
                {
                    connection.Close();
                }
            }
        }
        /// <summary>
        /// Verifies the state of the server instance.
        /// </summary>
        public void VerifyState()
        {
            // use a new connection instead of one from the connection pool
            var connection = new MongoConnection(this);

            try
            {
                try
                {
                    Ping(connection);
                    LookupServerInformation(connection);
                }
                catch
                {
                    // ignore exceptions (if any occured state will already be set to Disconnected)
                    // Console.WriteLine("MongoServerInstance[{0}]: VerifyState failed: {1}.", sequentialId, ex.Message);
                }
            }
            finally
            {
                connection.Close();
            }
        }
        internal void ReleaseConnection(MongoConnection connection)
        {
            if (connection.ConnectionPool != this)
            {
                throw new ArgumentException("The connection being released does not belong to this connection pool.", "connection");
            }

            lock (_connectionPoolLock)
            {
                // if connection is from another generation of the pool just close it
                if (connection.GenerationId != _generationId)
                {
                    connection.Close();
                    return;
                }

                // if the connection is no longer open don't remove it from the pool
                if (connection.State != MongoConnectionState.Open)
                {
                    RemoveConnection(connection);
                    return;
                }

                // don't put connections that have reached their maximum lifetime back in the pool
                // but only remove one connection at most per timer tick to avoid connection storms
                if (_connectionsRemovedSinceLastTimerTick == 0)
                {
                    if (DateTime.UtcNow - connection.CreatedAt > _server.Settings.MaxConnectionLifeTime)
                    {
                        RemoveConnection(connection);
                        return;
                    }
                }

                connection.LastUsedAt = DateTime.UtcNow;
                _availableConnections.Add(connection);
                Monitor.Pulse(_connectionPoolLock);
            }
        }
        /// <summary>
        /// Verifies the state of the server instance.
        /// </summary>
        public void VerifyState()
        {
            if (!ShouldVerifyState())
            {
                return;
            }

            // use a new connection instead of one from the connection pool
            var connection = new MongoConnection(this);
            try
            {
                try
                {
                    Ping(connection);
                    LookupServerInformation(connection);
                }
                catch
                {
                    // ignore exceptions (if any occured state will already be set to Disconnected)
                    // Console.WriteLine("MongoServerInstance[{0}]: VerifyState failed: {1}.", sequentialId, ex.Message);
                }
            }
            finally
            {
                connection.Close();
            }
        }
        // note: this method will run on a thread from the ThreadPool
        private void QueryNodeWorkItem(
            object parameters
        )
        {
            // this method has to work at a very low level because the connection pool isn't set up yet
            var args = (QueryNodeParameters) parameters;
            var response = new QueryNodeResponse { Address = args.Address };

            try {
                var connection = new MongoConnection(null, args.Address); // no connection pool
                try {
                    var isMasterCommand = new BsonDocument("ismaster", 1);
                    var isMasterResult = connection.RunCommand("admin.$cmd", QueryFlags.SlaveOk, isMasterCommand);

                    response.IsMasterResult = isMasterResult;
                    response.Connection = connection; // might become the first connection in the connection pool
                    response.IsPrimary = isMasterResult["ismaster", false].ToBoolean();

                    if (url.ReplicaSetName != null) {
                        var getStatusCommand = new BsonDocument("replSetGetStatus", 1);
                        var getStatusResult = connection.RunCommand("admin.$cmd", QueryFlags.SlaveOk, getStatusCommand);

                        var replicaSetName = getStatusResult["set"].AsString;
                        if (replicaSetName != url.ReplicaSetName) {
                            var message = string.Format("Host {0} belongs to a different replica set: {1}", args.Address, replicaSetName);
                            throw new MongoConnectionException(message);
                        }
                    }
                } catch {
                    try { connection.Close(); } catch { } // ignore exceptions
                    throw;
                }
            } catch (Exception ex) {
                response.Exception = ex;
            }

            args.ResponseQueue.Enqueue(response);
        }
        // private methods
        private void EnsureMinConnectionPoolSizeWorkItem(object state)
        {
            // make sure only one instance of EnsureMinConnectionPoolSizeWorkItem is running at a time
            if (_inEnsureMinConnectionPoolSizeWorkItem)
            {
                return;
            }

            _inEnsureMinConnectionPoolSizeWorkItem = true;
            try
            {
                // keep creating connections one at a time until MinConnectionPoolSize is reached
                var forGenerationId = (int)state;
                while (true)
                {
                    lock (_connectionPoolLock)
                    {
                        // stop if the connection pool generationId has changed or we have already reached MinConnectionPoolSize
                        if (_generationId != forGenerationId || _poolSize >= _settings.MinConnectionPoolSize)
                        {
                            return;
                        }
                    }

                    var connection = new MongoConnection(this);
                    try
                    {
                        connection.Open();

                        // compare against MaxConnectionPoolSize instead of MinConnectionPoolSize
                        // because while we were opening this connection many others may have already been created
                        // and we don't want to throw this one away unless we would exceed MaxConnectionPoolSize
                        var added = false;
                        lock (_connectionPoolLock)
                        {
                            if (_generationId == forGenerationId && _poolSize < _settings.MaxConnectionPoolSize)
                            {
                                _availableConnections.Add(connection);
                                _poolSize++;
                                added = true;
                                Monitor.Pulse(_connectionPoolLock);
                            }
                        }

                        if (!added)
                        {
                            // turns out we couldn't use the connection after all
                            connection.Close();
                        }
                    }
                    catch
                    {
                        // TODO: log exception?
                        // wait a bit before trying again
                        Thread.Sleep(TimeSpan.FromSeconds(1));
                    }
                }
            }
            catch
            {
                // don't let unhandled exceptions leave EnsureMinConnectionPoolSizeWorkItem
                // if the minimum connection pool size was not achieved a new work item will be queued shortly
                // TODO: log exception?
            }
            finally
            {
                _inEnsureMinConnectionPoolSizeWorkItem = false;
            }
        }
        // note: this method will run on a thread from the ThreadPool
        private void QueryNodeWorkItem(
            object parameters
        ) {
            // this method has to work at a very low level because the connection pool isn't set up yet
            var args = (QueryNodeParameters) parameters;
            var response = new QueryNodeResponse { Address = args.Address, EndPoint = args.EndPoint };

            try {
                var connection = new MongoConnection(null, args.EndPoint); // no connection pool
                try {
                    var isMasterCommand = new CommandDocument("ismaster", 1);
                    var isMasterResult = connection.RunCommand(server, "admin.$cmd", QueryFlags.SlaveOk, isMasterCommand);

                    response.IsMasterResult = isMasterResult;
                    response.Connection = connection; // might become the first connection in the connection pool
                    response.IsPrimary = isMasterResult.Response["ismaster", false].ToBoolean();
                    response.MaxDocumentSize = isMasterResult.Response["maxBsonObjectSize", server.MaxDocumentSize].ToInt32();
                    response.MaxMessageLength = Math.Max(MongoDefaults.MaxMessageLength, response.MaxDocumentSize + 1024); // derived from maxDocumentSize

                    if (server.Settings.ReplicaSetName != null) {
                        var getStatusCommand = new CommandDocument("replSetGetStatus", 1);
                        var getStatusResult = connection.RunCommand(server, "admin.$cmd", QueryFlags.SlaveOk, getStatusCommand);

                        var replicaSetName = getStatusResult.Response["set"].AsString;
                        if (replicaSetName != server.Settings.ReplicaSetName) {
                            var message = string.Format("Host {0} belongs to a different replica set: {1}", args.EndPoint, replicaSetName);
                            throw new MongoConnectionException(message);
                        }
                    }
                } catch {
                    try { connection.Close(); } catch { } // ignore exceptions
                    throw;
                }
            } catch (Exception ex) {
                response.Exception = ex;
            }

            args.ResponseQueue.Enqueue(response);
        }
        internal void CreateInitialConnectionsWorkItem(
            object state // ignored
        ) {
            // keep creating connections one at a time until MinConnectionPoolSize is reached
            while (true) {
                lock (connectionPoolLock) {
                    // stop if connection pool has been closed or we have already reached MinConnectionPoolSize
                    if (closed || poolSize >= server.Settings.MinConnectionPoolSize) {
                        return;
                    }
                }

                var connection = new MongoConnection(this);
                try {
                    connection.Open();

                    // compare against MaxConnectionPoolSize instead of MinConnectionPoolSize
                    // because while we were opening this connection many others may have already been created
                    // and we don't want to throw this one away unless we would exceed MaxConnectionPoolSize
                    var added = false;
                    lock (connectionPoolLock) {
                        if (poolSize < server.Settings.MaxConnectionPoolSize) {
                            availableConnections.Add(connection);
                            poolSize++;
                            added = true;
                        }
                    }

                    if (!added) {
                        // turns out we couldn't use the connection after all
                        connection.Close();
                    }
                } catch {
                    // TODO: log exception?
                    // wait a bit before trying again
                    Thread.Sleep(TimeSpan.FromSeconds(1));
                }
            }
        }
        private void Connect(
            IPEndPoint endPoint,
            TimeSpan timeout
        )
        {
            var connection = new MongoConnection(null, endPoint); // no connection pool
            bool isPrimary;

            try {
                var isMasterCommand = new CommandDocument("ismaster", 1);
                var isMasterResult = connection.RunCommand(server, "admin.$cmd", QueryFlags.SlaveOk, isMasterCommand);

                isPrimary = isMasterResult.Response["ismaster", false].ToBoolean();
                if (!isPrimary && !server.Settings.SlaveOk) {
                    throw new MongoConnectionException("Server is not a primary and SlaveOk is false");
                }

                maxDocumentSize = isMasterResult.Response["maxBsonObjectSize", server.MaxDocumentSize].ToInt32();
                maxMessageLength = Math.Max(MongoDefaults.MaxMessageLength, maxDocumentSize + 1024); // derived from maxDocumentSize
            } catch {
                try { connection.Close(); } catch { } // ignore exceptions
                throw;
            }

            this.connection = connection;
            this.isPrimary = isPrimary;
        }
        private void Connect(
            MongoServerAddress address,
            TimeSpan timeout
        )
        {
            var connection = new MongoConnection(null, address); // no connection pool
            bool isPrimary;

            try {
                var isMasterCommand = new BsonDocument("ismaster", 1);
                var isMasterResult = connection.RunCommand("admin.$cmd", QueryFlags.SlaveOk, isMasterCommand);

                isPrimary = isMasterResult["ismaster", false].ToBoolean();
                if (!isPrimary && !url.SlaveOk) {
                    throw new MongoConnectionException("Server is not a primary and SlaveOk is false");
                }
            } catch {
                try { connection.Close(); } catch { } // ignore exceptions
                throw;
            }

            this.address = address;
            this.connection = connection;
            this.isPrimary = isPrimary;
        }
        internal void ReleaseConnection(
            MongoConnection connection
        )
        {
            if (connection.ConnectionPool != this) {
                throw new ArgumentException("The connection being released does not belong to this connection pool.", "connection");
            }

            lock (connectionPoolLock) {
                if (!closed) {
                    // if the thread has called RequestStart just verify that the connection it is releasing is the right one
                    int threadId = Thread.CurrentThread.ManagedThreadId;
                    Request request;
                    if (requests.TryGetValue(threadId, out request)) {
                        if (connection != request.Connection) {
                            throw new ArgumentException("Connection being released is not the one assigned to the thread by RequestStart", "connection");
                        }
                        return;
                    }

                    // close connections that haven't been used for 10 minutes or more (should this be on a timer?)
                    DateTime cutoff = DateTime.UtcNow - maxIdleTime;
                    foreach (var idleConnection in pool.Where(c => c.LastUsed < cutoff).ToList()) {
                        ThreadPool.QueueUserWorkItem(CloseConnectionWorkItem, idleConnection);
                        pool.Remove(idleConnection);
                    }

                    if (pool.Count == maxPoolSize) {
                        ThreadPool.QueueUserWorkItem(CloseConnectionWorkItem, pool[0]); // close oldest connection
                        pool.RemoveAt(0);
                    }

                    connection.LastUsed = DateTime.UtcNow;
                    pool.Add(connection);
                } else {
                    connection.Close();
                }
            }
        }
Example #14
0
        /// <summary>
        /// Verifies the state of the server instance.
        /// </summary>
        public void VerifyState()
        {
            lock (_serverInstanceLock)
            {
                // use a new connection instead of one from the connection pool
                var connection = new MongoConnection(this);
                try
                {
                    // Console.WriteLine("MongoServerInstance[{0}]: VerifyState called.", sequentialId);
                    // if ping fails assume all connections in the connection pool are doomed
                    try
                    {
                        Ping(connection);
                    }
                    catch
                    {
                        // Console.WriteLine("MongoServerInstance[{0}]: Ping failed: {1}.", sequentialId, ex.Message);
                        _connectionPool.Clear();
                    }

                    var previousState = _state;
                    try
                    {
                        VerifyState(connection);
                    }
                    catch
                    {
                        // ignore exceptions (if any occured state will already be set to Disconnected)
                        // Console.WriteLine("MongoServerInstance[{0}]: VerifyState failed: {1}.", sequentialId, ex.Message);
                    }
                    if (_state != previousState && _state == MongoServerState.Disconnected)
                    {
                        _connectionPool.Clear();
                    }
                }
                finally
                {
                    connection.Close();
                }
            }
        }
 // private methods
 private void RemoveConnection(MongoConnection connection)
 {
     _availableConnections.Remove(connection); // it might or might not be in availableConnections (but remove it if it is)
     _poolSize -= 1;
     _connectionsRemovedSinceLastTimerTick += 1;
     connection.Close();
     Monitor.Pulse(_connectionPoolLock);
 }
        private void StateVerificationTimerCallback()
        {
            if (_inStateVerification)
            {
                return;
            }

            _inStateVerification = true;
            try
            {
                var connection = new MongoConnection(this);
                try
                {
                    Ping(connection);
                    LookupServerInformation(connection);
                    ThreadPool.QueueUserWorkItem(o => _connectionPool.MaintainPoolSize());
                    SetState(MongoServerState.Connected);
                }
                finally
                {
                    connection.Close();
                }
            }
            catch { } // this is called in a timer thread and we don't want any exceptions escaping
            finally
            {
                _inStateVerification = false;
            }
        }
        internal void ReleaseConnection(MongoConnection connection)
        {
            if (connection.ConnectionPool != this)
            {
                throw new ArgumentException("The connection being released does not belong to this connection pool.", "connection");
            }

            // if the connection is no longer open remove it from the pool
            if (connection.State != MongoConnectionState.Open || connection.IsExpired())
            {
                RemoveConnection(connection);
                return;
            }

            var closeConnection = false;
            lock (_connectionPoolLock)
            {
                if (connection.GenerationId == _generationId)
                {
                    if (_poolSize <= _settings.MaxConnectionPoolSize)
                    {
                        _availableConnections.Add(connection);
                    }
                    else
                    {
                        _poolSize -= 1;
                        closeConnection = true;
                    }
                    Monitor.Pulse(_connectionPoolLock);
                }
                else
                {
                    closeConnection = true;
                }
            }

            // if connection is from another generation of the pool just close it
            if (closeConnection)
            {
                connection.Close();
            }
        }
 /// <summary>
 /// Checks whether the server is alive (throws an exception if not).
 /// </summary>
 public void Ping()
 {
     // use a new connection instead of one from the connection pool
     var connection = new MongoConnection(this);
     try
     {
         Ping(connection);
     }
     finally
     {
         connection.Close();
     }
 }
        private void RemoveConnection(MongoConnection connection)
        {
            lock (_connectionPoolLock)
            {
                // even though we may have checked the GenerationId once before getting here it might have changed since
                if (connection.GenerationId == _generationId)
                {
                    _availableConnections.Remove(connection); // it might or might not be in availableConnections (but remove it if it is)
                    _poolSize -= 1;
                    Monitor.Pulse(_connectionPoolLock);
                }
            }

            // close connection outside of lock
            connection.Close();
        }
        internal void ReleaseConnection(
            MongoConnection connection
        )
        {
            if (connection.ConnectionPool != this) {
                throw new ArgumentException("The connection being released does not belong to this connection pool.", "connection");
            }

            lock (connectionPoolLock) {
                if (!closed) {
                    // close connections that haven't been used for 10 minutes or more (should this be on a timer?)
                    DateTime cutoff = DateTime.UtcNow - maxIdleTime;
                    foreach (var idleConnection in pool.Where(c => c.LastUsed < cutoff).ToList()) {
                        ThreadPool.QueueUserWorkItem(CloseConnectionWorkItem, idleConnection);
                        pool.Remove(idleConnection);
                    }

                    if (pool.Count == maxPoolSize) {
                        ThreadPool.QueueUserWorkItem(CloseConnectionWorkItem, pool[0]); // close oldest connection
                        pool.RemoveAt(0);
                    }

                    connection.LastUsed = DateTime.UtcNow;
                    pool.Add(connection);
                } else {
                    connection.Close();
                }
            }
        }