-
Type: Bug
-
Resolution: Works as Designed
-
Priority: Major - P3
-
None
-
Affects Version/s: 2.11.5
-
Component/s: Transactions
-
None
-
Environment:MongoDB Sharded Cluster version: 4.2.2-ent
OS: Ubuntu 16.04
5 threads to execute transactions in parallel and encounter lots of 251 errors:
MongoCommandException, 251, "NoSuchTransaction", "Command insert failed: cannot continue txnId 35 for session 618e6cd1-4db1-40ea-8b22-6386e204c36b - xxx with txnId 36"
Reproduce code:
public class TransactionTest { private const string DatabaseName = "PressureTest"; private const string CollectionName = "Test"; public const string ConnectionString = ""; public MongoClient GetMongoClient(int timeout = 5) { var clientSettings = MongoClientSettings.FromConnectionString(ConnectionString); clientSettings.ConnectTimeout = TimeSpan.FromSeconds(5); clientSettings.ServerSelectionTimeout = TimeSpan.FromSeconds(timeout); clientSettings.AllowInsecureTls = true; var mongoClient = new MongoClient(clientSettings); return mongoClient; } public async Task TestTransactionAsync() { var client = GetMongoClient(); var tasks = new List<Task>(); for (int i = 0; i < 5; ++i) { //var client = GetMongoClient(i + 5); tasks.Add(DoAsync(client)); } await Task.WhenAll(tasks); } private async Task DoAsync(IMongoClient mongoClient) { Console.WriteLine("Client hashcode: " + mongoClient.GetHashCode()); var collection = mongoClient.GetDatabase(DatabaseName).GetCollection<BsonDocument>(CollectionName); while (true) { var uuid1 = Guid.NewGuid().ToString("N").Substring(24); var uuid2 = Guid.NewGuid().ToString("N").Substring(24); try { using (var session = await mongoClient.StartSessionAsync()) { session.StartTransaction(); await collection.InsertOneAsync(session, new BsonDocument("Uuid", uuid1)); await collection.InsertOneAsync(session, new BsonDocument("Uuid", uuid2)); await session.CommitTransactionAsync(); } Console.WriteLine($"[{uuid1}] [{uuid2}]"); } catch (Exception e) { Console.WriteLine("$$$ " + e.Message); } } } }
If change the thread to 1, no error happens.
If not reuse the mongoClient by changing TestTransactionAsync(): create a dedicated mongoClient for each thread, no error happens:
public async Task TestTransactionAsync() { var tasks = new List<Task>(); for (int i = 0; i < 5; ++i) { var client = GetMongoClient(i + 5); tasks.Add(DoAsync(client)); } await Task.WhenAll(tasks); }
The above modification intentionally passes different ServerSelectionTimeout value to prevent mongoclient from reusing. Refer to: https://mongodb.github.io/mongo-csharp-driver/2.11/reference/driver/connecting/#mongo-client
multiple MongoClient instances created with the same settings will utilize the same connection pools underneath.
The document suggests re-use mongoclient by store it in a global place. However, a singleton mongoclient leads to parallel transaction failure.