[CSHARP-1670] Document the various connection modes Created: 16/May/16  Updated: 31/Mar/22

Status: Backlog
Project: C# Driver
Component/s: Documentation
Affects Version/s: None
Fix Version/s: None

Type: Improvement Priority: Major - P3
Reporter: Anatoliy Koperin Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: neweng
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

I used only one slave server in connection settings from the configured replica set:

rs.status()

{ 
    "myState" : 2, 
    "members" : [
        { "name" : "localhost:31500", "state" : 1, "stateStr" : "PRIMARY", ... }, 
        { "name" : "localhost:31502", "state" : 7, "stateStr" : "ARBITER", ... }, 
        { "name" : "localhost:31501", "state" : 2, "stateStr" : "SECONDARY", ... } ], ...
}

Test code:

var client = new MongoClient(new MongoClientSettings
	{
		ConnectionMode = ConnectionMode.Automatic,
		Servers = new []{ MongoServerAddress.Parse("localhost:31501")}
	});
var result = client.GetDatabase("test").RunCommand<BsonDocument>(new BsonDocument() { { "connectionStatus", 1 } });

I get an exception:

System.TimeoutException: A timeout occured after 30000ms selecting a server using CompositeServerSelector{ Selectors = ReadPreferenceServerSelector{ ReadPreference = { Mode = Primary, TagSets = [] } }, LatencyLimitingServerSelector{ AllowedLatencyRange = 00:00:00.0150000 } }. Client view of cluster state is { ClusterId : "6", ConnectionMode : "Automatic", Type : "ReplicaSet", State : "Connected", Servers : [{ ServerId: "{ ClusterId : 6, EndPoint : "Unspecified/localhost:31502" }", EndPoint: "Unspecified/localhost:31502", State: "Connected", Type: "ReplicaSetArbiter", WireVersionRange: "[0, 3]" }] }.
   at MongoDB.Driver.Core.Clusters.Cluster.ThrowTimeoutException(IServerSelector selector, ClusterDescription description)
   at MongoDB.Driver.Core.Clusters.Cluster.WaitForDescriptionChangedHelper.HandleCompletedTask(Task completedTask)
   at MongoDB.Driver.Core.Clusters.Cluster.WaitForDescriptionChanged(IServerSelector selector, ClusterDescription description, Task descriptionChangedTask, TimeSpan timeout, CancellationToken cancellationToken)
   at MongoDB.Driver.Core.Clusters.Cluster.SelectServer(IServerSelector selector, CancellationToken cancellationToken)
   at MongoDB.Driver.Core.Bindings.ReadPreferenceBinding.GetReadChannelSource(CancellationToken cancellationToken)
   at MongoDB.Driver.Core.Operations.ReadCommandOperation`1.Execute(IReadBinding binding, CancellationToken cancellationToken)
   at MongoDB.Driver.OperationExecutor.ExecuteReadOperation[TResult](IReadBinding binding, IReadOperation`1 operation, CancellationToken cancellationToken)
   at MongoDB.Driver.MongoDatabaseImpl.ExecuteReadOperation[T](IReadOperation`1 operation, ReadPreference readPreference, CancellationToken cancellationToken)
   at MongoDB.Driver.MongoDatabaseImpl.RunCommand[TResult](Command`1 command, ReadPreference readPreference, CancellationToken cancellationToken)

But if you specify another server name, the connection is established.

var client = new MongoClient(new MongoClientSettings
	{
		ConnectionMode = ConnectionMode.Automatic,
		Servers = new []{ MongoServerAddress.Parse("localhost:31501"), MongoServerAddress.Parse("unknown") }
	});
var result = client.GetDatabase("test").RunCommand<BsonDocument>(new BsonDocument() { { "connectionStatus", 1 } });

But it does not work if I connect to a single server:

  • localhost:30000 - standalone

var client = new MongoClient(new MongoClientSettings
	{
		ConnectionMode = ConnectionMode.Automatic,
		Servers = new []{ MongoServerAddress.Parse("localhost:30000"), MongoServerAddress.Parse("unknown") }
	});
var result = client.GetDatabase("test").RunCommand<BsonDocument>(new BsonDocument() { { "connectionStatus", 1 } });

How to connect with automatic mode to a variable number of servers?



 Comments   
Comment by Anatoliy Koperin [ 20/May/16 ]

I understand everything. Thanks!

Comment by Craig Wilson [ 19/May/16 ]

1. Not really. At the point where the exception is getting thrown, we don't know if this is the first connect. We are doing what asked and are unable to select a server, which the message indicates.

2. No, the timeout is required. If there wasn't a timeout, then the driver would just hang. It can be lowered or raised using the "serverSelectionTimeoutMS" connection string option. The default is 30 seconds.

As mentioned, the best way to combat this is to be very clear about what type of cluster you are attempting to talk to. If you have done this, these exceptions will only occur under actual error conditions.

Comment by Anatoliy Koperin [ 19/May/16 ]

Yes, this makes sence. But i still have 2 questions:
1. Is it possible to give more specific message?
2. Is it possible to eliminate the timeout?

Comment by Craig Wilson [ 17/May/16 ]

Gotcha,

So, the behavior is very unambiguous. Given X, we'll always do Y. However, I've realize that we don't have this documented, so I'll convert this into a docs ticket. For now, I believe the below is what you are wanting. Simple summarization -> If you intend to connect to a replica set, regardless of how many servers you are using, you should tell us that, either using a replica set name (strongly advised) or simply by telling us it's a replica set.

Longer -> You can see the code here: https://github.com/mongodb/mongo-csharp-driver/blob/master/src/MongoDB.Driver.Core/Core/Clusters/ClusterFactory.cs

When specifying only a single server in the connection string, automatic mode will allow you to connect. However, commands will only run against the primary for safety. In the case where you've connected to a secondary, commands like connectionStatus can't find a primary to talk to and will fail with a TimeoutException. If you have intended to connect to a secondary and want to run commands against it, you need to tell us that was what you intended by telling us to connect directly to it, using connect=direct in the connection string or ConnectionMode.Direct.

In the case where you intend to connect to a replica set but only have a single server at the current time (or only want to list a single one on the connection string), you should either give us a replica set name using the "replicaSet" connection string option or specify ConnectionMode.ReplicaSet. This will ensure that we'll discover the primary when it comes online.

Hope that makes sense.

Comment by Anatoliy Koperin [ 17/May/16 ]

Thanks for the answer!

With driver v1.10 I could connect to a replica set by specifying a secondary server only.
I broke the acceptance tests for this behavior when migrating to the driver v2.2

We used this when we were removing old servers from the replica set and adding new - to simplify the migration and eliminate the necessity of config modification on all web servers. Of course it was an intermediate step, and we finished with correct configs with all database servers specified.

But actually this is not the case - I am worried about ambiguous behavior of the driver v 2.2.
You can connect to a replica set by specifying a primary server only, but you can't do the same with a secondary. This could hide the error to administrator until the next election.

Comment by Craig Wilson [ 16/May/16 ]

Hi Anatoliy,

  • If you want to connect directly to a secondary, you need to indicate that with ConnectionMode.Direct.
  • If you want to connect to a standalone, you cannot specify more than one server address.

Perhaps you could indicate more exactly the server configuration you are expecting to connect to and I can help you out. If you don't know, then that could be a problem with the way we are currently parsing things, which is admittedly on the safe side in order to protect users from doing something unexpected.

Craig

Generated at Wed Feb 07 21:40:20 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.