示例#1
0
        // private methods
        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;
                    _connectionsRemovedSinceLastTimerTick += 1;
                    Monitor.Pulse(_connectionPoolLock);
                }
            }

            // close connection outside of lock
            connection.Close();
        }
示例#2
0
        private MongoReplyMessage <TDocument> GetFirst()
        {
            connection = cursor.Server.AcquireConnection(cursor.Database, cursor.SlaveOk);
            try {
                // some of these weird conditions are necessary to get commands to run correctly
                // specifically numberToReturn has to be 1 or -1 for commands
                int numberToReturn;
                if (cursor.Limit < 0)
                {
                    numberToReturn = cursor.Limit;
                }
                else if (cursor.Limit == 0)
                {
                    numberToReturn = cursor.BatchSize;
                }
                else if (cursor.BatchSize == 0)
                {
                    numberToReturn = cursor.Limit;
                }
                else if (cursor.Limit < cursor.BatchSize)
                {
                    numberToReturn = cursor.Limit;
                }
                else
                {
                    numberToReturn = cursor.BatchSize;
                }

                using (
                    var message = new MongoQueryMessage(
                        cursor.Server,
                        cursor.Collection.FullName,
                        cursor.Flags,
                        cursor.Skip,
                        numberToReturn,
                        WrapQuery(),
                        cursor.Fields
                        )
                    ) {
                    return(GetReply(message));
                }
            } catch {
                try { ReleaseConnection(); } catch { } // ignore exceptions
                throw;
            }
        }
示例#3
0
 private void ReleaseConnection()
 {
     if (connection != null)
     {
         try {
             if (openCursorId != 0)
             {
                 using (var message = new MongoKillCursorsMessage(cursor.Server, openCursorId)) {
                     connection.SendMessage(message, SafeMode.False); // no need to use SafeMode for KillCursors
                 }
             }
             cursor.Server.ReleaseConnection(connection);
         } finally {
             connection   = null;
             openCursorId = 0;
         }
     }
 }
示例#4
0
        // 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);
        }
示例#5
0
        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();
            }
        }
示例#6
0
 internal MongoQueryMessage(
     MongoConnection connection,
     string collectionFullName,
     QueryFlags flags,
     int numberToSkip,
     int numberToReturn,
     IMongoQuery query,
     IMongoFields fields,
     BsonBuffer buffer
     ) :
     base(connection, MessageOpcode.Query, buffer)
 {
     this.collectionFullName = collectionFullName;
     this.flags          = flags;
     this.numberToSkip   = numberToSkip;
     this.numberToReturn = numberToReturn;
     this.query          = query;
     this.fields         = fields;
 }
 protected MongoRequestMessage(
     MongoConnection connection,
     MessageOpcode opcode,
     BsonBuffer buffer // not null if piggybacking this message onto an existing buffer
     )
     : base(connection, opcode)
 {
     if (buffer == null)
     {
         this.buffer        = new BsonBuffer();
         this.disposeBuffer = true; // only call Dispose if we allocated the buffer
     }
     else
     {
         this.buffer        = buffer;
         this.disposeBuffer = false;
     }
     this.requestId = Interlocked.Increment(ref lastRequestId);
 }
        private void TimerCallback(
            object state // not used
            )
        {
            lock (connectionPoolLock) {
                // if connection pool is closed stop the timer
                if (closed)
                {
                    timer.Dispose();
                    return;
                }

                // only remove one connection per timer tick to avoid reconnection storms
                if (connectionsRemovedSinceLastTimerTick == 0)
                {
                    MongoConnection oldestConnection = null;
                    MongoConnection lruConnection    = null;
                    foreach (var connection in availableConnections)
                    {
                        if (oldestConnection == null || connection.CreatedAt < oldestConnection.CreatedAt)
                        {
                            oldestConnection = connection;
                        }
                        if (lruConnection == null || connection.LastUsedAt < lruConnection.LastUsedAt)
                        {
                            lruConnection = connection;
                        }
                    }

                    // remove old connections before idle connections
                    var now = DateTime.UtcNow;
                    if (oldestConnection != null && now > oldestConnection.CreatedAt + server.Settings.MaxConnectionLifeTime)
                    {
                        RemoveConnection(oldestConnection);
                    }
                    else if (poolSize > server.Settings.MinConnectionPoolSize && lruConnection != null && now > lruConnection.LastUsedAt + server.Settings.MaxConnectionIdleTime)
                    {
                        RemoveConnection(lruConnection);
                    }
                }
                connectionsRemovedSinceLastTimerTick = 0;
            }
        }
        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 pool is closed just close connection on worker thread
                if (closed)
                {
                    ThreadPool.QueueUserWorkItem(CloseConnectionWorkItem, connection);
                    return;
                }

                // don't put closed or damaged connections back in 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);
            }
        }
        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);
            }
        }
示例#11
0
        internal MongoConnection AcquireConnection(AcquireConnectionOptions options)
        {
            MongoConnection connectionToClose = null;

            try
            {
                DateTime timeoutAt = DateTime.UtcNow + options.WaitQueueTimeout;

                lock (_connectionPoolLock)
                {
                    if (_waitQueueSize >= _settings.WaitQueueSize && !options.OkToExceedWaitQueueSize)
                    {
                        throw new MongoConnectionException("Too many threads are already waiting for a connection.");
                    }

                    _waitQueueSize += 1;
                    try
                    {
                        while (true)
                        {
                            if (_availableConnections.Count > 0)
                            {
                                var connection = _availableConnections[_availableConnections.Count - 1];
                                if (connection.IsExpired())
                                {
                                    connectionToClose = connection;
                                    connection        = new MongoConnection(this);
                                }

                                _availableConnections.RemoveAt(_availableConnections.Count - 1);
                                return(connection);
                            }

                            // avoid waiting by creating a new connection if options allow it
                            if (options.OkToAvoidWaitingByCreatingNewConnection)
                            {
                                if (_poolSize < _settings.MaxConnectionPoolSize || options.OkToExceedMaxConnectionPoolSize)
                                {
                                    // make sure connection is created successfully before incrementing poolSize
                                    // connection will be opened later outside of the lock
                                    var connection = new MongoConnection(this);
                                    _poolSize += 1;
                                    return(connection);
                                }
                            }

                            // wait for a connection to be released
                            var timeRemaining = timeoutAt - DateTime.UtcNow;
                            if (timeRemaining > TimeSpan.Zero)
                            {
                                // other methods should call Monitor.Pulse whenever:
                                // 1. an available connection is added _availableConnections
                                // 2. the _poolSize changes
                                Monitor.Wait(_connectionPoolLock, timeRemaining);
                            }
                            else
                            {
                                if (options.OkToExceedMaxConnectionPoolSize)
                                {
                                    // make sure connection is created successfully before incrementing poolSize
                                    // connection will be opened later outside of the lock
                                    var connection = new MongoConnection(this);
                                    _poolSize += 1;
                                    return(connection);
                                }
                                else
                                {
                                    throw new TimeoutException("Timeout waiting for a MongoConnection.");
                                }
                            }
                        }
                    }
                    finally
                    {
                        _waitQueueSize -= 1;
                    }
                }
            }
            finally
            {
                if (connectionToClose != null)
                {
                    try
                    {
                        connectionToClose.Close();
                    }
                    catch
                    {
                        // ignore exceptions
                    }
                }
            }
        }
示例#12
0
        // 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;
            }
        }
        // internal methods
        internal MongoConnection AcquireConnection(MongoDatabase database)
        {
            if (database != null && database.Server != _server)
            {
                throw new ArgumentException("This connection pool is for a different server.", "database");
            }

            lock (_connectionPoolLock)
            {
                if (_waitQueueSize >= _server.Settings.WaitQueueSize)
                {
                    throw new MongoConnectionException("Too many threads are already waiting for a connection.");
                }

                _waitQueueSize += 1;
                try
                {
                    DateTime timeoutAt = DateTime.UtcNow + _server.Settings.WaitQueueTimeout;
                    while (true)
                    {
                        if (_availableConnections.Count > 0)
                        {
                            // first try to find the most recently used connection that is already authenticated for this database
                            for (int i = _availableConnections.Count - 1; i >= 0; i--)
                            {
                                if (_availableConnections[i].IsAuthenticated(database))
                                {
                                    var connection = _availableConnections[i];
                                    _availableConnections.RemoveAt(i);
                                    return(connection);
                                }
                            }

                            // otherwise find the most recently used connection that can be authenticated for this database
                            for (int i = _availableConnections.Count - 1; i >= 0; i--)
                            {
                                if (_availableConnections[i].CanAuthenticate(database))
                                {
                                    var connection = _availableConnections[i];
                                    _availableConnections.RemoveAt(i);
                                    return(connection);
                                }
                            }

                            // otherwise replace the least recently used connection with a brand new one
                            // if this happens a lot the connection pool size should be increased
                            _availableConnections[0].Close();
                            _availableConnections.RemoveAt(0);
                            return(new MongoConnection(this));
                        }

                        // create a new connection if maximum pool size has not been reached
                        if (_poolSize < _server.Settings.MaxConnectionPoolSize)
                        {
                            // make sure connection is created successfully before incrementing poolSize
                            var connection = new MongoConnection(this);
                            _poolSize += 1;
                            return(connection);
                        }

                        // wait for a connection to be released
                        var timeRemaining = timeoutAt - DateTime.UtcNow;
                        if (timeRemaining > TimeSpan.Zero)
                        {
                            Monitor.Wait(_connectionPoolLock, timeRemaining);
                        }
                        else
                        {
                            throw new TimeoutException("Timeout waiting for a MongoConnection.");
                        }
                    }
                }
                finally
                {
                    _waitQueueSize -= 1;
                }
            }
        }
        private void TimerCallback(object state)
        {
            // make sure only one instance of TimerCallback is running at a time
            if (_inTimerCallback)
            {
                // Console.WriteLine("MongoConnectionPool[{0}] TimerCallback skipped because previous callback has not completed.", serverInstance.SequentialId);
                return;
            }

            // Console.WriteLine("MongoConnectionPool[{0}]: TimerCallback called.", serverInstance.SequentialId);
            _inTimerCallback = true;
            try
            {
                var server = _serverInstance.Server;
                if (server.State == MongoServerState.Disconnected || server.State == MongoServerState.Disconnecting)
                {
                    return;
                }

                // on every timer callback verify the state of the server instance because it might have changed
                // we do this even if this one instance is currently Disconnected so we can discover when a disconnected instance comes back online
                _serverInstance.VerifyState();

                lock (_connectionPoolLock)
                {
                    // note: the state could have changed to Disconnected when VerifyState was called
                    if (_serverInstance.State == MongoServerState.Disconnected)
                    {
                        return;
                    }

                    // only remove one connection per timer tick to avoid reconnection storms
                    if (_connectionsRemovedSinceLastTimerTick == 0)
                    {
                        MongoConnection oldestConnection = null;
                        MongoConnection lruConnection    = null;
                        foreach (var connection in _availableConnections)
                        {
                            if (oldestConnection == null || connection.CreatedAt < oldestConnection.CreatedAt)
                            {
                                oldestConnection = connection;
                            }
                            if (lruConnection == null || connection.LastUsedAt < lruConnection.LastUsedAt)
                            {
                                lruConnection = connection;
                            }
                        }

                        // remove old connections before idle connections
                        var now = DateTime.UtcNow;
                        if (oldestConnection != null && now > oldestConnection.CreatedAt + server.Settings.MaxConnectionLifeTime)
                        {
                            RemoveConnection(oldestConnection);
                        }
                        else if (_poolSize > server.Settings.MinConnectionPoolSize && lruConnection != null && now > lruConnection.LastUsedAt + server.Settings.MaxConnectionIdleTime)
                        {
                            RemoveConnection(lruConnection);
                        }
                    }
                    _connectionsRemovedSinceLastTimerTick = 0;
                }

                if (_poolSize < server.Settings.MinConnectionPoolSize)
                {
                    ThreadPool.QueueUserWorkItem(EnsureMinConnectionPoolSizeWorkItem, _generationId);
                }
            }
            catch
            {
                // don't let any unhandled exceptions leave TimerCallback
                // server state will already have been change by earlier exception handling
                // TODO: log exception?
            }
            finally
            {
                _inTimerCallback = false;
            }
        }
