Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,622 @@
|
||||
"""Deterministic cross-thread cancellation tests for compression aux transports."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextvars
|
||||
import threading
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, Callable
|
||||
|
||||
import pytest
|
||||
|
||||
from agent import auxiliary_client as aux
|
||||
|
||||
|
||||
class _BlockingStream:
|
||||
def __init__(self, started: threading.Event) -> None:
|
||||
self.started = started
|
||||
self.closed = threading.Event()
|
||||
|
||||
def __iter__(self):
|
||||
self.started.set()
|
||||
self.closed.wait(timeout=5)
|
||||
raise RuntimeError("transport closed")
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed.set()
|
||||
|
||||
def get_final_message(self) -> Any:
|
||||
self.started.set()
|
||||
self.closed.wait(timeout=5)
|
||||
raise RuntimeError("transport closed")
|
||||
|
||||
|
||||
class _GenericCompletions:
|
||||
def __init__(self, stream: _BlockingStream) -> None:
|
||||
self.stream = stream
|
||||
|
||||
def create(self, **_kwargs: Any) -> _BlockingStream:
|
||||
return self.stream
|
||||
|
||||
|
||||
class _GenericClient:
|
||||
def __init__(self, stream: _BlockingStream) -> None:
|
||||
self.chat = SimpleNamespace(completions=_GenericCompletions(stream))
|
||||
self.stream = stream
|
||||
self.closed = threading.Event()
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed.set()
|
||||
self.stream.close()
|
||||
|
||||
|
||||
class _CodexResponses:
|
||||
def __init__(self, stream: _BlockingStream) -> None:
|
||||
self.stream = stream
|
||||
|
||||
def create(self, **_kwargs: Any) -> _BlockingStream:
|
||||
return self.stream
|
||||
|
||||
|
||||
class _CodexRealClient:
|
||||
def __init__(self, stream: _BlockingStream) -> None:
|
||||
self.responses = _CodexResponses(stream)
|
||||
self.api_key = "test"
|
||||
self.base_url = "https://example.test/codex"
|
||||
self.stream = stream
|
||||
self.closed = threading.Event()
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed.set()
|
||||
self.stream.close()
|
||||
|
||||
|
||||
class _AnthropicStreamContext:
|
||||
def __init__(self, stream: _BlockingStream) -> None:
|
||||
self.stream = stream
|
||||
|
||||
def __enter__(self) -> _BlockingStream:
|
||||
return self.stream
|
||||
|
||||
def __exit__(self, *_args: Any) -> None:
|
||||
self.stream.close()
|
||||
|
||||
|
||||
class _AnthropicMessages:
|
||||
def __init__(self, stream: _BlockingStream) -> None:
|
||||
self.stream_obj = stream
|
||||
|
||||
def stream(self, **_kwargs: Any) -> _AnthropicStreamContext:
|
||||
return _AnthropicStreamContext(self.stream_obj)
|
||||
|
||||
|
||||
class _AnthropicRealClient:
|
||||
def __init__(self, stream: _BlockingStream) -> None:
|
||||
self.messages = _AnthropicMessages(stream)
|
||||
self.stream = stream
|
||||
self.closed = threading.Event()
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed.set()
|
||||
self.stream.close()
|
||||
|
||||
|
||||
class _BedrockRuntimeClient:
|
||||
def __init__(self, started: threading.Event, release: threading.Event) -> None:
|
||||
self.started = started
|
||||
self.release = release
|
||||
self.closed = threading.Event()
|
||||
|
||||
def converse(self, **_kwargs: Any) -> dict[str, Any]:
|
||||
self.started.set()
|
||||
self.release.wait(timeout=5)
|
||||
return {
|
||||
"output": {
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": [{"text": "cancelled response"}],
|
||||
}
|
||||
},
|
||||
"usage": {"inputTokens": 1, "outputTokens": 1, "totalTokens": 2},
|
||||
"stopReason": "end_turn",
|
||||
}
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed.set()
|
||||
|
||||
|
||||
def _cancel_silent_request(
|
||||
client: Any,
|
||||
started: threading.Event,
|
||||
invoke: Callable[[Any], Any],
|
||||
) -> tuple[BaseException, float]:
|
||||
cancel_event = threading.Event()
|
||||
result: dict[str, BaseException] = {}
|
||||
|
||||
def _worker() -> None:
|
||||
try:
|
||||
with aux.aux_interrupt_protection(cancel_event=cancel_event):
|
||||
invoke(client)
|
||||
except BaseException as exc:
|
||||
result["exc"] = exc
|
||||
|
||||
worker = threading.Thread(target=_worker, daemon=True)
|
||||
worker.start()
|
||||
assert started.wait(timeout=1), "request never entered its silent transport"
|
||||
cancelled_at = time.monotonic()
|
||||
cancel_event.set()
|
||||
worker.join(timeout=1)
|
||||
elapsed = time.monotonic() - cancelled_at
|
||||
assert not worker.is_alive(), "explicit cancellation did not wake the silent request"
|
||||
return result["exc"], elapsed
|
||||
|
||||
|
||||
def _invoke_generic(client: Any) -> Any:
|
||||
return aux._relay_sync_completion(
|
||||
client,
|
||||
{"model": "test", "messages": [], "timeout": 30},
|
||||
create=lambda request: aux._create_with_progress(
|
||||
client, request, "compression", force_stream=True
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_protected_silent_provider_is_isolated_and_raises_frozen_explicit_cancel() -> None:
|
||||
started = threading.Event()
|
||||
stream = _BlockingStream(started)
|
||||
client = _GenericClient(stream)
|
||||
|
||||
exc, elapsed = _cancel_silent_request(client, started, _invoke_generic)
|
||||
|
||||
assert isinstance(exc, aux.AuxiliaryExplicitCancellation)
|
||||
assert exc.cause == "explicit_host_cancel"
|
||||
assert not client.closed.is_set()
|
||||
assert elapsed < 0.75
|
||||
stream.close() # release the bounded daemon provider worker
|
||||
|
||||
|
||||
def test_codex_silent_stream_is_isolated_without_closing_shared_client() -> None:
|
||||
started = threading.Event()
|
||||
stream = _BlockingStream(started)
|
||||
real_client = _CodexRealClient(stream)
|
||||
client = aux.CodexAuxiliaryClient(real_client, "gpt-test")
|
||||
|
||||
exc, elapsed = _cancel_silent_request(client, started, _invoke_generic)
|
||||
|
||||
assert isinstance(exc, aux.AuxiliaryExplicitCancellation)
|
||||
assert not real_client.closed.is_set()
|
||||
assert elapsed < 0.75
|
||||
stream.close()
|
||||
|
||||
|
||||
def test_cancelled_codex_orphan_timeout_preserves_cached_shared_client() -> None:
|
||||
"""A cancelled Codex worker's delayed timer owns only its event stream."""
|
||||
owner_started = threading.Event()
|
||||
|
||||
class _SilentOwnerStream:
|
||||
def __init__(self) -> None:
|
||||
self.closed = threading.Event()
|
||||
|
||||
def __iter__(self):
|
||||
owner_started.set()
|
||||
self.closed.wait(timeout=5)
|
||||
raise RuntimeError("owner stream closed")
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed.set()
|
||||
|
||||
class _SuccessStream:
|
||||
def __iter__(self):
|
||||
message = SimpleNamespace(
|
||||
type="message",
|
||||
content=[SimpleNamespace(type="output_text", text="ok")],
|
||||
)
|
||||
return iter(
|
||||
[
|
||||
SimpleNamespace(type="response.output_item.done", item=message),
|
||||
SimpleNamespace(
|
||||
type="response.completed",
|
||||
response=SimpleNamespace(
|
||||
status="completed", id="success", usage=None
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
def close(self) -> None:
|
||||
pass
|
||||
|
||||
owner_stream = _SilentOwnerStream()
|
||||
|
||||
class _SharedResponses:
|
||||
def __init__(self, real_client: Any) -> None:
|
||||
self.real_client = real_client
|
||||
|
||||
def create(self, **kwargs: Any) -> Any:
|
||||
if self.real_client.closed.is_set():
|
||||
raise RuntimeError("shared client was closed")
|
||||
if kwargs["model"] == "owner":
|
||||
return owner_stream
|
||||
return _SuccessStream()
|
||||
|
||||
class _SharedRealClient:
|
||||
def __init__(self) -> None:
|
||||
self.closed = threading.Event()
|
||||
self.api_key = "test"
|
||||
self.base_url = "https://example.test/codex"
|
||||
self.responses = _SharedResponses(self)
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed.set()
|
||||
owner_stream.close()
|
||||
|
||||
real_client = _SharedRealClient()
|
||||
wrapper = aux.CodexAuxiliaryClient(real_client, "gpt-test")
|
||||
cache_key = ("openai-codex", False, None, None, None)
|
||||
cancel_event = threading.Event()
|
||||
owner_outcome: dict[str, BaseException] = {}
|
||||
|
||||
def _run_owner() -> None:
|
||||
try:
|
||||
with aux.aux_interrupt_protection(cancel_event=cancel_event):
|
||||
aux._relay_sync_completion(
|
||||
wrapper,
|
||||
{"model": "owner", "messages": [], "timeout": 0.12},
|
||||
)
|
||||
except BaseException as exc:
|
||||
owner_outcome["exc"] = exc
|
||||
|
||||
with aux._client_cache_lock:
|
||||
aux._client_cache.clear()
|
||||
aux._client_cache[cache_key] = (wrapper, "gpt-test", None)
|
||||
owner = threading.Thread(target=_run_owner, daemon=True)
|
||||
try:
|
||||
owner.start()
|
||||
assert owner_started.wait(timeout=1)
|
||||
cancel_event.set()
|
||||
owner.join(timeout=1)
|
||||
assert not owner.is_alive()
|
||||
assert isinstance(owner_outcome["exc"], aux.AuxiliaryExplicitCancellation)
|
||||
# A real frontend clears the reusable host Event when the next turn
|
||||
# starts. The orphan must retain a frozen per-attempt cancellation cause.
|
||||
cancel_event.clear()
|
||||
|
||||
# A second user can use the shared client while the cancelled provider
|
||||
# worker is still orphaned and its total-timeout timer is still armed.
|
||||
assert not owner_stream.closed.is_set()
|
||||
concurrent = aux._relay_sync_completion(
|
||||
wrapper,
|
||||
{"model": "concurrent", "messages": [], "timeout": 1},
|
||||
)
|
||||
assert concurrent.choices[0].message.content == "ok"
|
||||
|
||||
# Let the orphan's real adapter timer fire. It may close the attempt's
|
||||
# event stream to wake that worker, but never the process-shared client.
|
||||
assert owner_stream.closed.wait(timeout=1)
|
||||
time.sleep(0.03)
|
||||
assert not real_client.closed.is_set()
|
||||
with aux._client_cache_lock:
|
||||
assert aux._client_cache[cache_key][0] is wrapper
|
||||
|
||||
successive = aux._relay_sync_completion(
|
||||
wrapper,
|
||||
{"model": "successive", "messages": [], "timeout": 1},
|
||||
)
|
||||
assert successive.choices[0].message.content == "ok"
|
||||
finally:
|
||||
owner_stream.close()
|
||||
with aux._client_cache_lock:
|
||||
aux._client_cache.clear()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("winner", ["timeout", "cancel"])
|
||||
def test_codex_timeout_and_explicit_cancel_have_one_linearized_outcome(
|
||||
winner: str,
|
||||
) -> None:
|
||||
"""Timeout and explicit cancel can never produce a mixed owner/cleanup result."""
|
||||
timer_read_started = threading.Event()
|
||||
allow_timer_read_return = threading.Event()
|
||||
request_cancelled = threading.Event()
|
||||
stream_started = threading.Event()
|
||||
|
||||
class _RacingCancelSource:
|
||||
def is_set(self) -> bool:
|
||||
if winner == "timeout" and threading.current_thread().name.startswith(
|
||||
"Thread-"
|
||||
):
|
||||
# Take the timer's false snapshot, then hold it at the exact seam
|
||||
# where the historical implementation could race owner polling.
|
||||
was_set = request_cancelled.is_set()
|
||||
timer_read_started.set()
|
||||
assert allow_timer_read_return.wait(timeout=1)
|
||||
return was_set
|
||||
return request_cancelled.is_set()
|
||||
|
||||
class _SilentStream:
|
||||
def __init__(self) -> None:
|
||||
self.closed = threading.Event()
|
||||
|
||||
def __iter__(self):
|
||||
stream_started.set()
|
||||
self.closed.wait(timeout=5)
|
||||
raise RuntimeError("stream closed")
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed.set()
|
||||
|
||||
stream = _SilentStream()
|
||||
|
||||
class _RealClient:
|
||||
def __init__(self) -> None:
|
||||
self.api_key = "test"
|
||||
self.base_url = "https://example.test/codex"
|
||||
self.responses = SimpleNamespace(create=lambda **_kwargs: stream)
|
||||
self.closed = threading.Event()
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed.set()
|
||||
stream.close()
|
||||
|
||||
real_client: Any = _RealClient()
|
||||
wrapper = aux.CodexAuxiliaryClient(real_client, "gpt-test")
|
||||
owner_outcome: dict[str, BaseException] = {}
|
||||
|
||||
def _run_owner() -> None:
|
||||
try:
|
||||
with aux.aux_interrupt_protection(cancel_event=_RacingCancelSource()):
|
||||
aux._relay_sync_completion(
|
||||
wrapper,
|
||||
{"model": "owner", "messages": [], "timeout": 0.08},
|
||||
)
|
||||
except BaseException as exc:
|
||||
owner_outcome["exc"] = exc
|
||||
|
||||
owner = threading.Thread(target=_run_owner, name="race-owner", daemon=True)
|
||||
owner.start()
|
||||
assert stream_started.wait(timeout=1)
|
||||
if winner == "timeout":
|
||||
assert timer_read_started.wait(timeout=1)
|
||||
request_cancelled.set()
|
||||
allow_timer_read_return.set()
|
||||
else:
|
||||
request_cancelled.set()
|
||||
owner.join(timeout=1)
|
||||
|
||||
assert not owner.is_alive()
|
||||
if winner == "timeout":
|
||||
assert real_client.closed.is_set()
|
||||
assert isinstance(owner_outcome["exc"], TimeoutError)
|
||||
assert not isinstance(owner_outcome["exc"], aux.AuxiliaryExplicitCancellation)
|
||||
else:
|
||||
assert isinstance(owner_outcome["exc"], aux.AuxiliaryExplicitCancellation)
|
||||
assert stream.closed.wait(timeout=1), "cancelled timer did not wake its stream"
|
||||
assert not real_client.closed.is_set()
|
||||
|
||||
|
||||
def test_anthropic_silent_stream_is_isolated_without_closing_shared_client() -> None:
|
||||
started = threading.Event()
|
||||
stream = _BlockingStream(started)
|
||||
real_client = _AnthropicRealClient(stream)
|
||||
client = aux.AnthropicAuxiliaryClient(
|
||||
real_client,
|
||||
"claude-test",
|
||||
"test-key",
|
||||
"https://api.anthropic.test",
|
||||
)
|
||||
|
||||
exc, elapsed = _cancel_silent_request(client, started, _invoke_generic)
|
||||
|
||||
assert isinstance(exc, aux.AuxiliaryExplicitCancellation)
|
||||
assert not real_client.closed.is_set()
|
||||
assert elapsed < 0.75
|
||||
stream.close()
|
||||
|
||||
|
||||
def test_cancelled_attempt_does_not_close_or_fail_concurrent_shared_client_call(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
a_started = threading.Event()
|
||||
a_release = threading.Event()
|
||||
b_started = threading.Event()
|
||||
b_release = threading.Event()
|
||||
closed = threading.Event()
|
||||
|
||||
class _SharedCompletions:
|
||||
def create(self, **kwargs: Any) -> Any:
|
||||
if kwargs["model"] == "session-a":
|
||||
a_started.set()
|
||||
a_release.wait(timeout=5)
|
||||
else:
|
||||
b_started.set()
|
||||
b_release.wait(timeout=5)
|
||||
if closed.is_set():
|
||||
raise RuntimeError("shared client was closed")
|
||||
return SimpleNamespace(
|
||||
choices=[SimpleNamespace(message=SimpleNamespace(content="ok"))]
|
||||
)
|
||||
|
||||
client = SimpleNamespace(
|
||||
chat=SimpleNamespace(completions=_SharedCompletions()),
|
||||
close=lambda: closed.set(),
|
||||
)
|
||||
cancel_event = threading.Event()
|
||||
outcomes: dict[str, Any] = {}
|
||||
evictions: list[Any] = []
|
||||
monkeypatch.setattr(
|
||||
aux, "_evict_cached_client_instance", lambda value: evictions.append(value)
|
||||
)
|
||||
|
||||
def _session_a() -> None:
|
||||
try:
|
||||
with aux.aux_interrupt_protection(cancel_event=cancel_event):
|
||||
aux._relay_sync_completion(
|
||||
client, {"model": "session-a", "messages": [], "timeout": 30}
|
||||
)
|
||||
except BaseException as exc:
|
||||
outcomes["a"] = exc
|
||||
|
||||
def _session_b() -> None:
|
||||
try:
|
||||
outcomes["b"] = aux._relay_sync_completion(
|
||||
client, {"model": "session-b", "messages": [], "timeout": 30}
|
||||
)
|
||||
except BaseException as exc: # pragma: no cover - asserted below
|
||||
outcomes["b"] = exc
|
||||
|
||||
a_thread = threading.Thread(target=_session_a, daemon=True)
|
||||
b_thread = threading.Thread(target=_session_b, daemon=True)
|
||||
a_thread.start()
|
||||
b_thread.start()
|
||||
assert a_started.wait(timeout=1)
|
||||
assert b_started.wait(timeout=1)
|
||||
cancel_event.set()
|
||||
a_thread.join(timeout=1)
|
||||
try:
|
||||
assert not a_thread.is_alive()
|
||||
assert isinstance(outcomes["a"], aux.AuxiliaryExplicitCancellation)
|
||||
assert not closed.is_set()
|
||||
assert evictions == []
|
||||
b_release.set()
|
||||
b_thread.join(timeout=1)
|
||||
assert not b_thread.is_alive()
|
||||
assert not isinstance(outcomes["b"], BaseException)
|
||||
assert outcomes["b"].choices[0].message.content == "ok"
|
||||
finally:
|
||||
a_release.set()
|
||||
b_release.set()
|
||||
|
||||
|
||||
def test_bedrock_silent_nonstream_request_is_isolated_without_close_wakeup() -> None:
|
||||
from agent.bedrock_adapter import _bedrock_runtime_client_cache, reset_client_cache
|
||||
|
||||
started = threading.Event()
|
||||
release = threading.Event()
|
||||
runtime_client = _BedrockRuntimeClient(started, release)
|
||||
reset_client_cache()
|
||||
_bedrock_runtime_client_cache["us-test-1"] = runtime_client
|
||||
client = aux.BedrockAuxiliaryClient("us-test-1", "bedrock-test")
|
||||
try:
|
||||
exc, elapsed = _cancel_silent_request(client, started, _invoke_generic)
|
||||
finally:
|
||||
release.set()
|
||||
reset_client_cache()
|
||||
|
||||
assert isinstance(exc, aux.AuxiliaryExplicitCancellation)
|
||||
assert not runtime_client.closed.is_set()
|
||||
assert elapsed < 0.75
|
||||
|
||||
|
||||
def test_unprotected_sync_completion_stays_on_calling_thread() -> None:
|
||||
caller = threading.get_ident()
|
||||
observed: list[int] = []
|
||||
client = SimpleNamespace(
|
||||
chat=SimpleNamespace(
|
||||
completions=SimpleNamespace(
|
||||
create=lambda **_kwargs: (
|
||||
observed.append(threading.get_ident()),
|
||||
SimpleNamespace(choices=[]),
|
||||
)[1]
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
aux._relay_sync_completion(client, {"model": "test", "messages": []})
|
||||
|
||||
assert observed == [caller]
|
||||
|
||||
|
||||
def test_isolated_provider_worker_inherits_protection_and_progress_hook() -> None:
|
||||
caller = threading.get_ident()
|
||||
cancel_event = threading.Event()
|
||||
progress: list[str] = []
|
||||
observed: dict[str, Any] = {}
|
||||
|
||||
def _create(**_kwargs: Any) -> Any:
|
||||
observed["thread"] = threading.get_ident()
|
||||
observed["protected"] = aux._aux_interrupt_protected()
|
||||
aux._notify_aux_progress()
|
||||
return SimpleNamespace(choices=[])
|
||||
|
||||
client = SimpleNamespace(
|
||||
chat=SimpleNamespace(completions=SimpleNamespace(create=_create))
|
||||
)
|
||||
with aux.aux_progress_hook(lambda: progress.append("tick")), aux.aux_interrupt_protection(
|
||||
cancel_event=cancel_event
|
||||
):
|
||||
aux._relay_sync_completion(client, {"model": "test", "messages": []})
|
||||
|
||||
assert observed["protected"] is True
|
||||
assert observed["thread"] != caller
|
||||
assert progress == ["tick"]
|
||||
|
||||
|
||||
def test_isolated_provider_worker_inherits_caller_contextvars() -> None:
|
||||
from tools.approval import (
|
||||
get_current_session_key,
|
||||
reset_current_session_key,
|
||||
set_current_session_key,
|
||||
)
|
||||
|
||||
arbitrary = contextvars.ContextVar("isolated-provider-test", default="missing")
|
||||
arbitrary_token = arbitrary.set("caller-value")
|
||||
session_token = set_current_session_key("session-from-caller")
|
||||
observed: dict[str, str] = {}
|
||||
client = SimpleNamespace(
|
||||
chat=SimpleNamespace(
|
||||
completions=SimpleNamespace(
|
||||
create=lambda **_kwargs: (
|
||||
observed.update(
|
||||
arbitrary=arbitrary.get(),
|
||||
session_key=get_current_session_key(),
|
||||
),
|
||||
SimpleNamespace(choices=[]),
|
||||
)[1]
|
||||
)
|
||||
)
|
||||
)
|
||||
try:
|
||||
with aux.aux_interrupt_protection(cancel_event=threading.Event()):
|
||||
aux._relay_sync_completion(client, {"model": "test", "messages": []})
|
||||
finally:
|
||||
reset_current_session_key(session_token)
|
||||
arbitrary.reset(arbitrary_token)
|
||||
|
||||
assert observed == {
|
||||
"arbitrary": "caller-value",
|
||||
"session_key": "session-from-caller",
|
||||
}
|
||||
|
||||
|
||||
def test_hard_cancel_wins_when_provider_result_is_published_in_same_race() -> None:
|
||||
cancel_event = threading.Event()
|
||||
|
||||
def _create(**_kwargs: Any) -> Any:
|
||||
cancel_event.set()
|
||||
return SimpleNamespace(choices=[])
|
||||
|
||||
client = SimpleNamespace(
|
||||
chat=SimpleNamespace(completions=SimpleNamespace(create=_create))
|
||||
)
|
||||
with aux.aux_interrupt_protection(cancel_event=cancel_event):
|
||||
with pytest.raises(aux.AuxiliaryExplicitCancellation):
|
||||
aux._relay_sync_completion(client, {"model": "test", "messages": []})
|
||||
|
||||
|
||||
def test_unrelated_interrupted_error_is_not_reclassified_as_explicit_cancel() -> None:
|
||||
client = SimpleNamespace(
|
||||
chat=SimpleNamespace(
|
||||
completions=SimpleNamespace(
|
||||
create=lambda **_kwargs: (_ for _ in ()).throw(
|
||||
InterruptedError("provider syscall interrupted")
|
||||
)
|
||||
)
|
||||
),
|
||||
close=lambda: None,
|
||||
)
|
||||
|
||||
with aux.aux_interrupt_protection(cancel_event=threading.Event()):
|
||||
with pytest.raises(InterruptedError, match="provider syscall interrupted") as caught:
|
||||
aux._relay_sync_completion(client, {"model": "test", "messages": []})
|
||||
|
||||
assert not isinstance(caught.value, aux.AuxiliaryExplicitCancellation)
|
||||
Reference in New Issue
Block a user