Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion cmd/tg/chats.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,14 @@ func listChats(ctx context.Context, api *tg.Client, m *peerManager, limit int, a
folder = 1
}

iter := query.GetDialogs(api).FolderID(folder).Iter()
// Fetch in large batches to minimize round trips. The dialogs iterator's
// default batch size is 1, so it issues one messages.getDialogs RPC per
// dialog, which makes listing slow. Telegram does not reject a per-request
// limit above 100 but silently returns fewer/incorrect results, so cap at
// 100. Clamp to at least 1: a non-positive batch size would panic the
// iterator (it preallocates a slice with that capacity).
batch := max(1, min(limit, 100))
iter := query.GetDialogs(api).FolderID(folder).BatchSize(batch).Iter()
now := time.Now().Unix()

var (
Expand Down
37 changes: 37 additions & 0 deletions cmd/tg/chats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,40 @@ func TestListChatsLimit(t *testing.T) {
t.Fatalf("limit not respected: got %d", len(list.Chats))
}
}

// TestListChatsBatchSize asserts the per-request limit is batched (capped at
// 100) instead of the iterator default of 1, and never goes below 1 (a
// non-positive batch size panics the iterator).
func TestListChatsBatchSize(t *testing.T) {
for _, tt := range []struct {
limit int
wantBatch int
}{
{limit: 5, wantBatch: 5},
{limit: 100, wantBatch: 100},
{limit: 350, wantBatch: 100},
{limit: 0, wantBatch: 1},
{limit: -1, wantBatch: 1},
} {
var gotBatch int
api := newFuncAPI(t, func(req bin.Encoder) (bin.Encoder, error) {
r, ok := req.(*tg.MessagesGetDialogsRequest)
if !ok {
return nil, errors.Errorf("unexpected request %T", req)
}
gotBatch = r.Limit
return &tg.MessagesDialogs{
Dialogs: []tg.DialogClass{&tg.Dialog{Peer: &tg.PeerUser{UserID: 1}, TopMessage: 1}},
Messages: []tg.MessageClass{&tg.Message{ID: 1, PeerID: &tg.PeerUser{UserID: 1}, Date: 1}},
Users: []tg.UserClass{&tg.User{ID: 1}},
}, nil
})

if _, err := listChats(context.Background(), api, nil, tt.limit, false); err != nil {
t.Fatalf("limit %d: %v", tt.limit, err)
}
if gotBatch != tt.wantBatch {
t.Errorf("limit %d: request limit = %d, want %d", tt.limit, gotBatch, tt.wantBatch)
}
}
}
6 changes: 5 additions & 1 deletion cmd/tg/drafts.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ func saveDraft(ctx context.Context, api *tg.Client, peer tg.InputPeerClass, text

// listDrafts collects drafts from the dialog list.
func listDrafts(ctx context.Context, api *tg.Client) (draftsResult, error) {
iter := query.GetDialogs(api).Iter()
// Scan every dialog to collect drafts. The iterator's default batch size is
// 1 (one messages.getDialogs RPC per dialog), so without batching this walks
// the whole account one round trip at a time. 100 is the per-request maximum
// Telegram serves.
iter := query.GetDialogs(api).BatchSize(100).Iter()
var out draftsResult
for iter.Next(ctx) {
elem := iter.Value()
Expand Down
45 changes: 45 additions & 0 deletions cmd/tg/drafts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package main

import (
"context"
"testing"

"github.com/go-faster/errors"

"github.com/gotd/td/bin"
"github.com/gotd/td/tg"
)

// TestListDraftsBatchSize asserts the dialog scan is batched at 100 per request
// instead of the iterator default of 1: listDrafts walks every dialog, so a
// batch size of 1 would issue one messages.getDialogs RPC per dialog.
func TestListDraftsBatchSize(t *testing.T) {
var gotBatch int
api := newFuncAPI(t, func(req bin.Encoder) (bin.Encoder, error) {
r, ok := req.(*tg.MessagesGetDialogsRequest)
if !ok {
return nil, errors.Errorf("unexpected request %T", req)
}
gotBatch = r.Limit
return &tg.MessagesDialogs{
Dialogs: []tg.DialogClass{&tg.Dialog{
Peer: &tg.PeerUser{UserID: 1},
TopMessage: 1,
Draft: &tg.DraftMessage{Message: "wip"},
}},
Messages: []tg.MessageClass{&tg.Message{ID: 1, PeerID: &tg.PeerUser{UserID: 1}, Date: 1}},
Users: []tg.UserClass{&tg.User{ID: 1}},
}, nil
})

res, err := listDrafts(context.Background(), api)
if err != nil {
t.Fatal(err)
}
if gotBatch != 100 {
t.Errorf("request limit = %d, want 100", gotBatch)
}
if len(res.Drafts) != 1 || res.Drafts[0].Message != "wip" {
t.Errorf("drafts = %+v", res.Drafts)
}
}