You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
405 lines
15 KiB
405 lines
15 KiB
/* Copyright 2010-2011 10gen Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
using MongoDB.Bson;
|
|
using MongoDB.Bson.Serialization;
|
|
using MongoDB.Driver.Internal;
|
|
|
|
namespace MongoDB.Driver {
|
|
/// <summary>
|
|
/// Represents an instance of a MongoDB server host (in the case of a replica set a MongoServer uses multiple MongoServerInstances).
|
|
/// </summary>
|
|
public class MongoServerInstance {
|
|
#region private static fields
|
|
private static int nextSequentialId;
|
|
#endregion
|
|
|
|
#region public events
|
|
/// <summary>
|
|
/// Occurs when the value of the State property changes.
|
|
/// </summary>
|
|
public event EventHandler StateChanged;
|
|
#endregion
|
|
|
|
#region private fields
|
|
private object serverInstanceLock = new object();
|
|
private MongoServerAddress address;
|
|
private MongoServerBuildInfo buildInfo;
|
|
private Exception connectException;
|
|
private MongoConnectionPool connectionPool;
|
|
private IPEndPoint endPoint;
|
|
private bool isArbiter;
|
|
private CommandResult isMasterResult;
|
|
private bool isPassive;
|
|
private bool isPrimary;
|
|
private bool isSecondary;
|
|
private int maxDocumentSize;
|
|
private int maxMessageLength;
|
|
private int sequentialId;
|
|
private MongoServer server;
|
|
private MongoServerState state; // always use property to set value so event gets raised
|
|
#endregion
|
|
|
|
#region constructors
|
|
internal MongoServerInstance(
|
|
MongoServer server,
|
|
MongoServerAddress address
|
|
) {
|
|
this.server = server;
|
|
this.address = address;
|
|
this.sequentialId = Interlocked.Increment(ref nextSequentialId);
|
|
this.maxDocumentSize = MongoDefaults.MaxDocumentSize;
|
|
this.maxMessageLength = MongoDefaults.MaxMessageLength;
|
|
this.state = MongoServerState.Disconnected;
|
|
this.connectionPool = new MongoConnectionPool(this);
|
|
// Console.WriteLine("MongoServerInstance[{0}]: {1}", sequentialId, address);
|
|
}
|
|
#endregion
|
|
|
|
#region public properties
|
|
/// <summary>
|
|
/// Gets the address of this server instance.
|
|
/// </summary>
|
|
public MongoServerAddress Address {
|
|
get { return address; }
|
|
internal set {
|
|
lock (serverInstanceLock) {
|
|
if (state != MongoServerState.Disconnected) {
|
|
throw new MongoInternalException("MongoServerInstance Address can only be set when State is Disconnected.");
|
|
}
|
|
address = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the version of this server instance.
|
|
/// </summary>
|
|
public MongoServerBuildInfo BuildInfo {
|
|
get { return buildInfo; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the exception thrown the last time Connect was called (null if Connect did not throw an exception).
|
|
/// </summary>
|
|
public Exception ConnectException {
|
|
get { return connectException; }
|
|
internal set { connectException = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the connection pool for this server instance.
|
|
/// </summary>
|
|
public MongoConnectionPool ConnectionPool {
|
|
get { return connectionPool; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the IP end point of this server instance.
|
|
/// </summary>
|
|
public IPEndPoint EndPoint {
|
|
get { return endPoint; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether this server instance is an arbiter instance.
|
|
/// </summary>
|
|
public bool IsArbiter {
|
|
get { return isArbiter; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the result of the most recent ismaster command sent to this server instance.
|
|
/// </summary>
|
|
public CommandResult IsMasterResult {
|
|
get { return isMasterResult; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether this server instance is a passive instance.
|
|
/// </summary>
|
|
public bool IsPassive {
|
|
get { return isPassive; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether this server instance is a primary.
|
|
/// </summary>
|
|
public bool IsPrimary {
|
|
get { return isPrimary; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether this server instance is a secondary.
|
|
/// </summary>
|
|
public bool IsSecondary {
|
|
get { return isSecondary; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the max document size for this server instance.
|
|
/// </summary>
|
|
public int MaxDocumentSize {
|
|
get { return maxDocumentSize; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the max message length for this server instance.
|
|
/// </summary>
|
|
public int MaxMessageLength {
|
|
get { return maxMessageLength; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the unique sequential Id for this server instance.
|
|
/// </summary>
|
|
public int SequentialId {
|
|
get { return sequentialId; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the server for this server instance.
|
|
/// </summary>
|
|
public MongoServer Server {
|
|
get { return server; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the state of this server instance.
|
|
/// </summary>
|
|
public MongoServerState State {
|
|
get { return state; }
|
|
internal set {
|
|
lock (serverInstanceLock) {
|
|
if (state != value) {
|
|
// Console.WriteLine("MongoServerInstance[{0}]: State changed: state={1}{2}", sequentialId, value, isPrimary ? " (Primary)" : "");
|
|
state = value;
|
|
if (StateChanged != null) {
|
|
try { StateChanged(this, null); } catch { } // ignore exceptions
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region public methods
|
|
/// <summary>
|
|
/// Checks whether the server is alive (throws an exception if not).
|
|
/// </summary>
|
|
public void Ping() {
|
|
var connection = connectionPool.AcquireConnection(null);
|
|
try {
|
|
var pingCommand = new CommandDocument("ping", 1);
|
|
connection.RunCommand("admin.$cmd", QueryFlags.SlaveOk, pingCommand);
|
|
} finally {
|
|
connectionPool.ReleaseConnection(connection);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies the state of the server instance.
|
|
/// </summary>
|
|
public void VerifyState() {
|
|
lock (serverInstanceLock) {
|
|
// Console.WriteLine("MongoServerInstance[{0}]: VerifyState called.", sequentialId);
|
|
// if ping fails assume all connections in the connection pool are doomed
|
|
try {
|
|
Ping();
|
|
} catch {
|
|
// Console.WriteLine("MongoServerInstance[{0}]: Ping failed: {1}.", sequentialId, ex.Message);
|
|
connectionPool.Clear();
|
|
}
|
|
|
|
var connection = connectionPool.AcquireConnection(null);
|
|
try {
|
|
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 {
|
|
ReleaseConnection(connection);
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region internal methods
|
|
internal MongoConnection AcquireConnection(
|
|
MongoDatabase database
|
|
) {
|
|
MongoConnection connection;
|
|
lock (serverInstanceLock) {
|
|
if (state != MongoServerState.Connected) {
|
|
var message = string.Format("Server instance {0} is no longer connected.", address);
|
|
throw new InvalidOperationException(message);
|
|
}
|
|
connection = connectionPool.AcquireConnection(database);
|
|
}
|
|
|
|
// check authentication outside the lock because it might involve a round trip to the server
|
|
try {
|
|
connection.CheckAuthentication(database); // will authenticate if necessary
|
|
} catch (MongoAuthenticationException) {
|
|
// don't let the connection go to waste just because authentication failed
|
|
ReleaseConnection(connection); // ReleaseConnection will reacquire the lock
|
|
throw;
|
|
}
|
|
|
|
return connection;
|
|
}
|
|
|
|
internal void Connect(
|
|
bool slaveOk
|
|
) {
|
|
// Console.WriteLine("MongoServerInstance[{0}]: Connect(slaveOk={1}) called.", sequentialId, slaveOk);
|
|
lock (serverInstanceLock) {
|
|
// note: don't check that state is Disconnected here
|
|
// when reconnecting to a replica set state can transition from Connected -> Connecting -> Connected
|
|
|
|
State = MongoServerState.Connecting;
|
|
connectException = null;
|
|
try {
|
|
endPoint = address.ToIPEndPoint(server.Settings.AddressFamily);
|
|
|
|
try {
|
|
var connection = connectionPool.AcquireConnection(null);
|
|
try {
|
|
VerifyState(connection);
|
|
if (!isPrimary && !slaveOk) {
|
|
throw new MongoConnectionException("Server is not a primary and SlaveOk is false.");
|
|
}
|
|
} finally {
|
|
connectionPool.ReleaseConnection(connection);
|
|
}
|
|
} catch {
|
|
connectionPool.Clear();
|
|
throw;
|
|
}
|
|
|
|
State = MongoServerState.Connected;
|
|
} catch (Exception ex) {
|
|
State = MongoServerState.Disconnected;
|
|
connectException = ex;
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void Disconnect() {
|
|
// Console.WriteLine("MongoServerInstance[{0}]: Disconnect called.", sequentialId);
|
|
lock (serverInstanceLock) {
|
|
if (state == MongoServerState.Disconnecting) {
|
|
throw new MongoInternalException("Disconnect called while disconnecting.");
|
|
}
|
|
if (state != MongoServerState.Disconnected) {
|
|
try {
|
|
State = MongoServerState.Disconnecting;
|
|
connectionPool.Clear();
|
|
} finally {
|
|
State = MongoServerState.Disconnected;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void ReleaseConnection(
|
|
MongoConnection connection
|
|
) {
|
|
lock (serverInstanceLock) {
|
|
connectionPool.ReleaseConnection(connection);
|
|
}
|
|
}
|
|
|
|
internal void VerifyState(
|
|
MongoConnection connection
|
|
) {
|
|
CommandResult isMasterResult = null;
|
|
try {
|
|
try {
|
|
var isMasterCommand = new CommandDocument("ismaster", 1);
|
|
isMasterResult = connection.RunCommand("admin.$cmd", QueryFlags.SlaveOk, isMasterCommand);
|
|
} catch (MongoCommandException ex) {
|
|
isMasterResult = ex.CommandResult;
|
|
throw;
|
|
}
|
|
|
|
var isPrimary = isMasterResult.Response["ismaster", false].ToBoolean();
|
|
var isSecondary = isMasterResult.Response["secondary", false].ToBoolean();
|
|
var isPassive = isMasterResult.Response["passive", false].ToBoolean();
|
|
var isArbiter = isMasterResult.Response["arbiterOnly", false].ToBoolean();
|
|
// workaround for CSHARP-273
|
|
if (isPassive && isArbiter) { isPassive = false; }
|
|
|
|
var maxDocumentSize = isMasterResult.Response["maxBsonObjectSize", MongoDefaults.MaxDocumentSize].ToInt32();
|
|
var maxMessageLength = Math.Max(MongoDefaults.MaxMessageLength, maxDocumentSize + 1024); // derived from maxDocumentSize
|
|
|
|
MongoServerBuildInfo buildInfo;
|
|
try {
|
|
var buildInfoCommand = new CommandDocument("buildinfo", 1);
|
|
var buildInfoResult = connection.RunCommand("admin.$cmd", QueryFlags.SlaveOk, buildInfoCommand);
|
|
buildInfo = new MongoServerBuildInfo(
|
|
buildInfoResult.Response["bits"].ToInt32(), // bits
|
|
buildInfoResult.Response["gitVersion"].AsString, // gitVersion
|
|
buildInfoResult.Response["sysInfo"].AsString, // sysInfo
|
|
buildInfoResult.Response["version"].AsString // versionString
|
|
);
|
|
} catch (MongoCommandException ex) {
|
|
// short term fix: if buildInfo fails due to auth we don't know the server version
|
|
if (ex.CommandResult.ErrorMessage == "need to login") {
|
|
buildInfo = null;
|
|
} else {
|
|
throw;
|
|
}
|
|
}
|
|
|
|
this.isMasterResult = isMasterResult;
|
|
this.isPrimary = isPrimary;
|
|
this.isSecondary = isSecondary;
|
|
this.isPassive = isPassive;
|
|
this.isArbiter = isArbiter;
|
|
this.maxDocumentSize = maxDocumentSize;
|
|
this.maxMessageLength = maxMessageLength;
|
|
this.buildInfo = buildInfo;
|
|
this.State = MongoServerState.Connected;
|
|
} catch {
|
|
this.isMasterResult = isMasterResult;
|
|
this.isPrimary = false;
|
|
this.isSecondary = false;
|
|
this.isPassive = false;
|
|
this.isArbiter = false;
|
|
this.maxDocumentSize = MongoDefaults.MaxDocumentSize;
|
|
this.maxMessageLength = MongoDefaults.MaxMessageLength;
|
|
this.buildInfo = null;
|
|
this.State = MongoServerState.Disconnected;
|
|
throw;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|