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(); } } }
// 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 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(); } }
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(); } } }