Files
wehub-resource-sync 0ef5fcb1c5
Security / Dependency audit (pip-audit) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled
Security / Secret scan (gitleaks) (push) Has been cancelled
rust / test (ubuntu) (push) Has been cancelled
rust / simulator e2e (macos-latest) (push) Has been cancelled
rust / simulator e2e (ubuntu-latest) (push) Has been cancelled
rust / simulator e2e (windows-latest) (push) Has been cancelled
rust / wheels (aarch64-apple-darwin) (push) Has been cancelled
rust / wheels (x86_64-unknown-linux-gnu) (push) Has been cancelled
rust / wheels (x86_64-apple-darwin) (push) Has been cancelled
rust / audit (push) Has been cancelled
rust / parity (nightly, allowed to fail during Phase 0) (push) Has been cancelled
CI / commitlint (push) Has been skipped
Dev Containers / validate (.devcontainer/devcontainer.json, default) (push) Failing after 0s
Dev Containers / validate (.devcontainer/memory-stack/devcontainer.json, memory-stack) (push) Failing after 0s
Dev Containers / validate-worktree (push) Failing after 0s
CI / changes (push) Failing after 4s
Deploy Documentation / validate (push) Has been skipped
Deploy Documentation / deploy (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, claude) (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, codex) (push) Failing after 1s
Install Native E2E / install-native (ubuntu-latest) (push) Failing after 1s
OpenCode Plugin / typecheck + build + test (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, copilot) (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
Wrap E2E / docker-wrap-e2e (push) Failing after 1s
Wrap Native E2E / wrap-native (ubuntu-latest) (push) Failing after 1s
Init E2E / docker-init-e2e (push) Failing after 4s
Merge Conflicts / merge-conflicts (push) Failing after 4s
CI / lint (push) Has been cancelled
CI / build-wheel (push) Has been cancelled
CI / build-wheel-windows (push) Has been cancelled
CI / prefetch-model (push) Has been cancelled
CI / test-dashboard-ui (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test-extras (push) Has been cancelled
CI / test-agno (push) Has been cancelled
CI / build (push) Has been cancelled
CI / workflow-validation (push) Has been cancelled
CI / docker-native-e2e (push) Has been cancelled
CI / windows-native-wrapper (push) Has been cancelled
CI / macos-native-wrapper (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / promote-latest (push) Has been cancelled
Init Native E2E / init-native (macos-latest, claude) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, codex) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, copilot) (push) Has been cancelled
Install Native E2E / install-native (macos-latest) (push) Has been cancelled
Wrap Native E2E / wrap-native (macos-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:03:20 +08:00

145 lines
5.4 KiB
Python

"""Off-path background compression (Phase 3, #1171).
The request path must never block on ML compression. When a cold-start-large
request would otherwise run kompress synchronously under the 30s budget (and
leak a non-preemptible worker on timeout -> executor saturation -> cascade),
it instead forwards the already-cached/uncompressed messages immediately and
enqueues the compression here. A single per-process drain runs it with NO
request-coupled deadline and stores the result in the session
``CompressionCache``, so the next turn is a cache hit and the forwarded bytes
become (and stay) the compressed form.
This is per-process by design: ``CompressionCache`` is already per-process
(``HeadroomProxy._compression_caches``), and multi-worker deployments are
already warned to use ``--workers 1`` or sticky sessions, so a per-process
drain matches the existing cache semantics without any new cross-process lock.
Limitations, all fail-open (lost savings, never lost correctness): only the
token-mode cold-start path defers here -- other modes compress synchronously;
the queue is in-memory, so a restart mid-drain drops queued jobs (they re-defer
on a later turn); and a full queue or a duplicate in-flight key drops the job,
surfaced to telemetry as ``deferred:dropped``. Background work is bounded by the
Phase 1 kompress deadline (a non-terminating compressor would pin the single
drain thread, but the compressors terminate).
"""
from __future__ import annotations
import asyncio
import logging
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any
logger = logging.getLogger(__name__)
@dataclass
class _Job:
key: str
compress: Callable[[], Any] # sync callable, runs in the executor (no timeout)
store: Callable[[Any], None] # sync callable, stores the result into the cache
class BackgroundCompressor:
"""Single per-process async drain that compresses enqueued work off the
request path, with no request-coupled deadline.
``run_in_executor`` is injected so the drain reuses the proxy's compression
ThreadPoolExecutor (without the request-path ``asyncio.wait_for`` timeout),
and so tests can supply a trivial runner.
"""
def __init__(
self,
run_in_executor: Callable[[Callable[[], Any]], Awaitable[Any]],
*,
max_queue: int = 256,
) -> None:
self._run_in_executor = run_in_executor
self._queue: asyncio.Queue[_Job] = asyncio.Queue(maxsize=max_queue)
self._pending: set[str] = set()
self._task: asyncio.Task[None] | None = None
self._processed = 0
self._dropped = 0
self._errors = 0
def enqueue(
self,
key: str,
compress: Callable[[], Any],
store: Callable[[Any], None],
) -> bool:
"""Queue a compression job. Returns False (and drops) if the key is
already in flight or the queue is full -- both are safe: the request
has already been forwarded uncompressed, so a drop just defers the
savings to a later turn."""
if key in self._pending:
return False # already queued / in flight -- dedup
# Claim the slot BEFORE the job is observable in the queue so dedup is
# atomic against another enqueue of the same key.
self._pending.add(key)
try:
self._queue.put_nowait(_Job(key, compress, store))
except asyncio.QueueFull:
self._pending.discard(key)
self._dropped += 1
logger.warning(
"background compression queue full (%d); dropping %s "
"(request already forwarded uncompressed)",
self._queue.maxsize,
key,
)
return False
return True
async def _process_one(self, job: _Job) -> None:
try:
result = await self._run_in_executor(job.compress)
job.store(result)
self._processed += 1
except Exception as e: # noqa: BLE001 -- fail-open: request already went out uncompressed
self._errors += 1
logger.warning("background compression failed for %s: %s", job.key, e)
finally:
self._pending.discard(job.key)
async def _drain(self) -> None:
while True:
job = await self._queue.get()
try:
await self._process_one(job)
finally:
self._queue.task_done()
async def start(self) -> None:
if self._task is None or self._task.done():
self._task = asyncio.create_task(self._drain(), name="headroom-bg-compress")
async def stop(self, *, drain: bool = True, timeout: float = 5.0) -> None:
if self._task is None:
return
if drain:
try:
await asyncio.wait_for(self._queue.join(), timeout=timeout)
except asyncio.TimeoutError:
logger.warning(
"background compression drain timed out with %d queued",
self._queue.qsize(),
)
self._task.cancel()
try:
await self._task
except asyncio.CancelledError:
pass
self._task = None
def stats(self) -> dict[str, int]:
return {
"queued": self._queue.qsize(),
"pending": len(self._pending),
"processed": self._processed,
"dropped": self._dropped,
"errors": self._errors,
}