// 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(); }
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; } }
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; } } }
// 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 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(); } }
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); } }
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 } } } }
// 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; } }
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; } } }
internal MongoReplyMessage( MongoConnection connection ) : base(connection, MessageOpcode.Reply) { }