Skip to content

Commit 16f94e1

Browse files
troychiuHavenXia
authored andcommitted
fix ListActors bug
1 parent c1bd202 commit 16f94e1

2 files changed

Lines changed: 111 additions & 49 deletions

File tree

cmd/ateapi/internal/store/ateredis/ateredis.go

Lines changed: 24 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -687,65 +687,40 @@ func (s *Persistence) ListActors(ctx context.Context, atespace string, pageSize
687687
}
688688

689689
var result []*ateapipb.Actor
690-
var nextToken string
691-
stop := false
690+
i := startIndex
691+
cursor := token.Cursor
692692

693-
for i := startIndex; i < len(masters) && !stop; i++ {
693+
for i < len(masters) && len(result) < int(pageSize) {
694694
master := masters[i]
695-
shardAddr := master.Options().Addr
696-
cursor := uint64(0)
697-
if i == startIndex && token.ShardHash != "" {
698-
cursor = token.Cursor
699-
}
695+
remaining := int(pageSize) - len(result)
700696

701-
for {
702-
remaining := int(pageSize) - len(result)
703-
if remaining <= 0 {
704-
if cursor != 0 {
705-
nextToken = encodePageToken(listActorsPageToken{
706-
ShardHash: hashShardAddr(shardAddr),
707-
Cursor: cursor,
708-
})
709-
} else if i+1 < len(masters) {
710-
nextToken = encodePageToken(listActorsPageToken{
711-
ShardHash: hashShardAddr(masters[i+1].Options().Addr),
712-
Cursor: 0,
713-
})
714-
} else {
715-
nextToken = ""
716-
}
717-
stop = true
718-
break
719-
}
697+
var keys []string
698+
keys, cursor, err = master.Scan(ctx, cursor, actorScanPattern(atespace), int64(remaining)).Result()
699+
if err != nil {
700+
return nil, "", fmt.Errorf("while scanning shard %s: %w", master.Options().Addr, err)
701+
}
720702

721-
var keys []string
722-
keys, cursor, err = master.Scan(ctx, cursor, actorScanPattern(atespace), int64(remaining)).Result()
703+
if len(keys) > 0 {
704+
actors, err := s.fetchActors(ctx, master, keys)
723705
if err != nil {
724-
return nil, "", fmt.Errorf("while scanning shard %s: %w", shardAddr, err)
725-
}
726-
727-
if len(keys) > 0 {
728-
actors, err := s.fetchActors(ctx, master, keys)
729-
if err != nil {
730-
return nil, "", err
731-
}
732-
result = append(result, actors...)
706+
return nil, "", err
733707
}
708+
result = append(result, actors...)
709+
}
734710

735-
if cursor == 0 {
736-
if i+1 < len(masters) {
737-
nextToken = encodePageToken(listActorsPageToken{
738-
ShardHash: hashShardAddr(masters[i+1].Options().Addr),
739-
Cursor: 0,
740-
})
741-
} else {
742-
nextToken = ""
743-
}
744-
break
745-
}
711+
if cursor == 0 {
712+
i++
746713
}
747714
}
748715

716+
var nextToken string
717+
if i < len(masters) {
718+
nextToken = encodePageToken(listActorsPageToken{
719+
ShardHash: hashShardAddr(masters[i].Options().Addr),
720+
Cursor: cursor,
721+
})
722+
}
723+
749724
return result, nextToken, nil
750725
}
751726

cmd/ateapi/internal/store/ateredis/ateredis_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,3 +1229,90 @@ func TestGetSortedMasters_ConcurrentCallbacks(t *testing.T) {
12291229
}
12301230
}
12311231
}
1232+
1233+
// TestListActors_MultiMaster_Pagination verifies that pagination across multiple
1234+
// Redis master shards collects items from every shard without skipping or duplicating
1235+
// shards when page boundaries align with shard boundaries.
1236+
func TestListActors_MultiMaster_Pagination(t *testing.T) {
1237+
ctx := context.Background()
1238+
const numShards = 3
1239+
1240+
type shard struct {
1241+
client *redis.Client
1242+
clusterClient *redis.ClusterClient
1243+
}
1244+
var shards []shard
1245+
for i := 0; i < numShards; i++ {
1246+
mr, err := miniredis.Run()
1247+
if err != nil {
1248+
t.Fatalf("failed to start miniredis %d: %v", i, err)
1249+
}
1250+
defer mr.Close()
1251+
1252+
client := redis.NewClient(&redis.Options{Addr: mr.Addr()})
1253+
clusterClient := redis.NewClusterClient(&redis.ClusterOptions{Addrs: []string{mr.Addr()}})
1254+
defer client.Close()
1255+
defer clusterClient.Close()
1256+
1257+
shards = append(shards, shard{
1258+
client: client,
1259+
clusterClient: clusterClient,
1260+
})
1261+
}
1262+
1263+
sort.Slice(shards, func(i, j int) bool {
1264+
return shards[i].client.Options().Addr < shards[j].client.Options().Addr
1265+
})
1266+
1267+
var clients []*redis.Client
1268+
for shardIdx, sh := range shards {
1269+
clients = append(clients, sh.client)
1270+
tempS := &Persistence{rdb: sh.clusterClient}
1271+
for itemIdx := 0; itemIdx < 3; itemIdx++ {
1272+
actor := &ateapipb.Actor{
1273+
Metadata: &ateapipb.ResourceMetadata{
1274+
Name: fmt.Sprintf("actor-shard%d-item%d", shardIdx, itemIdx),
1275+
Atespace: testAtespace,
1276+
},
1277+
ActorTemplateNamespace: "default",
1278+
ActorTemplateName: "test-template",
1279+
Status: ateapipb.Actor_STATUS_SUSPENDED,
1280+
}
1281+
if _, err := tempS.CreateActor(ctx, actor); err != nil {
1282+
t.Fatalf("failed to seed actor: %v", err)
1283+
}
1284+
}
1285+
}
1286+
1287+
fake := &concurrentMasterClient{
1288+
redisClient: shards[0].clusterClient,
1289+
masters: clients,
1290+
}
1291+
s := &Persistence{rdb: fake}
1292+
1293+
var allActors []*ateapipb.Actor
1294+
pageToken := ""
1295+
for {
1296+
actors, nextToken, err := s.ListActors(ctx, testAtespace, 2, pageToken)
1297+
if err != nil {
1298+
t.Fatalf("ListActors failed: %v", err)
1299+
}
1300+
allActors = append(allActors, actors...)
1301+
if nextToken == "" {
1302+
break
1303+
}
1304+
pageToken = nextToken
1305+
}
1306+
1307+
if len(allActors) != 9 {
1308+
t.Fatalf("expected 9 total actors across %d shards, got %d", numShards, len(allActors))
1309+
}
1310+
1311+
seen := make(map[string]bool)
1312+
for _, a := range allActors {
1313+
if seen[a.GetMetadata().GetName()] {
1314+
t.Errorf("duplicate actor found in paginated results: %s", a.GetMetadata().GetName())
1315+
}
1316+
seen[a.GetMetadata().GetName()] = true
1317+
}
1318+
}

0 commit comments

Comments
 (0)