Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
40 lines
1.1 KiB
Python
40 lines
1.1 KiB
Python
import logging
|
|
import threading
|
|
from contextlib import contextmanager
|
|
from typing import Dict, Iterator
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_LIMITERS: Dict[str, threading.BoundedSemaphore] = {}
|
|
_LIMITERS_LOCK = threading.Lock()
|
|
|
|
|
|
def _get_limiter(name: str, max_workers: int) -> threading.BoundedSemaphore:
|
|
with _LIMITERS_LOCK:
|
|
limiter = _LIMITERS.get(name)
|
|
if limiter is None:
|
|
limiter = threading.BoundedSemaphore(max_workers)
|
|
_LIMITERS[name] = limiter
|
|
return limiter
|
|
|
|
|
|
@contextmanager
|
|
def parser_worker_limit(name: str, max_workers: int) -> Iterator[None]:
|
|
"""Limit concurrent access to heavy, process-wide parser operations.
|
|
|
|
Set max_workers <= 0 to disable throttling for deployments that have enough
|
|
CPU/GPU resources and know the parser backend is safe under concurrency.
|
|
"""
|
|
|
|
if max_workers <= 0:
|
|
yield
|
|
return
|
|
|
|
limiter = _get_limiter(name, max_workers)
|
|
logger.debug("Waiting for %s parser slot (max_workers=%d)", name, max_workers)
|
|
limiter.acquire()
|
|
try:
|
|
yield
|
|
finally:
|
|
limiter.release()
|