示例#1
0
        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();
        }
示例#2
0
        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);
                    }
                });
            }
        }
示例#3
0
        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();
            }
        }