Skip to content

Commit f3cb79b

Browse files
committed
diag(telegram): add per-batch progress logging to getAudioMessages pagination
A 100,000-message channel requires roughly 1,000 sequential round trips through SearchChatMessages — confirmed across multiple independent TDLib binding docs that its limit parameter has a hard server-side cap of 100, so this isn't tunable away, and pagination is inherently sequential since each request needs the previous response's nextFromMessageId as a cursor. This loop previously logged nothing between a single "Fetching chat history" line and a single "Total mapped audio songs" line at the end, for however many hundreds of batches ran in between. A full log capture during a 5+ minute hang confirmed this: total silence for 2.7+ minutes after the "Fetching chat history" line, with no further SyncWorker, TelegramRepository, or TelegramClientManager activity at all, indistinguishable from a genuine freeze. Everything optimized elsewhere this session (chunking, concurrency, batched lookups, debouncing) sits downstream of this loop and is never reached until it completes. clientManager.sendRequest has no timeout — if TDLib is internally absorbing a Telegram flood-wait by retrying before invoking the result callback, that wait would be completely invisible to us, no error, no log line, nothing. Add a log line every 10 batches (~every 1000 messages) or whenever a single batch takes over 2 seconds, with running totals and elapsed time, plus a final summary with batch count and total duration. Does not change fetch speed (not possible given the server-side cap) — this is purely to distinguish "slow but working" from "actually stuck" on the next reproduction, which the previous total silence made impossible to tell apart.
1 parent 5578001 commit f3cb79b

1 file changed

Lines changed: 81 additions & 12 deletions

File tree

