internal void EnsureMinConnectionPoolSizeWorkItem( object state // forGenerationId ) { // 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 >= 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 (generationId == forGenerationId && 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)); } } } 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; } }
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 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); }
internal void MaintainPoolSize() { if (_inMaintainPoolSize) { return; } _inMaintainPoolSize = true; try { MongoConnection connectionToClose = null; lock (_connectionPoolLock) { for (int i = 0; i < _availableConnections.Count; i++) { var connection = _availableConnections[i]; if (connection.IsExpired()) { _availableConnections.RemoveAt(i); _poolSize -= 1; connectionToClose = connection; Monitor.Pulse(_connectionPoolLock); break; } } } if (connectionToClose != null) { try { connectionToClose.Close(); } catch { // ignore exceptions } } if (_poolSize < _settings.MinConnectionPoolSize) { ThreadPool.QueueUserWorkItem(EnsureMinConnectionPoolSizeWorkItem, _generationId); } } finally { _inMaintainPoolSize = false; } }
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"); } // if the connection is no longer open 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; } } var connectionIsFromAnotherGeneration = false; lock (_connectionPoolLock) { if (connection.GenerationId == _generationId) { connection.LastUsedAt = DateTime.UtcNow; _availableConnections.Add(connection); Monitor.Pulse(_connectionPoolLock); } else { connectionIsFromAnotherGeneration = true; } } // if connection is from another generation of the pool just close it if (connectionIsFromAnotherGeneration) { 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, 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 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 } } } }