-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #28 from castaneai/check-ticket-exists-after-match
backend: Check for existence before assigning tickets.
- Loading branch information
Showing
23 changed files
with
456 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package minimatch | ||
|
||
import ( | ||
"context" | ||
"log" | ||
"testing" | ||
|
||
"github.com/bojand/hri" | ||
"github.com/stretchr/testify/require" | ||
"open-match.dev/open-match/pkg/pb" | ||
|
||
"github.com/castaneai/minimatch/pkg/statestore" | ||
) | ||
|
||
func TestValidateTicketExistenceBeforeAssign(t *testing.T) { | ||
frontStore, backStore, _ := NewStateStoreWithMiniRedis(t) | ||
ctx := context.Background() | ||
|
||
t.Run("ValidationEnabled", func(t *testing.T) { | ||
backend, err := NewBackend(backStore, AssignerFunc(dummyAssign), WithTicketValidationBeforeAssign(true)) | ||
require.NoError(t, err) | ||
backend.AddMatchFunction(anyProfile, MatchFunctionSimple1vs1) | ||
|
||
err = frontStore.CreateTicket(ctx, &pb.Ticket{Id: "t1"}, defaultTicketTTL) | ||
require.NoError(t, err) | ||
err = frontStore.CreateTicket(ctx, &pb.Ticket{Id: "t2"}, defaultTicketTTL) | ||
require.NoError(t, err) | ||
|
||
activeTickets, err := backend.fetchActiveTickets(ctx, defaultFetchTicketsLimit) | ||
require.NoError(t, err) | ||
require.Len(t, activeTickets, 2) | ||
|
||
matches, err := backend.makeMatches(ctx, activeTickets) | ||
require.NoError(t, err) | ||
require.Len(t, matches, 1) | ||
|
||
// Delete "t1" after match is established | ||
err = frontStore.DeleteTicket(ctx, "t1") | ||
require.NoError(t, err) | ||
|
||
// If any ticket is lost in any part of a match, all matched tickets will not be assigned. | ||
err = backend.assign(ctx, matches) | ||
require.NoError(t, err) | ||
|
||
_, err = frontStore.GetAssignment(ctx, "t1") | ||
require.ErrorIs(t, err, statestore.ErrAssignmentNotFound) | ||
_, err = frontStore.GetAssignment(ctx, "t2") | ||
require.ErrorIs(t, err, statestore.ErrAssignmentNotFound) | ||
|
||
// Any remaining tickets for which no assignment is made will return to active again. | ||
activeTickets, err = backend.fetchActiveTickets(ctx, defaultFetchTicketsLimit) | ||
require.NoError(t, err) | ||
require.Len(t, activeTickets, 1) | ||
require.Equal(t, "t2", activeTickets[0].Id) | ||
}) | ||
|
||
t.Run("ValidationDisabled", func(t *testing.T) { | ||
backend, err := NewBackend(backStore, AssignerFunc(dummyAssign), WithTicketValidationBeforeAssign(false)) | ||
require.NoError(t, err) | ||
backend.AddMatchFunction(anyProfile, MatchFunctionSimple1vs1) | ||
|
||
err = frontStore.CreateTicket(ctx, &pb.Ticket{Id: "t3"}, defaultTicketTTL) | ||
require.NoError(t, err) | ||
err = frontStore.CreateTicket(ctx, &pb.Ticket{Id: "t4"}, defaultTicketTTL) | ||
require.NoError(t, err) | ||
|
||
activeTickets, err := backend.fetchActiveTickets(ctx, defaultFetchTicketsLimit) | ||
require.NoError(t, err) | ||
require.Len(t, activeTickets, 2) | ||
|
||
matches, err := backend.makeMatches(ctx, activeTickets) | ||
require.NoError(t, err) | ||
require.Len(t, matches, 1) | ||
|
||
// Delete "t3" after match is established | ||
err = frontStore.DeleteTicket(ctx, "t3") | ||
require.NoError(t, err) | ||
|
||
// Assignment is created even if the ticket disappears because the validation is invalid. | ||
err = backend.assign(ctx, matches) | ||
require.NoError(t, err) | ||
|
||
as1, err := frontStore.GetAssignment(ctx, "t3") | ||
require.NoError(t, err) | ||
require.NotNil(t, as1) | ||
as2, err := frontStore.GetAssignment(ctx, "t4") | ||
require.NoError(t, err) | ||
require.NotNil(t, as2) | ||
require.Equal(t, as1.Connection, as2.Connection) | ||
|
||
activeTickets, err = backend.fetchActiveTickets(ctx, defaultFetchTicketsLimit) | ||
require.NoError(t, err) | ||
require.Empty(t, activeTickets) | ||
}) | ||
} | ||
|
||
var anyProfile = &pb.MatchProfile{ | ||
Name: "test-profile", | ||
Pools: []*pb.Pool{ | ||
{Name: "test-pool"}, | ||
}, | ||
} | ||
|
||
func dummyAssign(ctx context.Context, matches []*pb.Match) ([]*pb.AssignmentGroup, error) { | ||
var asgs []*pb.AssignmentGroup | ||
for _, match := range matches { | ||
tids := ticketIDsFromMatch(match) | ||
conn := hri.Random() | ||
log.Printf("assign '%s' to tickets: %v", conn, tids) | ||
asgs = append(asgs, &pb.AssignmentGroup{ | ||
TicketIds: tids, | ||
Assignment: &pb.Assignment{Connection: conn}, | ||
}) | ||
} | ||
return asgs, nil | ||
} | ||
|
||
func ticketIDsFromMatch(match *pb.Match) []string { | ||
var ids []string | ||
for _, ticket := range match.Tickets { | ||
ids = append(ids, ticket.Id) | ||
} | ||
return ids | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Consistency and performance | ||
|
||
This document describes some of the consistency and performance points to consider with minimatch. | ||
|
||
In general, there is a trade-off between consistency and performance. As a distributed system over the Internet, we must accept a certain amount of inconsistency. | ||
|
||
## Invalid Assignment | ||
|
||
If some or all of the tickets in an Assignment are deleted, it becomes invalid. | ||
|
||
minimatch backend processes in the following order in one tick. | ||
|
||
1. fetch active tickets | ||
2. matchmaking | ||
3. allocating resources to established matches | ||
4. assigning the successful match to a ticket (creating an Assignment) | ||
5. the user retrieves the ticket's Assignment through minimatch Frontend | ||
|
||
If a ticket is deleted between 1 and 4 due to expiration or other request cancellation, an invalid Assignment will result. | ||
To prevent this, minimatch Backend checks the existence of the ticket again at step 4. | ||
|
||
**It is important to note that** even with this validation enabled, invalid Assignment cannot be completely prevented. | ||
For example, if a ticket is deleted during step 5, an invalid Assignment will still occur. | ||
Checking for the existence of tickets only reduces the possibility of invalid assignments, but does not prevent them completely. | ||
|
||
In addition, this validation has some impact on performance. | ||
If an invalid Assignment can be handled by the application and the ticket existence check is not needed, | ||
it can be disabled by `WithTicketValidationBeforeAssign(false)`. | ||
|
||
```go | ||
backend, err := minimatch.NewBackend(store, assigner, minimatch.WithTicketValidationBeforeAssign(false)) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.