diff --git a/README.md b/README.md index f463cfd..33e0c43 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,9 @@ Webhook( ``` Inject a shared `session` for throughput (one session per process); without one, each -send opens and closes its own. +send opens and closes its own. A single `Webhook` instance is safe to drive from many +concurrent `send()` calls — each call tracks its own attempt count, so concurrent sends +don't corrupt each other's `WebhookResult.attempts`. ## WebhookResult diff --git a/src/aiowebhooks/sender.py b/src/aiowebhooks/sender.py index a926bcb..ee8f81b 100644 --- a/src/aiowebhooks/sender.py +++ b/src/aiowebhooks/sender.py @@ -136,10 +136,10 @@ class Webhook: synthetic status-0). proxy rotation on connection errors lives inside the attempt and is capped separately. """ - self._attempt_no = 0 + counter = [0] try: return await aretry( - lambda: self._attempt(session, url, payload), + lambda: self._attempt(session, url, payload, counter), attempts=self.max_retries + 1, on=(_Retryable,), ) @@ -147,7 +147,7 @@ class Webhook: return exhausted.result async def _attempt( - self, session: aiohttp.ClientSession, url: str, payload: Dict + self, session: aiohttp.ClientSession, url: str, payload: Dict, counter: List[int] ) -> WebhookResult: """one logical send: proxy rotation + a single POST; may raise _Retryable @@ -155,14 +155,18 @@ class Webhook: aretry applies backoff; honors an explicit 429 retry_after by sleeping it before signalling. returns a final WebhookResult on success or a terminal (non-retryable) failure — never lets a provider/connection error escape. + + `counter` is a per-call mutable cell ([0]) owned by the calling `_send_loop`, + so the attempt count is local to one `send()` and concurrent sends on the + same instance don't corrupt each other's tally. """ timeout = aiohttp.ClientTimeout(total=self.timeout) last_proxy: Optional[str] = None proxy_tries = 0 while True: - self._attempt_no += 1 - attempts = self._attempt_no + counter[0] += 1 + attempts = counter[0] proxy_url = None if self._proxies is not None: try: