public void ExecuteTransaction(IList <CachedObject> itemsToPut, IList <OrQuery> conditions, IList <CachedObject> itemsToDelete = null) { var locksOk = false; var iteration = 0; while (!locksOk) { var delay = ThreadLocalRandom.Instance.Next(10 * iteration); TransactionStatistics.Retries(iteration + 1); Dbg.Trace( $"C: delay = {delay} for iteration {iteration} single stage transaction connector {GetHashCode()}"); if (delay > 0) { Thread.Sleep(delay); } if (itemsToPut == null) { throw new ArgumentNullException(nameof(itemsToPut)); } var request = new TransactionRequest(itemsToPut, conditions, itemsToDelete) { IsSingleStage = true }; TransactionStatistics.ExecutedAsSingleStage(); var response = Channel.SendRequest(request); if (response is NullResponse) { locksOk = true; } else if (response is ExceptionResponse exResponse) { if (exResponse.ExceptionType != ExceptionType.FailedToAcquireLock) { throw new CacheException(exResponse.Message, exResponse.ExceptionType); } } } TransactionStatistics.NewTransactionCompleted(); }
public void ExecuteTransaction(IList <CachedObject> itemsToPut, IList <OrQuery> conditions, IList <CachedObject> itemsToDelete = null) { if (itemsToPut.Count != conditions.Count) { throw new ArgumentException($"{nameof(itemsToPut)} and {nameof(conditions)} do not have the same size"); } // the same connector will not execute transactions in parallel lock (_transactionSync) { // split the global transaction between servers var itemsByServer = new ConcurrentDictionary <int, TransactionRequest>(); var transactionId = TransactionRequest.GenerateId(); var index = 0; foreach (var item in itemsToPut) { var serverIndex = WhichNode(item); if (!itemsByServer.ContainsKey(serverIndex)) { itemsByServer.TryAdd(serverIndex, new TransactionRequest()); } itemsByServer[serverIndex].ItemsToPut.Add(item); itemsByServer[serverIndex].Conditions.Add(conditions[index]); itemsByServer[serverIndex].TransactionId = transactionId; index++; } if (itemsToDelete != null) { foreach (var item in itemsToDelete) { var serverIndex = WhichNode(item); if (!itemsByServer.ContainsKey(serverIndex)) { itemsByServer.TryAdd(serverIndex, new TransactionRequest()); } itemsByServer[serverIndex].ItemsToDelete.Add(item); index++; } } // Fallback to single stage if only one node is concerned if (itemsByServer.Count == 1) { var server = itemsByServer.Keys.Single(); CacheClients[server].ExecuteTransaction(itemsToPut, conditions, itemsToDelete); TransactionStatistics.ExecutedAsSingleStage(); return; } var sessions = new ConcurrentDictionary <int, Session>(); var serverStatus = new ConcurrentDictionary <int, bool>(); // select only the clients that are concerned var clients = CacheClients.Where(c => itemsByServer.ContainsKey(c.ShardIndex)).ToList(); // first stage : send the transaction request to the servers and wait for them to acquire write locks SendRequestsAndWaitForLock(clients, itemsByServer, sessions, transactionId, serverStatus); Dbg.Trace($"C: proceeding with first stage transaction {transactionId} "); var exType = ExceptionType.Unknown; // first stage: the durable transaction is written in the transaction log Parallel.ForEach(clients, client => { try { var session = sessions[client.ShardIndex]; var response = client.Channel.GetResponse(session); if (response is ReadyResponse) { serverStatus[client.ShardIndex] = true; } else { serverStatus[client.ShardIndex] = false; if (response is ExceptionResponse exceptionResponse) { exType = exceptionResponse.ExceptionType; } } } catch (Exception) { serverStatus[client.ShardIndex] = false; } }); // second stage: commit or rollback only the servers that processed successfully the first stage // (if a server answered with an exception response the transaction was already rolled back on this server) var firstStageOk = serverStatus.Values.All(s => s); if (firstStageOk) { // commit the transaction Dbg.Trace($"C: proceeding with second stage transaction {transactionId} "); Parallel.ForEach(clients, client => { var session = sessions[client.ShardIndex]; client.Channel.Continue(session, true); }); TransactionStatistics.NewTransactionCompleted(); } else { Dbg.Trace($"C: rollback first stage transaction {transactionId} "); Parallel.ForEach(clients, client => { // need to rollback only the clients that have executed the first stage if (serverStatus[client.ShardIndex]) { var session = sessions[client.ShardIndex]; client.Channel.Continue(session, false); } }); throw new CacheException( $"Error in two stage transaction. The transaction was successfully rolled back: {exType}", exType); } // close the session Parallel.ForEach(CacheClients, client => { if (itemsByServer.ContainsKey(client.ShardIndex)) { var session = sessions[client.ShardIndex]; client.Channel.EndSession(session); } }); } }
private void SendRequestsAndWaitForLock <TRequest>(List <CacheClient> clients, ConcurrentDictionary <int, TRequest> requestByServer, ConcurrentDictionary <int, Session> sessions, string transactionId, ConcurrentDictionary <int, bool> serverStatus) where TRequest : Request { var locksOk = false; var iteration = 0; while (!locksOk) { try { var delay = ThreadLocalRandom.Instance.Next(10 * iteration); TransactionStatistics.Retries(iteration + 1); Dbg.Trace( $"C: delay = {delay} for iteration {iteration} transaction {transactionId} connector {GetHashCode()}"); if (delay > 0) { Thread.Sleep(delay); } // send transaction requests Parallel.ForEach(clients, client => { var request = requestByServer[client.ShardIndex]; try { var session = client.Channel.BeginSession(); sessions[client.ShardIndex] = session; Dbg.Trace( $"C: Sending transaction request to server {client.ShardIndex} transaction {transactionId} connector {GetHashCode()}"); client.Channel.PushRequest(session, request); } catch (Exception e) { // here if communication exception serverStatus[client.ShardIndex] = false; Dbg.Trace($"C: Exception while sending request to server {client.ShardIndex}:{e.Message}"); } }); // wait for servers to acquire lock Parallel.ForEach(clients, client => { try { var session = sessions[client.ShardIndex]; var answer = client.Channel.GetResponse(session); if (answer is ReadyResponse) { serverStatus[client.ShardIndex] = true; } else { serverStatus[client.ShardIndex] = false; } } catch (Exception e) { // here if communication exception serverStatus[client.ShardIndex] = false; Dbg.Trace($"C: Exception while sending request to server {client.ShardIndex}:{e.Message}"); } }); } catch (AggregateException e) { // this code should never be reached throw new CacheException( $"Error in the first stage of a two stage transaction:{e.InnerExceptions.First()}"); } locksOk = serverStatus.Values.All(s => s); Dbg.Trace(!locksOk ? $"C: Failed to acquire lock for transaction {transactionId}. retrying " : $"C: Lock acquired for all servers: transaction {transactionId} "); if (locksOk == false) { Parallel.ForEach(clients, client => { if (serverStatus[client.ShardIndex]) { var session = sessions[client.ShardIndex]; client.Channel.PushRequest(session, new ContinueRequest { Rollback = true }); } }); } else { Parallel.ForEach(clients, client => { var session = sessions[client.ShardIndex]; client.Channel.PushRequest(session, new ContinueRequest { Rollback = false }); }); } iteration++; TransactionStatistics.NewAttemptToLock(); } }