From 2bf475cc1d1e69228a7feacaf22ceb93cf39ca7a Mon Sep 17 00:00:00 2001 From: castaneai Date: Tue, 27 Aug 2024 19:37:14 +0900 Subject: [PATCH] backend: Check for existence before assigning tickets. If a ticket has been deleted due to cancellation or expiration after makeMatches, the involved tickets should be released without creating an Assignment. --- README.md | 6 ++ backend.go | 96 ++++++++++++++++++---- backend_test.go | 124 +++++++++++++++++++++++++++++ docs/backend_timeline.png | Bin 46118 -> 45152 bytes docs/consistency.md | 32 ++++++++ docs/metrics.md | 2 +- docs/metrics.png | Bin 0 -> 46118 bytes frontend.go | 53 ++++++++++-- loadtest/README.md | 4 + loadtest/cmd/backend/main.go | 4 +- loadtest/cmd/frontend/main.go | 4 +- metrics.go | 2 +- minimatch.go | 30 +++---- pkg/statestore/backend.go | 16 ++++ pkg/statestore/errors.go | 8 ++ pkg/statestore/frontend.go | 15 ++++ pkg/statestore/redis.go | 26 +++--- pkg/statestore/redis_test.go | 36 +++++---- pkg/statestore/statestore.go | 27 ------- pkg/statestore/ticketcache.go | 57 ++++++++----- pkg/statestore/ticketcache_test.go | 23 +++--- testing.go | 27 ++++--- tests/intergration_test.go | 8 +- 23 files changed, 456 insertions(+), 144 deletions(-) create mode 100644 backend_test.go create mode 100644 docs/consistency.md create mode 100644 docs/metrics.png create mode 100644 pkg/statestore/backend.go create mode 100644 pkg/statestore/errors.go create mode 100644 pkg/statestore/frontend.go delete mode 100644 pkg/statestore/statestore.go diff --git a/README.md b/README.md index c1bdb7e..12fee5c 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,12 @@ See [Differences from Open Match](./docs/differences.md) for details. Is minimatch really just a mini? No, it is not! Despite its name, minimatch has scalability. Please see [Scalable minimatch](./docs/scalable.md). +## Consistency and performance + +Please see the following docs for consistency and performance to consider in minimatch. + +[Consistency and performance](./docs/consistency.md) + ## Metrics minimatch Backend exposes metrics in OpenTelemetry format to help monitor performance. diff --git a/backend.go b/backend.go index 8570b85..0ef7096 100644 --- a/backend.go +++ b/backend.go @@ -20,7 +20,7 @@ const ( ) type Backend struct { - store statestore.StateStore + store statestore.BackendStore mmfs map[*pb.MatchProfile]MatchFunction mmfMu sync.RWMutex assigner Assigner @@ -39,18 +39,20 @@ func (f BackendOptionFunc) apply(options *backendOptions) { } type backendOptions struct { - evaluator Evaluator - fetchTicketsLimit int64 - meterProvider metric.MeterProvider - logger *slog.Logger + evaluator Evaluator + fetchTicketsLimit int64 + meterProvider metric.MeterProvider + logger *slog.Logger + validateTicketsBeforeAssign bool } func defaultBackendOptions() *backendOptions { return &backendOptions{ - evaluator: nil, - fetchTicketsLimit: defaultFetchTicketsLimit, - meterProvider: otel.GetMeterProvider(), - logger: slog.Default(), + evaluator: nil, + fetchTicketsLimit: defaultFetchTicketsLimit, + meterProvider: otel.GetMeterProvider(), + logger: slog.Default(), + validateTicketsBeforeAssign: true, } } @@ -66,6 +68,8 @@ func WithBackendMeterProvider(provider metric.MeterProvider) BackendOption { }) } +// FetchTicketsLimit prevents OOM Kill by limiting the number of tickets retrieved at one time. +// The default is 10000. func WithFetchTicketsLimit(limit int64) BackendOption { return BackendOptionFunc(func(options *backendOptions) { options.fetchTicketsLimit = limit @@ -78,7 +82,15 @@ func WithBackendLogger(logger *slog.Logger) BackendOption { }) } -func NewBackend(store statestore.StateStore, assigner Assigner, opts ...BackendOption) (*Backend, error) { +// WithTicketValidationBeforeAssign specifies whether to enable to check for the existence of tickets before assigning them. +// See docs/consistency.md for details. +func WithTicketValidationBeforeAssign(enabled bool) BackendOption { + return BackendOptionFunc(func(options *backendOptions) { + options.validateTicketsBeforeAssign = enabled + }) +} + +func NewBackend(store statestore.BackendStore, assigner Assigner, opts ...BackendOption) (*Backend, error) { options := defaultBackendOptions() for _, opt := range opts { opt.apply(options) @@ -145,10 +157,6 @@ func (b *Backend) Tick(ctx context.Context) error { } if len(matches) > 0 { if err := b.assign(ctx, matches); err != nil { - unmatchedTicketIDs = ticketIDsFromMatches(matches) - if err := b.store.ReleaseTickets(ctx, unmatchedTicketIDs); err != nil { - return fmt.Errorf("failed to release unmatched tickets: %w", err) - } return err } } @@ -209,13 +217,32 @@ func (b *Backend) makeMatches(ctx context.Context, activeTickets []*pb.Ticket) ( } func (b *Backend) assign(ctx context.Context, matches []*pb.Match) error { + var ticketIDsToRelease []string + defer func() { + if len(ticketIDsToRelease) > 0 { + _ = b.store.ReleaseTickets(ctx, ticketIDsToRelease) + } + }() + asgs, err := b.assigner.Assign(ctx, matches) if err != nil { + ticketIDsToRelease = append(ticketIDsToRelease, ticketIDsFromMatches(matches)...) return fmt.Errorf("failed to assign matches: %w", err) } if len(asgs) > 0 { + if b.options.validateTicketsBeforeAssign { + filteredAsgs, notAssigned, err := b.validateTicketsBeforeAssign(ctx, asgs) + ticketIDsToRelease = append(ticketIDsToRelease, notAssigned...) + if err != nil { + return fmt.Errorf("failed to validate ticket before assign: %w", err) + } + asgs = filteredAsgs + } + start := time.Now() - if err := b.store.AssignTickets(ctx, asgs); err != nil { + notAssigned, err := b.store.AssignTickets(ctx, asgs) + ticketIDsToRelease = append(ticketIDsToRelease, notAssigned...) + if err != nil { return fmt.Errorf("failed to assign tickets: %w", err) } b.metrics.recordAssignToRedisLatency(ctx, time.Since(start)) @@ -223,6 +250,35 @@ func (b *Backend) assign(ctx context.Context, matches []*pb.Match) error { return nil } +func (b *Backend) validateTicketsBeforeAssign(ctx context.Context, asgs []*pb.AssignmentGroup) ([]*pb.AssignmentGroup, []string, error) { + allTicketIDs := ticketIDsFromAssignmentGroups(asgs) + tickets, err := b.store.GetTickets(ctx, allTicketIDs) + if err != nil { + return nil, nil, fmt.Errorf("failed to fetch tickets: %w", err) + } + existsMap := map[string]struct{}{} + for _, existingTicketID := range ticketIDsFromTickets(tickets) { + existsMap[existingTicketID] = struct{}{} + } + + validAsgs := make([]*pb.AssignmentGroup, 0, len(asgs)) + var notAssignedTicketIDs []string + for _, asg := range asgs { + isValidAsg := true + for _, ticketID := range asg.TicketIds { + if _, ok := existsMap[ticketID]; !ok { + isValidAsg = false + } + } + if isValidAsg { + validAsgs = append(validAsgs, asg) + } else { + notAssignedTicketIDs = append(notAssignedTicketIDs, asg.TicketIds...) + } + } + return validAsgs, notAssignedTicketIDs, nil +} + func evaluateMatches(ctx context.Context, evaluator Evaluator, matches []*pb.Match) ([]*pb.Match, error) { evaluatedMatches := make([]*pb.Match, 0, len(matches)) evaluatedMatchIDs, err := evaluator.Evaluate(ctx, matches) @@ -263,7 +319,7 @@ func filterTickets(profile *pb.MatchProfile, tickets []*pb.Ticket) (map[string][ func filterUnmatchedTicketIDs(allTickets []*pb.Ticket, matches []*pb.Match) []string { matchedTickets := map[string]struct{}{} for _, match := range matches { - for _, ticketID := range ticketIDs(match.Tickets) { + for _, ticketID := range ticketIDsFromTickets(match.Tickets) { matchedTickets[ticketID] = struct{}{} } } @@ -286,3 +342,11 @@ func ticketIDsFromMatches(matches []*pb.Match) []string { } return ticketIDs } + +func ticketIDsFromAssignmentGroups(asgs []*pb.AssignmentGroup) []string { + var ticketIDs []string + for _, asg := range asgs { + ticketIDs = append(ticketIDs, asg.TicketIds...) + } + return ticketIDs +} diff --git a/backend_test.go b/backend_test.go new file mode 100644 index 0000000..a492338 --- /dev/null +++ b/backend_test.go @@ -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 +} diff --git a/docs/backend_timeline.png b/docs/backend_timeline.png index 1f3a9e4fef49caa7222d798cc3fe746dec4a7187..193037f7ef4a437b41f4fc0f84e9401e10b72a1b 100644 GIT binary patch literal 45152 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
  • 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 literal 46118 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
    >CF!3x&HYr=G3N0N;MjiE5cb3RE# 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 B_Vxe( diff --git a/docs/consistency.md b/docs/consistency.md new file mode 100644 index 0000000..2511a7d --- /dev/null +++ b/docs/consistency.md @@ -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)) +``` diff --git a/docs/metrics.md b/docs/metrics.md index a7a7393..9e30d19 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -18,7 +18,7 @@ minimatch Backend exposes metrics in OpenTelemetry format to help monitor perfor The following timeline shows the process flow of minimatch Backend and the corresponding metrics. -![](./backend_timeline.png) +![](./metrics.png) ## Meter provider diff --git a/docs/metrics.png b/docs/metrics.png new file mode 100644 index 0000000000000000000000000000000000000000..1f3a9e4fef49caa7222d798cc3fe746dec4a7187 GIT binary patch literal 46118 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
    >CF!3x&HYr=G3N0N;MjiE5cb3RE# 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 B_Vxe( literal 0 HcmV?d00001 diff --git a/frontend.go b/frontend.go index e7bc84e..0154ef0 100644 --- a/frontend.go +++ b/frontend.go @@ -10,24 +10,60 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" "open-match.dev/open-match/pkg/pb" "github.com/castaneai/minimatch/pkg/statestore" ) const ( - watchAssignmentInterval = 100 * time.Millisecond + watchAssignmentInterval = 100 * time.Millisecond + defaultTicketTTL = 10 * time.Minute + persistentFieldKeyTicketTTL = "ttl" ) +type FrontendOption interface { + apply(options *frontendOptions) +} + +type FrontendOptionFunc func(options *frontendOptions) + +func (f FrontendOptionFunc) apply(options *frontendOptions) { + f(options) +} + +type frontendOptions struct { + ticketTTL time.Duration +} + +func defaultFrontendOptions() *frontendOptions { + return &frontendOptions{ + ticketTTL: defaultTicketTTL, + } +} + +func WithTicketTTL(ticketTTL time.Duration) FrontendOption { + return FrontendOptionFunc(func(options *frontendOptions) { + options.ticketTTL = ticketTTL + }) +} + type FrontendService struct { - store statestore.StateStore + store statestore.FrontendStore + options *frontendOptions } -func NewFrontendService(store statestore.StateStore) *FrontendService { +func NewFrontendService(store statestore.FrontendStore, opts ...FrontendOption) *FrontendService { + options := defaultFrontendOptions() + for _, opt := range opts { + opt.apply(options) + } return &FrontendService{ - store: store, + store: store, + options: options, } } @@ -38,7 +74,14 @@ func (s *FrontendService) CreateTicket(ctx context.Context, req *pb.CreateTicket } ticket.Id = xid.New().String() ticket.CreateTime = timestamppb.Now() - if err := s.store.CreateTicket(ctx, ticket); err != nil { + ttlVal, err := anypb.New(wrapperspb.Int64(s.options.ticketTTL.Nanoseconds())) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to create ttl value") + } + ticket.PersistentField = map[string]*anypb.Any{ + persistentFieldKeyTicketTTL: ttlVal, + } + if err := s.store.CreateTicket(ctx, ticket, s.options.ticketTTL); err != nil { return nil, err } return ticket, nil diff --git a/loadtest/README.md b/loadtest/README.md index 6aed220..f5172fb 100644 --- a/loadtest/README.md +++ b/loadtest/README.md @@ -19,6 +19,10 @@ export SKAFFOLD_DEFAULT_REPO=xxx-docker.pkg.dev/yyy # Start loadtest skaffold dev --tail=false +# Open Grafana to check the metrics. +# Log in as user: admin, password: test. +open http://127.0.0.1:8080/d/ea66819b-b3f1-4068-808e-4d1c650a46b2/minimatch-loadtest?orgId=1&refresh=5s + # Scale attacker vim skaffold.yaml ``` diff --git a/loadtest/cmd/backend/main.go b/loadtest/cmd/backend/main.go index 2e73883..71f34c7 100644 --- a/loadtest/cmd/backend/main.go +++ b/loadtest/cmd/backend/main.go @@ -64,7 +64,7 @@ func main() { log.Fatalf("failed to create redis store: %+v", err) } ticketCache := cache.New[string, *pb.Ticket]() - store := statestore.NewStoreWithTicketCache(redisStore, ticketCache, + store := statestore.NewBackendStoreWithTicketCache(redisStore, ticketCache, statestore.WithTicketCacheTTL(conf.TicketCacheTTL)) assigner, err := newAssigner(&conf, meterProvider) backend, err := minimatch.NewBackend(store, assigner) @@ -99,7 +99,7 @@ func newAssigner(conf *config, provider metric.MeterProvider) (minimatch.Assigne return assigner, nil } -func newRedisStateStore(conf *config) (statestore.StateStore, error) { +func newRedisStateStore(conf *config) (statestore.BackendStore, error) { copt := rueidis.ClientOption{ InitAddress: []string{conf.RedisAddr}, DisableCache: true, diff --git a/loadtest/cmd/frontend/main.go b/loadtest/cmd/frontend/main.go index e56194c..92ffdc1 100644 --- a/loadtest/cmd/frontend/main.go +++ b/loadtest/cmd/frontend/main.go @@ -46,7 +46,7 @@ func main() { log.Fatalf("failed to create redis store: %+v", err) } ticketCache := cache.New[string, *pb.Ticket]() - store := statestore.NewStoreWithTicketCache(redisStore, ticketCache, + store := statestore.NewFrontendStoreWithTicketCache(redisStore, ticketCache, statestore.WithTicketCacheTTL(conf.TicketCacheTTL)) sv := grpc.NewServer() @@ -63,7 +63,7 @@ func main() { } } -func newRedisStateStore(conf *config) (statestore.StateStore, error) { +func newRedisStateStore(conf *config) (statestore.FrontendStore, error) { copt := rueidis.ClientOption{ InitAddress: []string{conf.RedisAddr}, DisableCache: true, diff --git a/metrics.go b/metrics.go index 91878b8..bf71f79 100644 --- a/metrics.go +++ b/metrics.go @@ -34,7 +34,7 @@ type backendMetrics struct { assignToRedisLatency metric.Float64Histogram } -func newBackendMetrics(provider metric.MeterProvider, store statestore.StateStore) (*backendMetrics, error) { +func newBackendMetrics(provider metric.MeterProvider, store statestore.BackendStore) (*backendMetrics, error) { meter := provider.Meter(metricsScopeName) ticketsFetched, err := meter.Int64Counter("minimatch.backend.tickets_fetched") if err != nil { diff --git a/minimatch.go b/minimatch.go index 1b2499e..546884f 100644 --- a/minimatch.go +++ b/minimatch.go @@ -17,10 +17,11 @@ import ( ) type MiniMatch struct { - store statestore.StateStore - mmfs map[*pb.MatchProfile]MatchFunction - backend *Backend - mu sync.RWMutex + frontendStore statestore.FrontendStore + backendStore statestore.BackendStore + mmfs map[*pb.MatchProfile]MatchFunction + backend *Backend + mu sync.RWMutex } func NewMiniMatchWithRedis(opts ...statestore.RedisOption) (*MiniMatch, error) { @@ -40,14 +41,15 @@ func NewMiniMatchWithRedis(opts ...statestore.RedisOption) (*MiniMatch, error) { return nil, fmt.Errorf("failed to new rueidis locker: %w", err) } store := statestore.NewRedisStore(rc, locker, opts...) - return NewMiniMatch(store), nil + return NewMiniMatch(store, store), nil } -func NewMiniMatch(store statestore.StateStore) *MiniMatch { +func NewMiniMatch(frontendStore statestore.FrontendStore, backendStore statestore.BackendStore) *MiniMatch { return &MiniMatch{ - store: store, - mmfs: map[*pb.MatchProfile]MatchFunction{}, - mu: sync.RWMutex{}, + frontendStore: frontendStore, + backendStore: backendStore, + mmfs: map[*pb.MatchProfile]MatchFunction{}, + mu: sync.RWMutex{}, } } @@ -56,7 +58,7 @@ func (m *MiniMatch) AddMatchFunction(profile *pb.MatchProfile, mmf MatchFunction } func (m *MiniMatch) FrontendService() pb.FrontendServiceServer { - return NewFrontendService(m.store) + return NewFrontendService(m.frontendStore) } func (m *MiniMatch) StartFrontend(listenAddr string) error { @@ -70,7 +72,7 @@ func (m *MiniMatch) StartFrontend(listenAddr string) error { } func (m *MiniMatch) StartBackend(ctx context.Context, assigner Assigner, tickRate time.Duration, opts ...BackendOption) error { - backend, err := NewBackend(m.store, assigner, opts...) + backend, err := NewBackend(m.backendStore, assigner, opts...) if err != nil { return fmt.Errorf("failed to create minimatch backend: %w", err) } @@ -109,15 +111,15 @@ var MatchFunctionSimple1vs1 = MatchFunctionFunc(func(ctx context.Context, profil func newMatch(profile *pb.MatchProfile, tickets []*pb.Ticket) *pb.Match { return &pb.Match{ - MatchId: fmt.Sprintf("%s_%v", profile.Name, ticketIDs(tickets)), + MatchId: fmt.Sprintf("%s_%v", profile.Name, ticketIDsFromTickets(tickets)), MatchProfile: profile.Name, MatchFunction: "Simple1vs1", Tickets: tickets, } } -func ticketIDs(tickets []*pb.Ticket) []string { - var ids []string +func ticketIDsFromTickets(tickets []*pb.Ticket) []string { + ids := make([]string, 0, len(tickets)) for _, ticket := range tickets { ids = append(ids, ticket.Id) } diff --git a/pkg/statestore/backend.go b/pkg/statestore/backend.go new file mode 100644 index 0000000..b4c4029 --- /dev/null +++ b/pkg/statestore/backend.go @@ -0,0 +1,16 @@ +package statestore + +import ( + "context" + + "open-match.dev/open-match/pkg/pb" +) + +type BackendStore interface { + GetTickets(ctx context.Context, ticketIDs []string) ([]*pb.Ticket, error) + GetActiveTicketIDs(ctx context.Context, limit int64) ([]string, error) + GetTicketCount(ctx context.Context) (int64, error) + ReleaseTickets(ctx context.Context, ticketIDs []string) error + // AssignTickets Returns a list of ticket IDs that have failed assignments; you will need to check that list when err occurs. + AssignTickets(ctx context.Context, asgs []*pb.AssignmentGroup) ([]string, error) +} diff --git a/pkg/statestore/errors.go b/pkg/statestore/errors.go new file mode 100644 index 0000000..b8e951d --- /dev/null +++ b/pkg/statestore/errors.go @@ -0,0 +1,8 @@ +package statestore + +import "errors" + +var ( + ErrTicketNotFound = errors.New("ticket not found") + ErrAssignmentNotFound = errors.New("assignment not found") +) diff --git a/pkg/statestore/frontend.go b/pkg/statestore/frontend.go new file mode 100644 index 0000000..924d800 --- /dev/null +++ b/pkg/statestore/frontend.go @@ -0,0 +1,15 @@ +package statestore + +import ( + "context" + "time" + + "open-match.dev/open-match/pkg/pb" +) + +type FrontendStore interface { + CreateTicket(ctx context.Context, ticket *pb.Ticket, ttl time.Duration) error + DeleteTicket(ctx context.Context, ticketID string) error + GetTicket(ctx context.Context, ticketID string) (*pb.Ticket, error) + GetAssignment(ctx context.Context, ticketID string) (*pb.Assignment, error) +} diff --git a/pkg/statestore/redis.go b/pkg/statestore/redis.go index 04901bf..9192144 100644 --- a/pkg/statestore/redis.go +++ b/pkg/statestore/redis.go @@ -14,7 +14,6 @@ import ( ) const ( - defaultTicketTTL = 10 * time.Minute defaultPendingReleaseTimeout = 1 * time.Minute defaultAssignedDeleteTimeout = 1 * time.Minute ) @@ -26,7 +25,6 @@ type RedisStore struct { } type redisOpts struct { - ticketTTL time.Duration pendingReleaseTimeout time.Duration assignedDeleteTimeout time.Duration // common key prefix in redis @@ -39,7 +37,6 @@ type redisOpts struct { func defaultRedisOpts() *redisOpts { return &redisOpts{ - ticketTTL: defaultTicketTTL, pendingReleaseTimeout: defaultPendingReleaseTimeout, assignedDeleteTimeout: defaultAssignedDeleteTimeout, keyPrefix: "", @@ -58,12 +55,6 @@ func (f RedisOptionFunc) apply(opts *redisOpts) { f(opts) } -func WithTicketTTL(ticketTTL time.Duration) RedisOption { - return RedisOptionFunc(func(opts *redisOpts) { - opts.ticketTTL = ticketTTL - }) -} - func WithPendingReleaseTimeout(pendingReleaseTimeout time.Duration) RedisOption { return RedisOptionFunc(func(opts *redisOpts) { opts.pendingReleaseTimeout = pendingReleaseTimeout @@ -106,7 +97,7 @@ func NewRedisStore(client rueidis.Client, locker rueidislock.Locker, opts ...Red } } -func (s *RedisStore) CreateTicket(ctx context.Context, ticket *pb.Ticket) error { +func (s *RedisStore) CreateTicket(ctx context.Context, ticket *pb.Ticket, ttl time.Duration) error { data, err := encodeTicket(ticket) if err != nil { return err @@ -115,7 +106,7 @@ func (s *RedisStore) CreateTicket(ctx context.Context, ticket *pb.Ticket) error s.client.B().Set(). Key(redisKeyTicketData(s.opts.keyPrefix, ticket.Id)). Value(rueidis.BinaryString(data)). - Ex(s.opts.ticketTTL). + Ex(ttl). Build(), s.client.B().Sadd(). Key(redisKeyTicketIndex(s.opts.keyPrefix)). @@ -292,8 +283,8 @@ func (s *RedisStore) ReleaseTickets(ctx context.Context, ticketIDs []string) err return nil } -func (s *RedisStore) AssignTickets(ctx context.Context, asgs []*pb.AssignmentGroup) error { - var assignedTicketIDs []string +func (s *RedisStore) AssignTickets(ctx context.Context, asgs []*pb.AssignmentGroup) ([]string, error) { + var assignedTicketIDs, notAssignedTicketIDs []string for _, asg := range asgs { if len(asg.TicketIds) == 0 { continue @@ -304,20 +295,21 @@ func (s *RedisStore) AssignTickets(ctx context.Context, asgs []*pb.AssignmentGro redis = s.opts.assignmentSpaceClient } if err := s.setAssignmentToTickets(ctx, redis, asg.TicketIds, asg.Assignment); err != nil { - return err + notAssignedTicketIDs = append(notAssignedTicketIDs, asg.TicketIds...) + return notAssignedTicketIDs, err } assignedTicketIDs = append(assignedTicketIDs, asg.TicketIds...) } if len(assignedTicketIDs) > 0 { // de-index assigned tickets if err := s.deIndexTickets(ctx, assignedTicketIDs); err != nil { - return fmt.Errorf("failed to deindex assigned tickets: %w", err) + return notAssignedTicketIDs, fmt.Errorf("failed to deindex assigned tickets: %w", err) } if err := s.setTicketsExpiration(ctx, assignedTicketIDs, s.opts.assignedDeleteTimeout); err != nil { - return err + return notAssignedTicketIDs, err } } - return nil + return notAssignedTicketIDs, nil } func (s *RedisStore) GetTicketCount(ctx context.Context) (int64, error) { diff --git a/pkg/statestore/redis_test.go b/pkg/statestore/redis_test.go index 331ee61..0267fd3 100644 --- a/pkg/statestore/redis_test.go +++ b/pkg/statestore/redis_test.go @@ -19,6 +19,7 @@ import ( const ( defaultGetTicketLimit = 10000 + defaultTicketTTL = 10 * time.Minute ) func newTestRedisStore(t *testing.T, addr string, opts ...RedisOption) *RedisStore { @@ -44,8 +45,8 @@ func TestPendingRelease(t *testing.T) { store := newTestRedisStore(t, mr.Addr()) ctx := context.Background() - require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "test1"})) - require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "test2"})) + require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "test1"}, defaultTicketTTL)) + require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "test2"}, defaultTicketTTL)) activeTicketIDs, err := store.GetActiveTicketIDs(ctx, defaultGetTicketLimit) require.NoError(t, err) require.ElementsMatch(t, activeTicketIDs, []string{"test1", "test2"}) @@ -69,7 +70,7 @@ func TestPendingReleaseTimeout(t *testing.T) { ctx := context.Background() // 1 active ticket - require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "test"})) + require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "test"}, defaultTicketTTL)) // get active tickets for proposal (active -> pending) activeTicketIDs, err := store.GetActiveTicketIDs(ctx, defaultGetTicketLimit) @@ -96,8 +97,8 @@ func TestAssignedDeleteTimeout(t *testing.T) { store := newTestRedisStore(t, mr.Addr()) ctx := context.Background() - require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "test1"})) - require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "test2"})) + require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "test1"}, defaultTicketTTL)) + require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "test2"}, defaultTicketTTL)) activeTicketIDs, err := store.GetActiveTicketIDs(ctx, defaultGetTicketLimit) require.NoError(t, err) require.ElementsMatch(t, activeTicketIDs, []string{"test1", "test2"}) @@ -108,9 +109,10 @@ func TestAssignedDeleteTimeout(t *testing.T) { require.Error(t, err, ErrAssignmentNotFound) as := &pb.Assignment{Connection: "test-assign"} - require.NoError(t, store.AssignTickets(ctx, []*pb.AssignmentGroup{ + _, err = store.AssignTickets(ctx, []*pb.AssignmentGroup{ {TicketIds: []string{"test1", "test2"}, Assignment: as}, - })) + }) + require.NoError(t, err) for i := 0; i < 3; i++ { as1, err := store.GetAssignment(ctx, "test1") require.NoError(t, err) @@ -138,11 +140,11 @@ func TestAssignedDeleteTimeout(t *testing.T) { func TestTicketTTL(t *testing.T) { mr := miniredis.RunT(t) ticketTTL := 5 * time.Second - store := newTestRedisStore(t, mr.Addr(), WithTicketTTL(ticketTTL)) + store := newTestRedisStore(t, mr.Addr()) ctx := context.Background() mustCreateTicket := func(id string) { - require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: id})) + require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: id}, ticketTTL)) ticket, err := store.GetTicket(ctx, id) require.NoError(t, err) require.Equal(t, id, ticket.Id) @@ -191,7 +193,7 @@ func TestConcurrentFetchActiveTickets(t *testing.T) { ticketCount := 1000 concurrency := 1000 for i := 0; i < ticketCount; i++ { - require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: xid.New().String()})) + require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: xid.New().String()}, defaultTicketTTL)) } eg, _ := errgroup.WithContext(ctx) @@ -228,7 +230,7 @@ func TestConcurrentFetchAndAssign(t *testing.T) { concurrency := 1000 for i := 0; i < ticketCount; i++ { ticket := &pb.Ticket{Id: xid.New().String()} - require.NoError(t, store.CreateTicket(ctx, ticket)) + require.NoError(t, store.CreateTicket(ctx, ticket, defaultTicketTTL)) } var mu sync.Mutex @@ -258,7 +260,7 @@ func TestConcurrentFetchAndAssign(t *testing.T) { mu.Unlock() } } - if err := store.AssignTickets(ctx, asgs); err != nil { + if _, err := store.AssignTickets(ctx, asgs); err != nil { return err } return nil @@ -275,21 +277,21 @@ func TestReadReplica(t *testing.T) { store := newTestRedisStore(t, mr.Addr(), WithRedisReadReplicaClient(newRedisClient(t, readReplica.Addr()))) replicaStore := newTestRedisStore(t, readReplica.Addr()) - require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "t1"})) + require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "t1"}, defaultTicketTTL)) t1, err := store.GetTicket(ctx, "t1") require.NoError(t, err) require.Equal(t, "t1", t1.Id) // emulate replication - require.NoError(t, replicaStore.CreateTicket(ctx, &pb.Ticket{Id: "t1"})) + require.NoError(t, replicaStore.CreateTicket(ctx, &pb.Ticket{Id: "t1"}, defaultTicketTTL)) t1, err = store.GetTicket(ctx, "t1") require.NoError(t, err) require.Equal(t, "t1", t1.Id) // t2 is replicated but have different params - require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "t2", SearchFields: &pb.SearchFields{Tags: []string{"primary"}}})) - require.NoError(t, replicaStore.CreateTicket(ctx, &pb.Ticket{Id: "t2", SearchFields: &pb.SearchFields{Tags: []string{"replica"}}})) + require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "t2", SearchFields: &pb.SearchFields{Tags: []string{"primary"}}}, defaultTicketTTL)) + require.NoError(t, replicaStore.CreateTicket(ctx, &pb.Ticket{Id: "t2", SearchFields: &pb.SearchFields{Tags: []string{"replica"}}}, defaultTicketTTL)) // t3 is not replicated - require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "t3"})) + require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "t3"}, defaultTicketTTL)) tickets, err := replicaStore.GetTickets(ctx, []string{"t1", "t2", "t3"}) require.NoError(t, err) diff --git a/pkg/statestore/statestore.go b/pkg/statestore/statestore.go deleted file mode 100644 index ee8ebb9..0000000 --- a/pkg/statestore/statestore.go +++ /dev/null @@ -1,27 +0,0 @@ -package statestore - -import ( - "context" - "errors" - - "open-match.dev/open-match/pkg/pb" -) - -var ( - ErrTicketNotFound = errors.New("ticket not found") - ErrAssignmentNotFound = errors.New("assignment not found") -) - -type StateStore interface { - CreateTicket(ctx context.Context, ticket *pb.Ticket) error - DeleteTicket(ctx context.Context, ticketID string) error - // GetTicket is an API to retrieve the status of a single ticket and is called from Frontend. - GetTicket(ctx context.Context, ticketID string) (*pb.Ticket, error) - // GetTickets on the other hand, retrieves multiple tickets at once and is intended to be called from Backend. - GetTickets(ctx context.Context, ticketIDs []string) ([]*pb.Ticket, error) - GetAssignment(ctx context.Context, ticketID string) (*pb.Assignment, error) - GetActiveTicketIDs(ctx context.Context, limit int64) ([]string, error) - GetTicketCount(ctx context.Context) (int64, error) - ReleaseTickets(ctx context.Context, ticketIDs []string) error - AssignTickets(ctx context.Context, asgs []*pb.AssignmentGroup) error -} diff --git a/pkg/statestore/ticketcache.go b/pkg/statestore/ticketcache.go index 016f057..6e2733e 100644 --- a/pkg/statestore/ticketcache.go +++ b/pkg/statestore/ticketcache.go @@ -28,41 +28,43 @@ func (f ticketCacheOptionFunc) apply(options *ticketCacheOptions) { f(options) } +// WithTicketCacheTTL specifies the time to cache ticket data in-memory. +// The default is 10 seconds. func WithTicketCacheTTL(ttl time.Duration) TicketCacheOption { return ticketCacheOptionFunc(func(options *ticketCacheOptions) { options.ttl = ttl }) } -// StoreWithTicketCache caches GetTicket results in-memory with TTL -type StoreWithTicketCache struct { - origin StateStore +// FrontendStoreWithTicketCache caches GetTicket results in-memory with TTL +type FrontendStoreWithTicketCache struct { + origin FrontendStore ticketCache *cache.Cache[string, *pb.Ticket] options *ticketCacheOptions } -func NewStoreWithTicketCache(origin StateStore, ticketCache *cache.Cache[string, *pb.Ticket], opts ...TicketCacheOption) *StoreWithTicketCache { +func NewFrontendStoreWithTicketCache(origin FrontendStore, ticketCache *cache.Cache[string, *pb.Ticket], opts ...TicketCacheOption) *FrontendStoreWithTicketCache { options := defaultTicketCacheOptions() for _, o := range opts { o.apply(options) } - return &StoreWithTicketCache{ + return &FrontendStoreWithTicketCache{ origin: origin, ticketCache: ticketCache, options: options, } } -func (s *StoreWithTicketCache) CreateTicket(ctx context.Context, ticket *pb.Ticket) error { - return s.origin.CreateTicket(ctx, ticket) +func (s *FrontendStoreWithTicketCache) CreateTicket(ctx context.Context, ticket *pb.Ticket, ttl time.Duration) error { + return s.origin.CreateTicket(ctx, ticket, ttl) } -func (s *StoreWithTicketCache) DeleteTicket(ctx context.Context, ticketID string) error { +func (s *FrontendStoreWithTicketCache) DeleteTicket(ctx context.Context, ticketID string) error { s.ticketCache.Delete(ticketID) return s.origin.DeleteTicket(ctx, ticketID) } -func (s *StoreWithTicketCache) GetTicket(ctx context.Context, ticketID string) (*pb.Ticket, error) { +func (s *FrontendStoreWithTicketCache) GetTicket(ctx context.Context, ticketID string) (*pb.Ticket, error) { if ticket, hit := s.ticketCache.Get(ticketID); hit { return ticket, nil } @@ -74,7 +76,30 @@ func (s *StoreWithTicketCache) GetTicket(ctx context.Context, ticketID string) ( return ticket, nil } -func (s *StoreWithTicketCache) GetTickets(ctx context.Context, ticketIDs []string) ([]*pb.Ticket, error) { +func (s *FrontendStoreWithTicketCache) GetAssignment(ctx context.Context, ticketID string) (*pb.Assignment, error) { + return s.origin.GetAssignment(ctx, ticketID) +} + +// BackendStoreWithTicketCache caches GetTickets results in-memory with TTL +type BackendStoreWithTicketCache struct { + origin BackendStore + ticketCache *cache.Cache[string, *pb.Ticket] + options *ticketCacheOptions +} + +func NewBackendStoreWithTicketCache(origin BackendStore, ticketCache *cache.Cache[string, *pb.Ticket], opts ...TicketCacheOption) *BackendStoreWithTicketCache { + options := defaultTicketCacheOptions() + for _, o := range opts { + o.apply(options) + } + return &BackendStoreWithTicketCache{ + origin: origin, + ticketCache: ticketCache, + options: options, + } +} + +func (s *BackendStoreWithTicketCache) GetTickets(ctx context.Context, ticketIDs []string) ([]*pb.Ticket, error) { var noCachedTicketIDs []string tickets := make([]*pb.Ticket, 0, len(ticketIDs)) for _, ticketID := range ticketIDs { @@ -97,22 +122,18 @@ func (s *StoreWithTicketCache) GetTickets(ctx context.Context, ticketIDs []strin return tickets, nil } -func (s *StoreWithTicketCache) GetAssignment(ctx context.Context, ticketID string) (*pb.Assignment, error) { - return s.origin.GetAssignment(ctx, ticketID) -} - -func (s *StoreWithTicketCache) GetActiveTicketIDs(ctx context.Context, limit int64) ([]string, error) { +func (s *BackendStoreWithTicketCache) GetActiveTicketIDs(ctx context.Context, limit int64) ([]string, error) { return s.origin.GetActiveTicketIDs(ctx, limit) } -func (s *StoreWithTicketCache) ReleaseTickets(ctx context.Context, ticketIDs []string) error { +func (s *BackendStoreWithTicketCache) ReleaseTickets(ctx context.Context, ticketIDs []string) error { return s.origin.ReleaseTickets(ctx, ticketIDs) } -func (s *StoreWithTicketCache) AssignTickets(ctx context.Context, asgs []*pb.AssignmentGroup) error { +func (s *BackendStoreWithTicketCache) AssignTickets(ctx context.Context, asgs []*pb.AssignmentGroup) ([]string, error) { return s.origin.AssignTickets(ctx, asgs) } -func (s *StoreWithTicketCache) GetTicketCount(ctx context.Context) (int64, error) { +func (s *BackendStoreWithTicketCache) GetTicketCount(ctx context.Context) (int64, error) { return s.origin.GetTicketCount(ctx) } diff --git a/pkg/statestore/ticketcache_test.go b/pkg/statestore/ticketcache_test.go index c6786dc..fc4ff05 100644 --- a/pkg/statestore/ticketcache_test.go +++ b/pkg/statestore/ticketcache_test.go @@ -16,24 +16,25 @@ func TestTicketCache(t *testing.T) { ticketCache := cache.New[string, *pb.Ticket]() ttl := 500 * time.Millisecond redisStore := newTestRedisStore(t, mr.Addr()) - store := NewStoreWithTicketCache(redisStore, ticketCache, WithTicketCacheTTL(ttl)) + frontendStore := NewFrontendStoreWithTicketCache(redisStore, ticketCache, WithTicketCacheTTL(ttl)) + backendStore := NewBackendStoreWithTicketCache(redisStore, ticketCache, WithTicketCacheTTL(ttl)) ctx := context.Background() - require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "t1"})) - t1, err := store.GetTicket(ctx, "t1") + require.NoError(t, frontendStore.CreateTicket(ctx, &pb.Ticket{Id: "t1"}, defaultTicketTTL)) + t1, err := frontendStore.GetTicket(ctx, "t1") require.NoError(t, err) require.Equal(t, "t1", t1.Id) require.NoError(t, redisStore.DeleteTicket(ctx, "t1")) // it can be retrieved from the cache even if deleted - t1, err = store.GetTicket(ctx, "t1") + t1, err = frontendStore.GetTicket(ctx, "t1") require.NoError(t, err) require.Equal(t, "t1", t1.Id) time.Sleep(ttl + 10*time.Millisecond) - _, err = store.GetTicket(ctx, "t1") + _, err = frontendStore.GetTicket(ctx, "t1") require.Error(t, err, ErrTicketNotFound) getTicketIDs := func(l []*pb.Ticket) []string { @@ -44,10 +45,10 @@ func TestTicketCache(t *testing.T) { return tids } - require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "t2"})) - require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "t3"})) - require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "t4"})) - ts, err := store.GetTickets(ctx, []string{"t2", "t3", "t4", "t5"}) + require.NoError(t, frontendStore.CreateTicket(ctx, &pb.Ticket{Id: "t2"}, defaultTicketTTL)) + require.NoError(t, frontendStore.CreateTicket(ctx, &pb.Ticket{Id: "t3"}, defaultTicketTTL)) + require.NoError(t, frontendStore.CreateTicket(ctx, &pb.Ticket{Id: "t4"}, defaultTicketTTL)) + ts, err := backendStore.GetTickets(ctx, []string{"t2", "t3", "t4", "t5"}) require.NoError(t, err) require.ElementsMatch(t, []string{"t2", "t3", "t4"}, getTicketIDs(ts)) @@ -55,14 +56,14 @@ func TestTicketCache(t *testing.T) { require.NoError(t, redisStore.DeleteTicket(ctx, "t3")) // "t3" is still in cache - ts, err = store.GetTickets(ctx, []string{"t2", "t3", "t4"}) + ts, err = backendStore.GetTickets(ctx, []string{"t2", "t3", "t4"}) require.NoError(t, err) require.ElementsMatch(t, []string{"t2", "t3", "t4"}, getTicketIDs(ts)) // expires "t3" cache time.Sleep(ttl + 10*time.Millisecond) - ts, err = store.GetTickets(ctx, []string{"t2", "t3", "t4"}) + ts, err = backendStore.GetTickets(ctx, []string{"t2", "t3", "t4"}) require.NoError(t, err) require.ElementsMatch(t, []string{"t2", "t4"}, getTicketIDs(ts)) } diff --git a/testing.go b/testing.go index 2800a6a..edc0cda 100644 --- a/testing.go +++ b/testing.go @@ -44,6 +44,12 @@ func WithTestServerBackendTick(tick time.Duration) TestServerOption { }) } +func WithTestServerFrontendOptions(frontendOptions ...FrontendOption) TestServerOption { + return TestServerOptionFunc(func(opts *testServerOptions) { + opts.frontendOptions = frontendOptions + }) +} + func WithTestServerBackendOptions(backendOptions ...BackendOption) TestServerOption { return TestServerOptionFunc(func(opts *testServerOptions) { opts.backendOptions = backendOptions @@ -51,15 +57,17 @@ func WithTestServerBackendOptions(backendOptions ...BackendOption) TestServerOpt } type testServerOptions struct { + frontendListenAddr string + frontendOptions []FrontendOption backendTick time.Duration backendOptions []BackendOption - frontendListenAddr string } func defaultTestServerOpts() *testServerOptions { return &testServerOptions{ - backendTick: 1 * time.Second, frontendListenAddr: "127.0.0.1:0", // random port + frontendOptions: nil, + backendTick: 1 * time.Second, backendOptions: nil, } } @@ -94,7 +102,7 @@ func (ts *TestFrontendServer) Stop() { ts.sv.Stop() } -func NewTestFrontendServer(t *testing.T, store statestore.StateStore, addr string) *TestFrontendServer { +func NewTestFrontendServer(t *testing.T, store statestore.FrontendStore, addr string, opts ...FrontendOption) *TestFrontendServer { // start frontend lis, err := net.Listen("tcp", addr) if err != nil { @@ -102,7 +110,7 @@ func NewTestFrontendServer(t *testing.T, store statestore.StateStore, addr strin } t.Cleanup(func() { _ = lis.Close() }) sv := grpc.NewServer() - pb.RegisterFrontendServiceServer(sv, NewFrontendService(store)) + pb.RegisterFrontendServiceServer(sv, NewFrontendService(store, opts...)) ts := &TestFrontendServer{ sv: sv, lis: lis, @@ -118,13 +126,13 @@ func RunTestServer(t *testing.T, matchFunctions map[*pb.MatchProfile]MatchFuncti for _, o := range opts { o.apply(options) } - store, _ := NewStateStoreWithMiniRedis(t) - mm := NewMiniMatch(store) + front, back, _ := NewStateStoreWithMiniRedis(t) + mm := NewMiniMatch(front, back) for profile, mmf := range matchFunctions { mm.AddMatchFunction(profile, mmf) } - frontend := NewTestFrontendServer(t, store, options.frontendListenAddr) + frontend := NewTestFrontendServer(t, front, options.frontendListenAddr) ts := &TestServer{mm: mm, frontend: frontend, options: options} // start backend @@ -194,7 +202,7 @@ func waitForTCPServerReady(t *testing.T, addr string, timeout time.Duration) { } } -func NewStateStoreWithMiniRedis(t *testing.T) (statestore.StateStore, *miniredis.Miniredis) { +func NewStateStoreWithMiniRedis(t *testing.T) (statestore.FrontendStore, statestore.BackendStore, *miniredis.Miniredis) { mr := miniredis.RunT(t) copt := rueidis.ClientOption{InitAddress: []string{mr.Addr()}, DisableCache: true} redis, err := rueidis.NewClient(copt) @@ -207,5 +215,6 @@ func NewStateStoreWithMiniRedis(t *testing.T) (statestore.StateStore, *miniredis if err != nil { t.Fatalf("failed to create rueidis locker: %+v", err) } - return statestore.NewRedisStore(redis, locker), mr + redisStore := statestore.NewRedisStore(redis, locker) + return redisStore, redisStore, mr } diff --git a/tests/intergration_test.go b/tests/intergration_test.go index 8c6ed74..f575567 100644 --- a/tests/intergration_test.go +++ b/tests/intergration_test.go @@ -276,21 +276,21 @@ func TestEvaluator(t *testing.T) { } func TestAssignerError(t *testing.T) { - store, _ := minimatch.NewStateStoreWithMiniRedis(t) + frontStore, backStore, _ := minimatch.NewStateStoreWithMiniRedis(t) invalidAssigner := minimatch.AssignerFunc(func(ctx context.Context, matches []*pb.Match) ([]*pb.AssignmentGroup, error) { return nil, errors.New("error") }) - invalidBackend, err := minimatch.NewBackend(store, invalidAssigner) + invalidBackend, err := minimatch.NewBackend(backStore, invalidAssigner) require.NoError(t, err) invalidBackend.AddMatchFunction(anyProfile, minimatch.MatchFunctionSimple1vs1) validAssigner := minimatch.AssignerFunc(dummyAssign) - validBackend, err := minimatch.NewBackend(store, validAssigner) + validBackend, err := minimatch.NewBackend(backStore, validAssigner) require.NoError(t, err) validBackend.AddMatchFunction(anyProfile, minimatch.MatchFunctionSimple1vs1) ctx := context.Background() - frontend := minimatch.NewTestFrontendServer(t, store, "127.0.0.1:0") + frontend := minimatch.NewTestFrontendServer(t, frontStore, "127.0.0.1:0") frontend.Start(t) fc := frontend.Dial(t) t1, err := fc.CreateTicket(ctx, &pb.CreateTicketRequest{Ticket: &pb.Ticket{}})