示例#15
0
        public void Connect(
            TimeSpan timeout
            )
        {
            // query all servers in seed list in parallel (they will report responses back through the responsesQueue)
            var responsesQueue = QueueSeedListQueries();

            // process the responses as they come back and stop as soon as we find the primary (unless SlaveOk is true)
            // stragglers will continue to report responses to the responsesQueue but no one will read them
            // and eventually it will all get garbage collected

            var exceptions = new List <Exception>();
            var timeoutAt  = DateTime.UtcNow + timeout;

            while (responses.Count < queries.Count)
            {
                var timeRemaining = timeoutAt - DateTime.UtcNow;
                var response      = responsesQueue.Dequeue(timeRemaining);
                if (response == null)
                {
                    break; // we timed out
                }
                responses.Add(response.Address, response);

                if (response.Exception != null)
                {
                    exceptions.Add(response.Exception);
                    continue;
                }

                if (response.IsPrimary)
                {
                    primaryConnection = response.Connection;
                    replicaSet        = GetHostAddresses(response);
                    maxDocumentSize   = response.MaxDocumentSize;
                    maxMessageLength  = response.MaxMessageLength;
                    if (!server.Settings.SlaveOk)
                    {
                        break; // if we're not going to use the secondaries no need to wait for their replies
                    }
                }
                else
                {
                    if (server.Settings.SlaveOk)
                    {
                        secondaryConnections.Add(response.Connection);
                    }
                    else
                    {
                        response.Connection.Close();
                    }
                }

                // look for additional members of the replica set that might not have been in the seed list and query them also
                foreach (var address in GetHostAddresses(response))
                {
                    if (!queries.Contains(address))
                    {
                        var args = new QueryNodeParameters {
                            Address       = address,
                            EndPoint      = address.ToIPEndPoint(server.Settings.AddressFamily),
                            ResponseQueue = responsesQueue
                        };
                        ThreadPool.QueueUserWorkItem(QueryNodeWorkItem, args);
                        queries.Add(address);
                    }
                }
            }

            if (primaryConnection == null)
            {
                var innerException = exceptions.FirstOrDefault();
                var exception      = new MongoConnectionException("Unable to connect to server", innerException);
                if (exceptions.Count > 1)
                {
                    exception.Data.Add("InnerExceptions", exceptions);
                }
                throw exception;
            }
        }
        internal MongoConnection AcquireConnection(
            MongoDatabase database
            )
        {
            if (database.Server != server)
            {
                throw new ArgumentException("This connection pool is for a different server", "database");
            }

            lock (connectionPoolLock) {
                if (waitQueueSize >= server.Settings.WaitQueueSize)
                {
                    throw new MongoConnectionException("Too many threads are already waiting for a connection");
                }

                waitQueueSize += 1;
                try {
                    DateTime timeoutAt = DateTime.UtcNow + server.Settings.WaitQueueTimeout;
                    while (true)
                    {
                        if (closed)
                        {
                            throw new InvalidOperationException("Attempt to get a connection from a closed connection pool");
                        }

                        if (availableConnections.Count > 0)
                        {
                            // first try to find the most recently used connection that is already authenticated for this database
                            for (int i = availableConnections.Count - 1; i >= 0; i--)
                            {
                                if (availableConnections[i].IsAuthenticated(database))
                                {
                                    var connection = availableConnections[i];
                                    availableConnections.RemoveAt(i);
                                    return(connection);
                                }
                            }

                            // otherwise find the most recently used connection that can be authenticated for this database
                            for (int i = availableConnections.Count - 1; i >= 0; i--)
                            {
                                if (availableConnections[i].CanAuthenticate(database))
                                {
                                    var connection = availableConnections[i];
                                    availableConnections.RemoveAt(i);
                                    return(connection);
                                }
                            }

                            // otherwise replace the least recently used connection with a brand new one
                            // if this happens a lot the connection pool size should be increased
                            ThreadPool.QueueUserWorkItem(CloseConnectionWorkItem, availableConnections[0]);
                            availableConnections.RemoveAt(0);
                            return(new MongoConnection(this, endPoint));
                        }

                        // create a new connection if maximum pool size has not been reached
                        if (poolSize < server.Settings.MaxConnectionPoolSize)
                        {
                            // make sure connection is created successfully before incrementing poolSize
                            var connection = new MongoConnection(this, endPoint);
                            poolSize += 1;
                            return(connection);
                        }

                        // wait for a connection to be released
                        var timeRemaining = timeoutAt - DateTime.UtcNow;
                        if (timeRemaining > TimeSpan.Zero)
                        {
                            Monitor.Wait(connectionPoolLock, timeRemaining);
                        }
                        else
                        {
                            throw new TimeoutException("Timeout waiting for a MongoConnection");
                        }
                    }
                } finally {
                    waitQueueSize -= 1;
                }
            }
        }
示例#17
0
 internal MongoReplyMessage(
     MongoConnection connection
     )
     : base(connection, MessageOpcode.Reply)
 {
 }