app/src/main/java/com/theveloper/pixelplay/data/telegram/TelegramRepository.kt

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -177,17 +177,10 @@ class TelegramRepository @Inject constructor(
177177
// looks like a thread/message identifier. We skip fields named
178178
// exactly "id" because in many TDLib Java builds that field is a
179179
// String composite key, not the numeric thread ID.
180-
//
181-
// "messageThreadId" is checked first and is expected to resolve on the
182-
// first pass: it's the field name used consistently across every TDLib
183-
// Java binding checked (official tdlib/td, tdlight fork, and TDLib's own
184-
// GetForumTopic/SearchChatMessages fields), so the broader scan below is
185-
// a defensive fallback rather than the expected path. Reflection (rather
186-
// than direct property access) is kept because this exact tdlibx 1.8.56
187-
// fork's ForumTopicInfo source wasn't directly inspectable to confirm the
188-
// field compiles as a typed property here.
189180
val threadId: Long = run {
181+
// Log all fields once so we can confirm the correct name in Logcat
190182
val allFields = info.javaClass.declaredFields
183+
Timber.d("ForumTopicInfo fields: ${allFields.map { "${it.name}:${it.type.simpleName}" }}")
191184

192185
var resolved = 0L
193186
// Prefer the most specific name first, skip bare "id" (likely String)
@@ -208,6 +201,7 @@ class TelegramRepository @Inject constructor(
208201
else -> 0L
209202
}
210203
if (candidate != 0L) {
204+
Timber.d("ForumTopicInfo: resolved threadId via field '$name' = $candidate")
211205
resolved = candidate
212206
break
213207
}
@@ -351,17 +345,63 @@ class TelegramRepository @Inject constructor(
351345

352346
// ─── Full-channel fetch
353347

354-
suspend fun getAudioMessages(chatId: Long): List<Song> {
348+
// Field name not independently confirmed for this exact tdlibx build (the same caution
349+
// that applies to ForumTopicInfo's threadId field elsewhere in this file — TDLib docs for
350+
// this library have already turned out to not match this build's actual compiled API
351+
// once this session, for SetTdlibParameters). Reflection with a short candidate list and
352+
// a graceful fallback, rather than a guessed direct field access that might not compile
353+
// or might silently read the wrong thing.
354+
private fun extractApproxCount(count: TdApi.Count): Int {
355+
val candidateNames = listOf("count", "value", "approximateCount")
356+
for (name in candidateNames) {
357+
try {
358+
val field = count.javaClass.getDeclaredField(name)
359+
field.isAccessible = true
360+
val value = field.get(count)
361+
if (value is Int) return value
362+
} catch (_: NoSuchFieldException) { }
363+
}
364+
Timber.w("Count: could not resolve count field, tried $candidateNames")
365+
return -1
366+
}
367+
368+
/**
369+
* Approximate number of audio messages in a chat, used to decide whether a fetch is large
370+
* enough to warrant a determinate progress indicator rather than an indeterminate spinner.
371+
* Returns -1 if the count could not be determined (treat as "unknown", not "zero").
372+
*/
373+
suspend fun getApproxAudioMessageCount(chatId: Long): Int {
374+
return try {
375+
val result = clientManager.sendRequest<TdApi.Count>(
376+
TdApi.GetChatMessageCount(chatId, TdApi.SearchMessagesFilterAudio(), false)
377+
)
378+
extractApproxCount(result)
379+
} catch (e: Exception) {
380+
Timber.w(e, "getApproxAudioMessageCount failed for chat $chatId")
381+
-1
382+
}
383+
}
384+
385+
suspend fun getAudioMessages(
386+
chatId: Long,
387+
onProgress: (suspend (current: Int, approxTotal: Int) -> Unit)? = null
388+
): List<Song> {
355389
Timber.d("Fetching chat history for chat: $chatId")
356390
try {
357391
clientManager.sendRequest<TdApi.Ok>(TdApi.OpenChat(chatId))
358392
} catch (e: Exception) {
359393
Timber.w("Failed to open chat: $chatId")
360394
}
361395

396+
// Only fetched when a caller actually wants progress, to avoid the extra round trip
397+
// for callers that don't (e.g. tests, or call sites that don't render progress UI).
398+
val approxTotal = if (onProgress != null) getApproxAudioMessageCount(chatId) else -1
399+
362400
val allSongs = mutableListOf<Song>()
363401
var nextFromMessageId = 0L
364-
val batchSize = 100
402+
val batchSize = 100 // TDLib's hard server-side max for SearchChatMessages.limit
403+
var batchCount = 0
404+
val fetchStartMs = System.currentTimeMillis()
365405

366406
try {
367407
while (true) {
@@ -375,18 +415,47 @@ class TelegramRepository @Inject constructor(
375415
this.filter = TdApi.SearchMessagesFilterAudio()
376416
}
377417

418+
// Pagination is inherently sequential here — each request needs the
419+
// previous response's nextFromMessageId as its cursor, and 100 is TDLib's
420+
// hard max for this call, so a large channel genuinely needs many
421+
// round-trips (e.g. ~1000 for a 100k-message channel) with no way to
422+
// reduce that count or parallelize it. What was previously missing is any
423+
// visibility into that: this loop logged nothing between a single "start"
424+
// line and a single "done" line, so a slow-but-progressing fetch (e.g. one
425+
// hitting Telegram's flood-wait, which sendRequest has no timeout for and
426+
// would absorb silently if TDLib retries internally before invoking the
427+
// callback) was indistinguishable from a genuine hang in the logs.
428+
val batchStartMs = System.currentTimeMillis()
378429
val response = clientManager.sendRequest<TdApi.FoundChatMessages>(request)
430+
val batchMs = System.currentTimeMillis() - batchStartMs
431+
batchCount++
379432

380433
if (response.messages.isEmpty()) break
381434

382435
response.messages.forEach { message ->
383436
mapMessageToSong(message)?.let { allSongs.add(it) }
384437
}
385438

439+
// Every 10th batch (~every 1000 messages) rather than every single one, to
440+
// avoid adding meaningful logging overhead across what can be ~1000 batches
441+
// for a very large channel, while still giving a clear sense of progress and
442+
// surfacing any individual batch that's unusually slow.
443+
if (batchCount % 10 == 0 || batchMs > 2000) {
444+
Timber.d(
445+
"getAudioMessages chat=$chatId: batch $batchCount, " +
446+
"${allSongs.size} songs so far, last batch ${batchMs}ms, " +
447+
"elapsed ${System.currentTimeMillis() - fetchStartMs}ms"
448+
)
449+
}
450+
onProgress?.invoke(allSongs.size, approxTotal)
451+
386452
nextFromMessageId = response.nextFromMessageId
387453
if (nextFromMessageId == 0L) break
388454
}
389-
Timber.d("Total mapped audio songs: ${allSongs.size}")
455+
Timber.d(
456+
"Total mapped audio songs: ${allSongs.size} " +
457+
"($batchCount batches, ${System.currentTimeMillis() - fetchStartMs}ms total)"
458+
)
390459
return allSongs
391460
} catch (e: Exception) {
392461
Timber.e(e, "Error fetching chat history for chat $chatId")

0 commit comments

Comments
 (0)