public override async Task <AssignDeploymentsResponse> AssignDeployments(AssignDeploymentsRequest request,
                                                                                 ServerCallContext context)
        {
            try
            {
                using (var memClient = _matchmakingMemoryStoreClientManager.GetClient())
                {
                    var toUpdate = new List <Entry>();
                    foreach (var assignment in request.Assignments)
                    {
                        Reporter.AssignDeploymentInc(assignment.DeploymentId, assignment.Result);
                        foreach (var memberId in assignment.Party.MemberIds)
                        {
                            var playerJoinRequest = await memClient.GetAsync <PlayerJoinRequest>(memberId);

                            if (playerJoinRequest == null)
                            {
                                continue;
                            }
                            switch (assignment.Result)
                            {
                            case Assignment.Types.Result.Error:
                                playerJoinRequest.State = MatchState.Error;
                                break;

                            case Assignment.Types.Result.Matched:
                                playerJoinRequest.AssignMatch(assignment.DeploymentId, assignment.DeploymentName);
                                break;

                            case Assignment.Types.Result.Requeued:
                                playerJoinRequest.State = MatchState.Requested;
                                break;
                            }

                            toUpdate.Add(playerJoinRequest);
                        }
                    }

                    var toRequeue = new List <PartyJoinRequest>();
                    var toDelete  = new List <PartyJoinRequest>();

                    foreach (var assignment in request.Assignments)
                    {
                        var party            = assignment.Party;
                        var partyJoinRequest = await memClient.GetAsync <PartyJoinRequest>(party.Id);

                        if (partyJoinRequest == null)
                        {
                            // Party join request has been cancelled.
                            continue;
                        }

                        var eventAttributes = new Dictionary <string, string>
                        {
                            { "partyId", partyJoinRequest.Id },
                            { "matchRequestId", partyJoinRequest.MatchRequestId },
                            { "queueType", partyJoinRequest.Type },
                            { "partyPhase", partyJoinRequest.Party.CurrentPhase.ToString() }
                        };

                        if (assignment.Result == Assignment.Types.Result.Matched)
                        {
                            toDelete.Add(partyJoinRequest);

                            eventAttributes.Add("spatialProjectId", _project);
                            eventAttributes.Add("deploymentName", assignment.DeploymentName);
                            eventAttributes.Add("deploymentId", assignment.DeploymentId);
                            _analytics.Send("party_matched", eventAttributes, partyJoinRequest.Party.LeaderPlayerId);
                        }
                        else if (assignment.Result == Assignment.Types.Result.Requeued)
                        {
                            partyJoinRequest.RefreshQueueData();
                            toRequeue.Add(partyJoinRequest);
                            toUpdate.Add(partyJoinRequest);
                            _analytics.Send("party_requeued", eventAttributes, partyJoinRequest.Party.LeaderPlayerId);
                        }
                        else if (assignment.Result == Assignment.Types.Result.Error)
                        {
                            toDelete.Add(partyJoinRequest);
                            _analytics.Send("party_error", eventAttributes, partyJoinRequest.Party.LeaderPlayerId);
                        }
                        else
                        {
                            toDelete.Add(partyJoinRequest);
                        }
                    }

                    using (var tx = memClient.CreateTransaction())
                    {
                        tx.UpdateAll(toUpdate);
                        tx.EnqueueAll(toRequeue);
                        tx.DeleteAll(toDelete);
                    }

                    foreach (var playerJoinRequest in toUpdate.OfType <PlayerJoinRequest>())
                    {
                        var eventAttributes = new Dictionary <string, string>
                        {
                            { "partyId", playerJoinRequest.PartyId },
                            { "matchRequestId", playerJoinRequest.MatchRequestId },
                            { "queueType", playerJoinRequest.Type },
                            { "playerJoinRequestState", playerJoinRequest.State.ToString() }
                        };

                        switch (playerJoinRequest.State)
                        {
                        case MatchState.Matched:
                            eventAttributes.Add("spatialProjectId", _project);
                            eventAttributes.Add("deploymentName", playerJoinRequest.DeploymentName);
                            eventAttributes.Add("deploymentId", playerJoinRequest.DeploymentId);
                            _analytics.Send("player_matched", eventAttributes, playerJoinRequest.Id);
                            break;

                        case MatchState.Requested:
                            _analytics.Send("player_requeued", eventAttributes, playerJoinRequest.Id);
                            break;

                        case MatchState.Error:
                            _analytics.Send("player_error", eventAttributes, playerJoinRequest.Id);
                            break;
                        }
                    }
                }
            }
            catch (EntryNotFoundException e)
            {
                Reporter.AssignDeploymentNotFoundInc(e.Id);
                Log.Warning($"Attempted to assign deployment to nonexistent join request {e.Id}.");
                throw new RpcException(new Status(StatusCode.NotFound, "Join request does not exist"));
            }
            catch (TransactionAbortedException)
            {
                Reporter.TransactionAbortedInc("AssignDeployments");
                Log.Warning("Transaction aborted during deployment assignment.");
                throw new RpcException(new Status(StatusCode.Unavailable,
                                                  "assignment aborted due to concurrent modification; safe to retry"));
            }

            return(new AssignDeploymentsResponse());
        }
        public override async Task <PopWaitingPartiesResponse> PopWaitingParties(PopWaitingPartiesRequest request,
                                                                                 ServerCallContext context)
        {
            Reporter.GetWaitingPartiesInc(request.NumParties);
            if (request.NumParties == 0)
            {
                throw new RpcException(new Status(StatusCode.InvalidArgument, "must request at least one party"));
            }

            using (var memClient = _matchmakingMemoryStoreClientManager.GetClient())
            {
                try
                {
                    Task <IEnumerable <string> > dequeuedPartyIds;
                    using (var tx = memClient.CreateTransaction())
                    {
                        dequeuedPartyIds = tx.DequeueAsync(request.Type, request.NumParties);
                    }

                    dequeuedPartyIds.Wait();

                    // TODO investigate best approach to handling this error (leave as null, log warning, ignore?)
                    IEnumerable <PartyJoinRequest> partyJoinRequests;
                    try
                    {
                        partyJoinRequests = dequeuedPartyIds.Result
                                            .Select(async id =>
                                                    await memClient.GetAsync <PartyJoinRequest>(id) ?? throw new EntryNotFoundException(id))
                                            .Select(t => t.Result)
                                            .ToList();
                    }
                    catch (AggregateException ex)
                    {
                        throw ex.InnerException;
                    }

                    var playerJoinRequestsToUpdate = new List <PlayerJoinRequest>();
                    foreach (var partyJoinRequest in partyJoinRequests)
                    {
                        foreach (var(memberId, _) in partyJoinRequest.Party.MemberIdToPit)
                        {
                            var playerJoinRequest = await memClient.GetAsync <PlayerJoinRequest>(memberId) ??
                                                    throw new EntryNotFoundException(memberId);

                            playerJoinRequest.State = MatchState.Matching;
                            playerJoinRequestsToUpdate.Add(playerJoinRequest);
                        }
                    }

                    using (var tx = memClient.CreateTransaction())
                    {
                        tx.UpdateAll(playerJoinRequestsToUpdate);
                    }

                    var response = new PopWaitingPartiesResponse();
                    foreach (var partyJoinRequest in partyJoinRequests)
                    {
                        response.Parties.Add(ConvertToProto(partyJoinRequest));
                    }

                    return(response);
                }
                catch (EntryNotFoundException ex)
                {
                    // TODO: maybe add metrics for this.
                    throw new RpcException(new Status(StatusCode.Internal, $"could not find JoinRequest for {ex.Id}"));
                }
                catch (InsufficientEntriesException)
                {
                    Reporter.InsufficientWaitingPartiesInc(request.NumParties);
                    throw new RpcException(new Status(StatusCode.ResourceExhausted,
                                                      "requested number of parties players could not be met"));
                }
                catch (TransactionAbortedException)
                {
                    throw new RpcException(new Status(StatusCode.Unavailable,
                                                      "dequeue aborted due to concurrent modification; safe to retry"));
                }
            }
        }