SilverElixir commited on
Commit
87dd95b
·
1 Parent(s): 6b895d8

Perf: download album extras in parallel, keep message order

Browse files
Files changed (2) hide show
  1. lumen_message_core.py +5 -1
  2. tests/test_bot_state.py +48 -0
lumen_message_core.py CHANGED
@@ -59,6 +59,7 @@ async def _process_media_group_buffers(mgid: str) -> None:
59
  MAX_ALBUM_EXTRA = 9 # первое уходит как основное, до +9 дополнительных (итого 10 — как лимит TikTok-слайдшоу)
60
  album_state = bot.get_state(main_msg.chat.id)
61
  album_user_id = main_msg.from_user.id if main_msg.from_user else None
 
62
  for m in messages[1:1 + MAX_ALBUM_EXTRA]:
63
  src = _msg_media_source(m)
64
  if not src:
@@ -66,7 +67,10 @@ async def _process_media_group_buffers(mgid: str) -> None:
66
  fid, mime, _ = _media_file_id_and_mime(src)
67
  if not fid:
68
  continue
69
- fetched = await bot._fetch_media(fid, mime)
 
 
 
70
  if fetched:
71
  extra_media.append(fetched)
72
  # Регрессия: файлы альбома пишем в recent_media_ids, иначе "что на втором фото" не найдёт их.
 
59
  MAX_ALBUM_EXTRA = 9 # первое уходит как основное, до +9 дополнительных (итого 10 — как лимит TikTok-слайдшоу)
60
  album_state = bot.get_state(main_msg.chat.id)
61
  album_user_id = main_msg.from_user.id if main_msg.from_user else None
62
+ targets: list[tuple[str, str, Any]] = []
63
  for m in messages[1:1 + MAX_ALBUM_EXTRA]:
64
  src = _msg_media_source(m)
65
  if not src:
 
67
  fid, mime, _ = _media_file_id_and_mime(src)
68
  if not fid:
69
  continue
70
+ targets.append((fid, mime, src))
71
+ # Качаем параллельно, а не по очереди: альбом из 9 фото иначе ждал бы до ~10-20с последовательных скачиваний.
72
+ fetched_list = await asyncio.gather(*(bot._fetch_media(fid, mime) for fid, mime, _ in targets))
73
+ for (fid, mime, src), fetched in zip(targets, fetched_list):
74
  if fetched:
75
  extra_media.append(fetched)
76
  # Регрессия: файлы альбома пишем в recent_media_ids, иначе "что на втором фото" не найдёт их.
tests/test_bot_state.py CHANGED
@@ -484,6 +484,54 @@ def test_process_media_group_buffers_records_extra_photos_to_recent_media():
484
  bot.chat_state.pop(chat_id, None)
485
 
486
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  def test_media_question_without_file_gets_no_file_notice(rate_guard_setup, monkeypatch):
488
  # Прод-кейс 17.09.2026: "что на фото?" без файла — модель выдумала описание
489
  # несуществующего скриншота. Раз резолвинг ничего не нашёл, этот факт едет
 
484
  bot.chat_state.pop(chat_id, None)
485
 
486
 
487
+ def test_process_media_group_buffers_fetches_extras_in_parallel_keeping_order():
488
+ # Альбом качается параллельно: медленное первое фото не должно задерживать остальные,
489
+ # а порядок вложений обязан совпасть с порядком сообщений.
490
+ chat_id = 999961
491
+ user = SimpleNamespace(id=778)
492
+
493
+ def _photo_msg(file_id):
494
+ photo = SimpleNamespace(file_id=file_id, mime_type=None, file_name=None)
495
+ return SimpleNamespace(
496
+ chat=SimpleNamespace(id=chat_id, type=bot.ChatType.PRIVATE), from_user=user,
497
+ media_group_id="mg2", text=None, caption=None, reply_to_message=None,
498
+ photo=[photo], video=None, animation=None, video_note=None,
499
+ voice=None, audio=None, document=None, sticker=None,
500
+ )
501
+
502
+ msgs = [_photo_msg(f"file_{i}") for i in range(5)]
503
+ captured = {}
504
+ concurrent = 0
505
+ max_concurrent = 0
506
+
507
+ async def fake_fetch_media(file_id, mime):
508
+ nonlocal concurrent, max_concurrent
509
+ concurrent += 1
510
+ max_concurrent = max(max_concurrent, concurrent)
511
+ try:
512
+ await asyncio.sleep(0.01)
513
+ return (file_id.encode(), "image/jpeg")
514
+ finally:
515
+ concurrent -= 1
516
+
517
+ async def fake_handle_core(message, extra_media=None):
518
+ captured["extra"] = extra_media
519
+
520
+ original_fetch, original_core = bot._fetch_media, bot._handle_message_core
521
+ bot._fetch_media = fake_fetch_media
522
+ bot._handle_message_core = fake_handle_core
523
+ bot._mg_buffers["mg2"] = msgs
524
+ try:
525
+ asyncio.run(bot._process_media_group_buffers("mg2"))
526
+ # Все 4 доп. фото качались одновременно, а не по очереди.
527
+ assert max_concurrent == 4
528
+ assert [b for b, _ in captured["extra"]] == [b"file_1", b"file_2", b"file_3", b"file_4"]
529
+ finally:
530
+ bot._fetch_media = original_fetch
531
+ bot._handle_message_core = original_core
532
+ bot.chat_state.pop(chat_id, None)
533
+
534
+
535
  def test_media_question_without_file_gets_no_file_notice(rate_guard_setup, monkeypatch):
536
  # Прод-кейс 17.09.2026: "что на фото?" без файла — модель выдумала описание
537
  # несуществующего скриншота. Раз резолвинг ничего не нашёл, этот факт едет