fix: race condition between close() and _encode_sync in H264LiveEncoder#292
fix: race condition between close() and _encode_sync in H264LiveEncoder#292zackzmai wants to merge 2 commits into
Conversation
close() sets self._codec = None on the main thread to signal concurrent _encode_sync calls to stop. However, _encode_sync may have already passed the 'assert self._codec is not None' check and be about to call self._codec.encode(frame) — at which point self._codec becomes None, causing 'NoneType has no attribute encode'. Fix: capture self._codec into a local variable right after the assertion and use that local for the encode loop. close() can safely set self._codec = None without affecting an in-flight encode.
|
👋 感谢提交 PR @zackzmai!维护者会尽快 review。 提交前请确认:
|
|
🟡 [Review] 背景: 问题:修复对 assert self._codec is not None # L134 ← 第 1 次读 self._codec
# Capture codec ref locally ...
codec = self._codec # L137 ← 第 2 次读 self._codec
...
for packet in codec.encode(frame): # L151CPython 可在任意两条字节码之间切线程。若主线程在 L134 和 L137 之间执行
窗口比修复前小(原窗口 L134→L151 含 补充:Python 断言在 改进:只读一次 # Capture codec ref locally FIRST so a single read is checked and used —
# close() setting self._codec = None on the main thread can't slip in
# between the check and the encode.
codec = self._codec
if codec is None:
return []
frame = av.VideoFrame.from_ndarray(bgr, format="bgr24")
frame = frame.reformat(format="yuv420p")
...
out: list[tuple[bytes, bool]] = []
for packet in codec.encode(frame):
out.append((bytes(packet), bool(packet.is_keyframe)))
return out若想保留断言来捕捉"非 close 路径下 codec 不应为 None"的编程错误,也要让断言作用在局部上,确保只读一次: codec = self._codec
assert codec is not None # 校验即将使用的同一个局部,不再二次读 self._codec |
close() sets self._codec = None on the main thread to block concurrent _encode_sync calls. The previous fix still read self._codec twice (assert at L134 then capture at L137), so the main thread could set None between the two reads and the captured local would be None, reproducing 'NoneType' object has no attribute 'encode'. Under PYTHONOPTIMIZE the assert is stripped entirely. Per review (yangbaofu007): capture self._codec into a local in a single read and guard with an early return. close() is a normal concurrent shutdown, not a programming error, so early return fits the semantics better than an assert. The whole encode loop now references only the local, so close() setting self._codec = None can no longer affect an in-flight encode.
|
@yangbaofu007 采纳,已按建议改为单次读取 + 早返回(commit 40ca318): codec = self._codec
if codec is None:
return []原 assert + 二次读取捕获的窗口已消除:整个 encode 循环只引用局部 |
Problem
H264LiveEncoder.close()setsself._codec = Noneon the main thread to signal concurrent_encode_synccalls to stop. However,_encode_syncmay have already passed theassert self._codec is not Noneguard and be about to callself._codec.encode(frame)— at which pointself._codecbecomesNone, causing:This triggers whenever a WebSocket video-stream client disconnects while a frame encode is in-flight, and is visible in logs as repeated
transcode encode errorentries.Fix
Capture
self._codecinto a local variable right after the assertion and use that local for the encode loop.close()can safely setself._codec = Nonewithout affecting an in-flight encode.One-line semantic change, no API or behavioral difference.