quyu666 commited on
Commit
6091558
·
1 Parent(s): ef2250d

sync processor from GitLab d3aa79b: get_episode 未命中强制重扫(多进程缓存可见性修复)

Browse files
Files changed (1) hide show
  1. processor/app/localstore.py +37 -7
processor/app/localstore.py CHANGED
@@ -34,6 +34,9 @@ _sessions_cache_at = 0.0
34
  # fast fallback; the ingest queue refreshes it after the batch is committed.
35
  _task_sessions_cache: list[dict] | None = None
36
  _SESSIONS_CACHE_TTL = 300.0
 
 
 
37
  # 全量遍历远程挂载很慢:只允许单一线程执行(_scan_lock),遍历本身不持有
38
  # _lock —— 缓存过期触发的重扫不再把状态读写请求拖在锁后面排队。
39
  _scan_lock = threading.Lock()
@@ -202,13 +205,18 @@ def _run_session_refresh() -> None:
202
  _sessions_refresh_running = False
203
 
204
 
205
- def _scan_sessions_blocking() -> list[dict]:
206
- """Full remote tree walk; callers block for the duration."""
 
 
 
 
 
207
  global _sessions_cache, _sessions_cache_at, _task_sessions_cache
208
  while True:
209
  with _lock:
210
  now = time.monotonic()
211
- if (_sessions_cache is not None
212
  and now - _sessions_cache_at < _SESSIONS_CACHE_TTL):
213
  return copy.deepcopy(_sessions_cache)
214
  generation = _scan_generation
@@ -216,7 +224,9 @@ def _scan_sessions_blocking() -> list[dict]:
216
  with _scan_lock:
217
  with _lock:
218
  now = time.monotonic()
219
- if (_sessions_cache is not None
 
 
220
  and now - _sessions_cache_at < _SESSIONS_CACHE_TTL):
221
  # 等待 _scan_lock 期间已有线程完成扫描
222
  return copy.deepcopy(_sessions_cache)
@@ -619,10 +629,30 @@ def _probe_video_cached(path: Path) -> tuple[int, float]:
619
 
620
 
621
  def get_episode(episode_id: str) -> dict | None:
622
- """按批次名查 episode(扫描目录)。"""
 
 
 
 
 
 
 
 
 
 
623
  name = str(episode_id)
624
- return next((episode for episode in scan_sessions()
625
- if str(episode.get("id")) == name), None)
 
 
 
 
 
 
 
 
 
 
626
 
627
 
628
  # ── 审核状态 ─────────────────────────────────────────
 
34
  # fast fallback; the ingest queue refreshes it after the batch is committed.
35
  _task_sessions_cache: list[dict] | None = None
36
  _SESSIONS_CACHE_TTL = 300.0
37
+ # get_episode 未命中后允许强制重扫的最小间隔:既让刚上传的批次立刻可见,
38
+ # 又不至于被反复查询的无效 id 触发远程全量遍历风暴。
39
+ _MISS_RESCAN_MIN_INTERVAL = 5.0
40
  # 全量遍历远程挂载很慢:只允许单一线程执行(_scan_lock),遍历本身不持有
41
  # _lock —— 缓存过期触发的重扫不再把状态读写请求拖在锁后面排队。
42
  _scan_lock = threading.Lock()
 
205
  _sessions_refresh_running = False
206
 
207
 
208
+ def _scan_sessions_blocking(force: bool = False) -> list[dict]:
209
+ """Full remote tree walk; callers block for the duration.
210
+
211
+ ``force=True`` 跳过 TTL 判断,即使快照还新鲜也重新遍历 —— 供
212
+ :func:`get_episode` 在未命中时使用:快照里没有不代表磁盘上没有,
213
+ 可能只是本进程还没看到刚上传的批次。
214
+ """
215
  global _sessions_cache, _sessions_cache_at, _task_sessions_cache
216
  while True:
217
  with _lock:
218
  now = time.monotonic()
219
+ if (not force and _sessions_cache is not None
220
  and now - _sessions_cache_at < _SESSIONS_CACHE_TTL):
221
  return copy.deepcopy(_sessions_cache)
222
  generation = _scan_generation
 
224
  with _scan_lock:
225
  with _lock:
226
  now = time.monotonic()
227
+ # force 时不吃这个捷径:那份快照可能正是我们刚刚未命中的
228
+ # 那一份。既然调用方明确要求重扫,就实实在在走一遍。
229
+ if (not force and _sessions_cache is not None
230
  and now - _sessions_cache_at < _SESSIONS_CACHE_TTL):
231
  # 等待 _scan_lock 期间已有线程完成扫描
232
  return copy.deepcopy(_sessions_cache)
 
629
 
630
 
631
  def get_episode(episode_id: str) -> dict | None:
632
+ """按批次名查 episode(扫描目录)。
633
+
634
+ 未命中时会强制同步重扫一次再判断 —— **快照里没有不等于磁盘上没有**:
635
+ 上传只清发起上传那个进程的缓存,别的进程(worker / 另一个 API 实例)
636
+ 要等自己的 TTL 过期才看得见新批次。而未命中恰恰是不能相信缓存的时刻:
637
+ 导出链路里 ``get_episode`` 返回 None 会让整条 run 直接失败(实测 ep74-79
638
+ 在 17:25-17:31 全部因此 failed,派生产物没能合并回 canonical)。
639
+
640
+ 重扫有最小间隔,避免反复查询不存在的 id 时把远程全量遍历打成风暴;
641
+ 真正的"不存在"在重扫后依然返回 None,只是多付一次遍历。
642
+ """
643
  name = str(episode_id)
644
+ episode = next((item for item in scan_sessions()
645
+ if str(item.get("id")) == name), None)
646
+ if episode is not None:
647
+ return episode
648
+ with _lock:
649
+ just_scanned = (time.monotonic() - _sessions_cache_at
650
+ < _MISS_RESCAN_MIN_INTERVAL)
651
+ if just_scanned:
652
+ # 刚刚才遍历过,这次未命中是可信的
653
+ return None
654
+ return next((item for item in _scan_sessions_blocking(force=True)
655
+ if str(item.get("id")) == name), None)
656
 
657
 
658
  # ── 审核状态 ─────────────────────────────────────────