fix(vector): guard unbound doc_task + address review nits (#891)

Round-1 review on PR #891:
- Guard processor_task's broad except handler against an unbound doc_task
  (mirrors multi_user_processor_task): initialise doc_task=None before the loop
  and branch the error log. Fixes a latent NameError if receive() raises a
  non-TimeoutError/EndOfStream before the first document binds. Regression test
  added.
- Drop the unnecessary `from __future__ import annotations` in vector/_errors.py
  and express format_exception_group's non-group fast path as an explicit
  isinstance check.
- Add a copy_resource Destination-header encoding test (analogue to MOVE);
  strengthen the ExceptionGroup test to assert the full leaf repr survives.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-06-11 05:31:56 +02:00
co-authored by Claude Opus 4.8
parent 0388735593
commit a188e9fced
5 changed files with 96 additions and 12 deletions
+2 -4
View File
@@ -9,8 +9,6 @@ exceptions so log lines name the actual cause; pair it with ``exc_info=True`` to
keep the full traceback.
"""
from __future__ import annotations
def format_exception_group(exc: BaseException) -> str:
"""Return a concise, leaf-naming string for ``exc``.
@@ -20,9 +18,9 @@ def format_exception_group(exc: BaseException) -> str:
result is meant for the human-readable portion of a log message, not for
parsing.
"""
leaves = _flatten(exc)
if len(leaves) == 1 and leaves[0] is exc:
if not isinstance(exc, BaseExceptionGroup):
return repr(exc)
leaves = _flatten(exc)
return f"{len(leaves)} sub-exception(s): " + "; ".join(repr(e) for e in leaves)
+21 -8
View File
@@ -236,6 +236,11 @@ async def processor_task(
# Signal that the task has started and is ready
task_status.started()
# Initialised before the loop so the broad except handler below can't hit an
# unbound name if receive() itself raises a non-TimeoutError/EndOfStream
# exception on the very first iteration (mirrors multi_user_processor_task).
doc_task: DocumentTask | None = None
while not shutdown_event.is_set():
try:
# Get document with timeout (allows checking shutdown)
@@ -265,14 +270,22 @@ async def processor_task(
break
except Exception as e:
logger.error(
"Processor %s error processing %s_%s: %s",
worker_id,
doc_task.doc_type,
doc_task.doc_id,
format_exception_group(e),
exc_info=True,
)
if doc_task is not None:
logger.error(
"Processor %s error processing %s_%s: %s",
worker_id,
doc_task.doc_type,
doc_task.doc_id,
format_exception_group(e),
exc_info=True,
)
else:
logger.error(
"Processor %s error: %s",
worker_id,
format_exception_group(e),
exc_info=True,
)
# Continue to next document (no task_done() needed with streams)
logger.info("Processor %s stopped", worker_id)