libcuframes: fix TOCTOU race в consumer slot read
build / cmake build (CUDA 12.4, Ubuntu 22.04) (push) Successful in 1m34s
build / ffmpeg filter patch (out-of-tree) (push) Successful in 1m19s
release / build runtime Docker image (push) Failing after 1s
release / build source tarball (push) Successful in 4s
test-u4-runner / u4 runner smoke test (push) Has been cancelled

Bug: producer signals **один global** cudaEvent для всего ring (один на
producer). Consumer waits этот event после slot_seq validation, но event
соответствует ПОСЛЕДНЕМУ published frame, не slot[target_seq]. Если
producer wrap'нет ring во время event wait (ring=6 = 240ms окно), slot
содержит уже next-gen data, consumer возвращает torn/stale frame.

Симптом в production: video stream показывает «back-jump на момент»
periodically — camera OSD timestamp дёргается, motion machines briefly
teleport назад. cluster md5 analysis НЕ ловит (содержимое frames всё ещё
unique, просто из неправильной epoch).

Fix: post-sync verify. После cudaStreamWaitEvent / cudaEventSynchronize
re-check slots[slot_idx].seq == target_seq. Если producer перезаписал —
continue outer loop с новым target_seq.

Закрывает race window между slot validation и event sync return. Остаются
открытыми:
  - downstream GPU access после frame fill (consumer-side) — producer
    может wrap во время этого. Mitigation: STRICT_WAIT policy в publisher
    + ack discipline в consumer (cuframes_release_frame ack уже works).
  - bigger ring size снижает wrap frequency (240ms → 1.2s при ring=30).

Test: после deploy в cuda-grid-pipeline (Phase 7 single cam), camera OSD
clock больше не дёргается (раньше дёргалось каждые ~16 sec).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 22:27:39 +01:00
parent 4d54173bb2
commit 517107d741
+11
View File
@@ -290,6 +290,17 @@ int cuframes_subscriber_next(cuframes_subscriber_t *sub,
if (cerr != cudaSuccess) return CUFRAMES_ERR_CUDA;
}
/* TOCTOU защита: producer_event signal'ит для последнего published
* frame, не per-slot. Если producer wrapped ring пока мы ждали
* event sync, slot[slot_idx] уже содержит DIFFERENT seq.
* Re-verify slot_seq — если изменился, retry с новым target_seq. */
uint64_t verify_seq = atomic_load_explicit(&sub->hdr->slots[slot_idx].seq,
memory_order_acquire);
if (verify_seq != target_seq) {
/* Slot overwritten во время event wait — outer loop пересчитает */
continue;
}
/* Fill frame_out */
struct cuframes_frame *f = &sub->frame_obj;
f->cuda_ptr = sub->mapped_ptrs[slot_idx];