vercel-queue 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. vercel/queue/__init__.py +8 -0
  2. vercel/queue/__main__.py +10 -0
  3. vercel/queue/_internal/__init__.py +1 -0
  4. vercel/queue/_internal/api_async.py +219 -0
  5. vercel/queue/_internal/api_common.py +104 -0
  6. vercel/queue/_internal/api_sync.py +137 -0
  7. vercel/queue/_internal/asgi.py +145 -0
  8. vercel/queue/_internal/asynctools.py +40 -0
  9. vercel/queue/_internal/cli.py +313 -0
  10. vercel/queue/_internal/client.py +1109 -0
  11. vercel/queue/_internal/client_sync.py +432 -0
  12. vercel/queue/_internal/config.py +160 -0
  13. vercel/queue/_internal/constants.py +50 -0
  14. vercel/queue/_internal/devserver.py +205 -0
  15. vercel/queue/_internal/embedded.py +1900 -0
  16. vercel/queue/_internal/errors.py +262 -0
  17. vercel/queue/_internal/http.py +634 -0
  18. vercel/queue/_internal/lease.py +1007 -0
  19. vercel/queue/_internal/log.py +143 -0
  20. vercel/queue/_internal/messages.py +122 -0
  21. vercel/queue/_internal/multipart.py +255 -0
  22. vercel/queue/_internal/names.py +101 -0
  23. vercel/queue/_internal/polling.py +200 -0
  24. vercel/queue/_internal/push.py +246 -0
  25. vercel/queue/_internal/response.py +111 -0
  26. vercel/queue/_internal/retry.py +86 -0
  27. vercel/queue/_internal/streams.py +313 -0
  28. vercel/queue/_internal/subscribers.py +1025 -0
  29. vercel/queue/_internal/transports.py +363 -0
  30. vercel/queue/_internal/types.py +300 -0
  31. vercel/queue/_internal/typeutils.py +203 -0
  32. vercel/queue/devserver.py +24 -0
  33. vercel/queue/embedded.py +50 -0
  34. vercel/queue/py.typed +1 -0
  35. vercel/queue/sync.py +8 -0
  36. vercel/queue/testing/__init__.py +14 -0
  37. vercel/queue/testing/pytest.py +42 -0
  38. vercel/queue/testing/state.py +32 -0
  39. vercel/queue/version.py +3 -0
  40. vercel_queue-0.7.0.dist-info/METADATA +681 -0
  41. vercel_queue-0.7.0.dist-info/RECORD +43 -0
  42. vercel_queue-0.7.0.dist-info/WHEEL +4 -0
  43. vercel_queue-0.7.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,1007 @@
1
+ """Lease lifecycle helpers for queue message processing.
2
+
3
+ Automatic lease renewal is coordinated through one process-wide daemon thread
4
+ running an AnyIO worker. Public sync and async clients enqueue renewal commands
5
+ into an AnyIO memory stream owned by that worker, while the worker runs one
6
+ AnyIO task per active renewal.
7
+
8
+ Registration waits until the worker records the renewal and schedules its task,
9
+ but it does not wait for the first lease extension to complete. This surfaces
10
+ delivery failures at handler startup without coupling startup to renewal
11
+ network I/O. Fire-and-forget stop paths are still logged asynchronously.
12
+
13
+ Stopping a renewal removes future work and cancels any in-flight extension. The
14
+ RetryAfter path waits for that cancellation so a late automatic renewal cannot
15
+ overwrite the handler's explicit visibility update, while acknowledge paths can
16
+ avoid that tail latency because late renewals are harmless after ACK.
17
+
18
+ Lease renewal invariants:
19
+
20
+ - A renewal handle is single-use and idempotently stoppable.
21
+ - Start waits for worker scheduling, not lease extension I/O.
22
+ - Stop with wait=True waits for in-flight renewal cancellation or completion.
23
+ - At most one renewal for a token may be in flight at a time.
24
+ - Command submission uses a large bounded AnyIO memory stream.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from typing import Any, NewType, Protocol, TypeAlias
30
+ from typing_extensions import Self
31
+
32
+ import logging
33
+ import math
34
+ import threading
35
+ import time
36
+ from collections import OrderedDict
37
+ from collections.abc import Callable, Coroutine, Iterator, MutableMapping
38
+ from dataclasses import dataclass
39
+ from datetime import datetime, timezone
40
+ from types import TracebackType
41
+
42
+ import anyio
43
+ import anyio.from_thread
44
+ from anyio import to_thread
45
+ from anyio.abc import ObjectReceiveStream, ObjectSendStream, TaskGroup
46
+ from anyio.lowlevel import EventLoopToken, current_token
47
+ from anyio.streams.memory import MemoryObjectSendStream
48
+
49
+ from vercel.oidc import get_vercel_oidc_token_sync
50
+
51
+ from .asynctools import iter_coroutine
52
+ from .errors import MessageNotFoundError, QueueError, ThrottledError
53
+ from .log import debug_log
54
+ from .retry import retry_async_follow_up, retry_sync_follow_up
55
+ from .streams import (
56
+ AsyncStreamPayload,
57
+ AsyncTextStreamPayload,
58
+ SyncStreamPayload,
59
+ SyncTextStreamPayload,
60
+ )
61
+ from .types import (
62
+ Duration,
63
+ Message,
64
+ MessageMetadata,
65
+ duration_to_float_seconds,
66
+ duration_to_seconds,
67
+ )
68
+
69
+
70
+ class AsyncExtendMessageLease(Protocol):
71
+ def __call__(
72
+ self,
73
+ message: Message[Any] | MessageMetadata,
74
+ duration: Duration,
75
+ ) -> Coroutine[Any, Any, None]: ...
76
+
77
+
78
+ class ExtendMessageLease(Protocol):
79
+ def __call__(self, message: Message[Any] | MessageMetadata, duration: Duration) -> None: ...
80
+
81
+
82
+ class _LeaseStopEvent(Protocol):
83
+ def set(self) -> None: ...
84
+
85
+
86
+ DEFAULT_PROCESSING_LEASE_SECONDS = 300
87
+ MIN_PROCESSING_LEASE_SECONDS = 30
88
+ MAX_VISIBILITY_TIMEOUT_SECONDS = 3600
89
+ _TRANSIENT_LEASE_RETRY_SECONDS = 3.0
90
+ _DUE_RENEWAL_START_GRACE_SECONDS = 0.01
91
+ _IMMEDIATE_RENEWAL_GRACE_SECONDS = 10.0
92
+ _STOPPED_LEASE_TOKEN_CACHE_SIZE = 1024
93
+ _LEASE_STOP_WAIT_TIMEOUT_SECONDS = 5.0
94
+ _LEASE_START_WAIT_TIMEOUT_SECONDS = 5.0
95
+ _LEASE_COMMAND_BUFFER_SIZE = 65_536
96
+ _LeaseRenewalToken = NewType("_LeaseRenewalToken", object)
97
+ logger = logging.getLogger(__name__)
98
+
99
+
100
+ class LeaseAsyncClient(Protocol):
101
+ def _renew_lease(
102
+ self,
103
+ message: Message[Any],
104
+ duration: Duration,
105
+ ) -> Coroutine[Any, Any, None]: ...
106
+
107
+ def _extend_lease(
108
+ self,
109
+ message: Message[Any] | MessageMetadata,
110
+ duration: Duration,
111
+ ) -> Coroutine[Any, Any, None]: ...
112
+
113
+
114
+ @dataclass(kw_only=True)
115
+ class _LeaseExtensionRequest:
116
+ token: _LeaseRenewalToken
117
+ message: Message[Any]
118
+ client: LeaseAsyncClient
119
+ lease_seconds: int
120
+ next_extension_at: float
121
+
122
+
123
+ @dataclass(kw_only=True)
124
+ class _LeaseExtensionStart:
125
+ request: _LeaseExtensionRequest
126
+ scheduled_event: threading.Event | anyio.Event
127
+ event_loop_token: EventLoopToken | None = None
128
+
129
+
130
+ @dataclass(frozen=True, kw_only=True)
131
+ class _LeaseExtensionStop:
132
+ token: _LeaseRenewalToken
133
+ done_send: ObjectSendStream[None] | None = None
134
+ done_event: _LeaseStopEvent | None = None
135
+ event_loop_token: EventLoopToken | None = None
136
+
137
+
138
+ @dataclass(frozen=True)
139
+ class _LeaseWorkerShutdown:
140
+ pass
141
+
142
+
143
+ _LeaseExtensionCommand: TypeAlias = (
144
+ _LeaseExtensionStart | _LeaseExtensionStop | _LeaseWorkerShutdown
145
+ )
146
+
147
+
148
+ @dataclass(kw_only=True)
149
+ class _LeaseRenewalTask:
150
+ request: _LeaseExtensionRequest
151
+ cancel_scope: anyio.CancelScope
152
+ stop_waiters: list[_LeaseExtensionStop]
153
+
154
+
155
+ @dataclass(kw_only=True)
156
+ class _LeaseWorkerRuntimeState:
157
+ active: dict[_LeaseRenewalToken, _LeaseRenewalTask]
158
+ stopped: OrderedDict[_LeaseRenewalToken, None]
159
+
160
+
161
+ @dataclass(kw_only=True)
162
+ class _LeaseWorkerState:
163
+ command_send: MemoryObjectSendStream[_LeaseExtensionCommand] | None = None
164
+ worker_token: EventLoopToken | None = None
165
+ started: bool = False
166
+ thread: threading.Thread | None = None
167
+ startup_error: BaseException | None = None
168
+
169
+
170
+ _lease_worker_state = _LeaseWorkerState()
171
+ _lease_worker_ready = threading.Event()
172
+ _lease_worker_lock = threading.Lock()
173
+ _LEASE_WORKER_START_TIMEOUT_SECONDS = 5.0
174
+
175
+
176
+ def visibility_timeout_seconds(duration: Duration, *, name: str = "duration") -> int:
177
+ exact_seconds = duration_to_float_seconds(duration)
178
+ if not math.isfinite(exact_seconds):
179
+ raise ValueError(f"{name} must be finite")
180
+ if exact_seconds < 0:
181
+ raise ValueError(f"{name} must be non-negative")
182
+ if exact_seconds > MAX_VISIBILITY_TIMEOUT_SECONDS:
183
+ raise ValueError(f"{name} cannot exceed {MAX_VISIBILITY_TIMEOUT_SECONDS} seconds")
184
+ seconds = duration_to_seconds(duration)
185
+ if exact_seconds > 0 and seconds == 0:
186
+ raise ValueError(f"{name} must be at least 1 second or exactly 0")
187
+ return seconds
188
+
189
+
190
+ def processing_lease_seconds(duration: Duration | None, *, name: str = "lease_duration") -> int:
191
+ if duration is None:
192
+ return DEFAULT_PROCESSING_LEASE_SECONDS
193
+ seconds = visibility_timeout_seconds(duration, name=name)
194
+ if seconds == 0:
195
+ raise ValueError(f"{name} must be positive for automatic renewal")
196
+ return max(MIN_PROCESSING_LEASE_SECONDS, seconds)
197
+
198
+
199
+ def _next_extension_delay(metadata: MessageMetadata, lease_seconds: int) -> float:
200
+ if metadata.visibility_deadline is not None:
201
+ remaining = (metadata.visibility_deadline - datetime.now(timezone.utc)).total_seconds()
202
+ if remaining <= _IMMEDIATE_RENEWAL_GRACE_SECONDS:
203
+ return 0.0
204
+ return max(0.0, remaining - _IMMEDIATE_RENEWAL_GRACE_SECONDS)
205
+ return _renewal_interval_seconds(lease_seconds)
206
+
207
+
208
+ def _renewal_interval_seconds(visibility_timeout_seconds: float) -> float:
209
+ return min(60.0, max(10.0, visibility_timeout_seconds / 5))
210
+
211
+
212
+ def _next_extension_at(metadata: MessageMetadata, lease_seconds: int) -> float:
213
+ delay = _next_extension_delay(metadata, lease_seconds)
214
+ if delay <= 0.0:
215
+ delay = _DUE_RENEWAL_START_GRACE_SECONDS
216
+ return time.monotonic() + delay
217
+
218
+
219
+ class LeaseRenewal:
220
+ """Single-use context manager for keeping a message lease alive."""
221
+
222
+ def __init__(
223
+ self,
224
+ message: Message[Any],
225
+ client: LeaseAsyncClient,
226
+ lease_duration: Duration | None = None,
227
+ ) -> None:
228
+ self._message = message
229
+ self._client = client
230
+ self._lease_seconds = processing_lease_seconds(lease_duration)
231
+ self._token: _LeaseRenewalToken | None = None
232
+ self._entered = False
233
+ self._closed = False
234
+ self._closing = False
235
+
236
+ def __enter__(self) -> Self:
237
+ self.start()
238
+ return self
239
+
240
+ def start(self) -> None:
241
+ """Start renewing the lease."""
242
+ self._start_with_wait()
243
+
244
+ async def start_async(self) -> None:
245
+ """Start renewing the lease from async code."""
246
+ await self._start_with_wait_async()
247
+
248
+ def _start_with_wait(self) -> None:
249
+ request = self._prepare_start()
250
+ if request is None:
251
+ return
252
+ try:
253
+ _send_lease_extension_start(request)
254
+ except Exception:
255
+ self._reset_start_after_failure()
256
+ raise
257
+ self._log_start_result()
258
+
259
+ async def _start_with_wait_async(self) -> None:
260
+ request = self._prepare_start()
261
+ if request is None:
262
+ return
263
+ try:
264
+ await _send_lease_extension_start_async(request)
265
+ except BaseException:
266
+ await _send_best_effort_async_start_failure_stop(request.token)
267
+ self._reset_start_after_failure()
268
+ raise
269
+ self._log_start_result()
270
+
271
+ def _prepare_start(self) -> _LeaseExtensionRequest | None:
272
+ if self._entered:
273
+ raise RuntimeError("lease renewal handles are single-use")
274
+ if self._closed:
275
+ raise RuntimeError("lease renewal handle is closed")
276
+ if self._message.metadata.receipt_handle is None:
277
+ return None
278
+ self._entered = True
279
+ _prime_oidc_token_cache()
280
+ _ensure_lease_renewal_thread()
281
+ self._token = _LeaseRenewalToken(object())
282
+ return _LeaseExtensionRequest(
283
+ token=self._token,
284
+ message=self._message,
285
+ client=self._client,
286
+ lease_seconds=self._lease_seconds,
287
+ next_extension_at=_next_extension_at(self._message.metadata, self._lease_seconds),
288
+ )
289
+
290
+ def _reset_start_after_failure(self) -> None:
291
+ self._token = None
292
+ self._entered = False
293
+
294
+ def _log_start_result(self) -> None:
295
+ debug_log(
296
+ "lease.renewal_registered",
297
+ message_id=self._message.metadata.message_id,
298
+ topic=self._message.metadata.topic,
299
+ consumer_group=self._message.metadata.consumer_group,
300
+ lease_seconds=self._lease_seconds,
301
+ )
302
+
303
+ def __exit__(
304
+ self,
305
+ exc_type: type[BaseException] | None,
306
+ exc: BaseException | None,
307
+ traceback: TracebackType | None,
308
+ ) -> None:
309
+ self.stop()
310
+
311
+ def stop(self, *, wait: bool = True) -> None:
312
+ """Stop renewing the lease. Safe to call more than once."""
313
+ if self._closed or self._closing:
314
+ return
315
+ token = self._token
316
+ if token is None:
317
+ self._closed = True
318
+ return
319
+ self._closing = True
320
+ try:
321
+ stopped = self._send_stop(token, wait=wait)
322
+ except Exception:
323
+ self._closing = False
324
+ raise
325
+ self._token = None
326
+ self._closed = True
327
+ self._closing = False
328
+ self._log_stop_result(stopped=stopped)
329
+
330
+ async def stop_async(self, *, wait: bool = True) -> None:
331
+ """Stop renewing the lease without blocking the current event loop."""
332
+ if self._closed or self._closing:
333
+ return
334
+ token = self._token
335
+ if token is None:
336
+ self._closed = True
337
+ return
338
+ self._closing = True
339
+ try:
340
+ stopped = await self._send_stop_async(token, wait=wait)
341
+ except BaseException:
342
+ self._closing = False
343
+ raise
344
+ self._token = None
345
+ self._closed = True
346
+ self._closing = False
347
+ self._log_stop_result(stopped=stopped)
348
+
349
+ def _send_stop(self, token: _LeaseRenewalToken, *, wait: bool) -> bool:
350
+ if wait:
351
+ return _send_lease_extension_stop(token)
352
+ self._send_best_effort_stop(token)
353
+ return True
354
+
355
+ async def _send_stop_async(self, token: _LeaseRenewalToken, *, wait: bool) -> bool:
356
+ if wait:
357
+ return await _send_lease_extension_stop_async(token)
358
+ await self._send_best_effort_stop_async(token)
359
+ return True
360
+
361
+ def _log_stop_result(self, *, stopped: bool) -> None:
362
+ if stopped:
363
+ debug_log(
364
+ "lease.renewal_stopped",
365
+ message_id=self._message.metadata.message_id,
366
+ topic=self._message.metadata.topic,
367
+ consumer_group=self._message.metadata.consumer_group,
368
+ )
369
+ return
370
+ logger.warning(
371
+ "timed out waiting for queue lease renewal to stop for message %s on topic %s/%s",
372
+ self._message.metadata.message_id,
373
+ self._message.metadata.topic,
374
+ self._message.metadata.consumer_group,
375
+ )
376
+ debug_log(
377
+ "lease.renewal_stop_timeout",
378
+ message_id=self._message.metadata.message_id,
379
+ topic=self._message.metadata.topic,
380
+ consumer_group=self._message.metadata.consumer_group,
381
+ timeout_seconds=_LEASE_STOP_WAIT_TIMEOUT_SECONDS,
382
+ )
383
+
384
+ def _send_best_effort_stop(self, token: _LeaseRenewalToken) -> None:
385
+ try:
386
+ _send_lease_command(_LeaseExtensionStop(token=token))
387
+ except Exception as exc: # noqa: BLE001
388
+ _log_best_effort_lease_stop_failure(exc)
389
+
390
+ async def _send_best_effort_stop_async(self, token: _LeaseRenewalToken) -> None:
391
+ try:
392
+ await to_thread.run_sync(_send_lease_command, _LeaseExtensionStop(token=token))
393
+ except Exception as exc: # noqa: BLE001
394
+ _log_best_effort_lease_stop_failure(exc)
395
+
396
+ async def extend_async(self, duration: Duration) -> None:
397
+ """Extend the lease immediately if this message has lease metadata.
398
+
399
+ Applies settlement retry semantics: transient failures are retried
400
+ and an already-missing lease is tolerated with a warning.
401
+ """
402
+ if self._message.metadata.receipt_handle is None:
403
+ return
404
+ await retry_message_after_async(
405
+ self._message,
406
+ duration,
407
+ self._client._extend_lease, # noqa: SLF001
408
+ )
409
+
410
+ def extend(self, duration: Duration) -> None:
411
+ """Sync flavor of :meth:`extend_async`."""
412
+ if self._message.metadata.receipt_handle is None:
413
+ return
414
+ retry_message_after_sync(
415
+ self._message,
416
+ duration,
417
+ lambda message, duration: iter_coroutine(
418
+ self._client._extend_lease(message, duration) # noqa: SLF001
419
+ ),
420
+ )
421
+
422
+
423
+ def _prime_oidc_token_cache() -> None:
424
+ try:
425
+ get_vercel_oidc_token_sync()
426
+ except Exception as exc: # noqa: BLE001
427
+ debug_log(
428
+ "lease.oidc_cache_prime_failed",
429
+ exception_class=exc.__class__.__name__,
430
+ exception_message=str(exc),
431
+ )
432
+
433
+
434
+ async def retry_message_after_async(
435
+ message: Message[Any] | MessageMetadata,
436
+ duration: Duration,
437
+ extend_lease: AsyncExtendMessageLease,
438
+ ) -> None:
439
+ """Request redelivery of a leased message after ``duration``.
440
+
441
+ Retries transient failures and tolerates an already-missing lease with a
442
+ warning: the message can be settled or redelivered elsewhere before this
443
+ lands (lease expiry, concurrent consumer), in which case the requested
444
+ visibility no longer applies but the message itself is not lost.
445
+ """
446
+ try:
447
+ await retry_async_follow_up(
448
+ lambda: extend_lease(message, duration),
449
+ event_prefix="visibility",
450
+ )
451
+ except MessageNotFoundError:
452
+ _warn_lease_gone(message)
453
+
454
+
455
+ def retry_message_after_sync(
456
+ message: Message[Any] | MessageMetadata,
457
+ duration: Duration,
458
+ extend_lease: ExtendMessageLease,
459
+ ) -> None:
460
+ """Sync flavor of :func:`retry_message_after_async`."""
461
+ try:
462
+ retry_sync_follow_up(
463
+ lambda: extend_lease(message, duration),
464
+ event_prefix="visibility",
465
+ )
466
+ except MessageNotFoundError:
467
+ _warn_lease_gone(message)
468
+
469
+
470
+ def _warn_lease_gone(message: Message[Any] | MessageMetadata) -> None:
471
+ metadata = message.metadata if isinstance(message, Message) else message
472
+ logger.warning(
473
+ "queue lease for message %s on topic %s/%s no longer exists; "
474
+ "skipping requested visibility update",
475
+ metadata.message_id,
476
+ metadata.topic,
477
+ metadata.consumer_group,
478
+ )
479
+ debug_log(
480
+ "visibility.lease_gone",
481
+ message_id=metadata.message_id,
482
+ topic=metadata.topic,
483
+ consumer_group=metadata.consumer_group,
484
+ )
485
+
486
+
487
+ def _send_lease_command(command: _LeaseExtensionCommand) -> None:
488
+ command_send = _lease_worker_state.command_send
489
+ worker_token = _lease_worker_state.worker_token
490
+ if command_send is None or worker_token is None:
491
+ raise RuntimeError("lease extension worker is not ready")
492
+ try:
493
+ anyio.from_thread.run_sync(command_send.send_nowait, command, token=worker_token)
494
+ except anyio.WouldBlock as exc:
495
+ raise RuntimeError("lease extension worker command buffer is full") from exc
496
+
497
+
498
+ def _send_lease_extension_start(request: _LeaseExtensionRequest) -> None:
499
+ scheduled_event = threading.Event()
500
+ command = _LeaseExtensionStart(request=request, scheduled_event=scheduled_event)
501
+ _send_lease_command(command)
502
+ if not scheduled_event.wait(_LEASE_START_WAIT_TIMEOUT_SECONDS):
503
+ _send_best_effort_start_timeout_stop(request.token)
504
+ raise TimeoutError("timed out waiting for queue lease renewal to start")
505
+
506
+
507
+ async def _send_lease_extension_start_async(request: _LeaseExtensionRequest) -> None:
508
+ scheduled_event = anyio.Event()
509
+ command = _LeaseExtensionStart(
510
+ request=request,
511
+ scheduled_event=scheduled_event,
512
+ event_loop_token=current_token(),
513
+ )
514
+ send_errors = _send_lease_extension_start_command_async(command)
515
+ try:
516
+ with anyio.fail_after(_LEASE_START_WAIT_TIMEOUT_SECONDS):
517
+ await scheduled_event.wait()
518
+ except TimeoutError:
519
+ await to_thread.run_sync(_send_best_effort_start_timeout_stop, request.token)
520
+ raise
521
+ if send_errors:
522
+ raise send_errors[0]
523
+
524
+
525
+ def _send_lease_extension_start_command_async(
526
+ command: _LeaseExtensionStart,
527
+ ) -> list[Exception]:
528
+ command_send = _lease_worker_state.command_send
529
+ worker_token = _lease_worker_state.worker_token
530
+ if command_send is None or worker_token is None:
531
+ raise RuntimeError("lease extension worker is not ready")
532
+ caller_token = current_token()
533
+ send_errors: list[Exception] = []
534
+
535
+ def send_nowait() -> None:
536
+ try:
537
+ command_send.send_nowait(command)
538
+ except anyio.WouldBlock as exc:
539
+ error = RuntimeError("lease extension worker command buffer is full")
540
+ error.__cause__ = exc
541
+ send_errors.append(error)
542
+ _schedule_event_loop_callback(command.scheduled_event.set, caller_token)
543
+ except Exception as exc: # noqa: BLE001
544
+ send_errors.append(exc)
545
+ _schedule_event_loop_callback(command.scheduled_event.set, caller_token)
546
+
547
+ _schedule_event_loop_callback(send_nowait, worker_token)
548
+ return send_errors
549
+
550
+
551
+ def _send_best_effort_start_timeout_stop(token: _LeaseRenewalToken) -> None:
552
+ try:
553
+ _send_lease_command(_LeaseExtensionStop(token=token))
554
+ except Exception as exc:
555
+ logger.debug("failed to cancel timed out queue lease renewal start", exc_info=exc)
556
+ debug_log(
557
+ "lease.start_timeout_stop_failed",
558
+ exception_class=exc.__class__.__name__,
559
+ exception_message=str(exc),
560
+ )
561
+
562
+
563
+ async def _send_best_effort_async_start_failure_stop(token: _LeaseRenewalToken) -> None:
564
+ with anyio.CancelScope(shield=True):
565
+ try:
566
+ await to_thread.run_sync(_send_best_effort_start_timeout_stop, token)
567
+ except Exception as exc:
568
+ logger.debug("failed to cancel failed queue lease renewal start", exc_info=exc)
569
+ debug_log(
570
+ "lease.start_failure_stop_failed",
571
+ exception_class=exc.__class__.__name__,
572
+ exception_message=str(exc),
573
+ )
574
+
575
+
576
+ def _log_best_effort_lease_stop_failure(exc: BaseException) -> None:
577
+ logger.debug("failed to send best-effort queue lease renewal stop", exc_info=exc)
578
+ debug_log(
579
+ "lease.best_effort_stop_failed",
580
+ exception_class=exc.__class__.__name__,
581
+ exception_message=str(exc),
582
+ )
583
+
584
+
585
+ def _send_lease_extension_stop(token: _LeaseRenewalToken) -> bool:
586
+ deadline = time.monotonic() + _LEASE_STOP_WAIT_TIMEOUT_SECONDS
587
+ done_event = threading.Event()
588
+ _send_lease_command(_LeaseExtensionStop(token=token, done_event=done_event))
589
+ return done_event.wait(max(0.0, deadline - time.monotonic()))
590
+
591
+
592
+ async def _send_lease_extension_stop_async(token: _LeaseRenewalToken) -> bool:
593
+ done_event = anyio.Event()
594
+ await to_thread.run_sync(
595
+ _send_lease_command,
596
+ _LeaseExtensionStop(
597
+ token=token,
598
+ done_event=done_event,
599
+ event_loop_token=current_token(),
600
+ ),
601
+ )
602
+ with anyio.move_on_after(_LEASE_STOP_WAIT_TIMEOUT_SECONDS) as scope:
603
+ await done_event.wait()
604
+ return not scope.cancel_called
605
+
606
+
607
+ def _ensure_lease_renewal_thread() -> None:
608
+ if _lease_worker_state.started:
609
+ _wait_for_lease_worker_ready()
610
+ return
611
+ wait_for_ready = False
612
+ with _lease_worker_lock:
613
+ if _lease_worker_state.started:
614
+ wait_for_ready = True
615
+ else:
616
+ _lease_worker_ready.clear()
617
+ _lease_worker_state.startup_error = None
618
+ thread = threading.Thread(
619
+ target=_lease_renewal_worker_thread,
620
+ name="vercel-queue-lease-renewal",
621
+ daemon=True,
622
+ )
623
+ debug_log("lease.worker_start", thread_name=thread.name)
624
+ thread.start()
625
+ _lease_worker_state.thread = thread
626
+ _lease_worker_state.started = True
627
+ wait_for_ready = True
628
+ if wait_for_ready:
629
+ _wait_for_lease_worker_ready()
630
+
631
+
632
+ def _wait_for_lease_worker_ready() -> None:
633
+ thread = _lease_worker_state.thread
634
+ if not _lease_worker_ready.wait(_LEASE_WORKER_START_TIMEOUT_SECONDS):
635
+ _mark_lease_worker_stopped(thread)
636
+ raise RuntimeError("lease extension worker did not start")
637
+ startup_error = _lease_worker_state.startup_error
638
+ if startup_error is not None:
639
+ _mark_lease_worker_stopped(thread)
640
+ raise RuntimeError("lease extension worker failed to start") from startup_error
641
+
642
+
643
+ def _lease_renewal_worker_thread() -> None:
644
+ try:
645
+ _run_lease_renewal_worker_thread()
646
+ except BaseException as exc:
647
+ _lease_worker_state.startup_error = exc
648
+ _lease_worker_ready.set()
649
+ raise
650
+ finally:
651
+ _mark_lease_worker_stopped(threading.current_thread())
652
+
653
+
654
+ def _mark_lease_worker_stopped(thread: threading.Thread | None = None) -> None:
655
+ with _lease_worker_lock:
656
+ if thread is not None and _lease_worker_state.thread is not thread:
657
+ return
658
+ _lease_worker_state.command_send = None
659
+ _lease_worker_state.worker_token = None
660
+ _lease_worker_state.thread = None
661
+ _lease_worker_state.started = False
662
+
663
+
664
+ def _run_lease_renewal_worker_thread() -> None:
665
+ anyio.run(_run_lease_renewal_worker_async)
666
+
667
+
668
+ async def _run_lease_renewal_worker_async() -> None:
669
+ command_send, command_receive = anyio.create_memory_object_stream[_LeaseExtensionCommand](
670
+ _LEASE_COMMAND_BUFFER_SIZE
671
+ )
672
+ _lease_worker_state.command_send = command_send
673
+ _lease_worker_state.worker_token = current_token()
674
+ _lease_worker_ready.set()
675
+ debug_log("lease.worker_ready")
676
+ try:
677
+ await _lease_worker_async(command_receive)
678
+ finally:
679
+ command_send.close()
680
+ await command_receive.aclose()
681
+
682
+
683
+ async def _lease_worker_async(
684
+ commands: ObjectReceiveStream[_LeaseExtensionCommand],
685
+ ) -> None:
686
+ state = _LeaseWorkerRuntimeState(
687
+ active={},
688
+ stopped=OrderedDict(),
689
+ )
690
+ async with anyio.create_task_group() as task_group:
691
+ while True:
692
+ try:
693
+ command = await _receive_lease_command(commands)
694
+ except anyio.EndOfStream:
695
+ task_group.cancel_scope.cancel()
696
+ return
697
+ if command is None:
698
+ continue
699
+ if await _handle_lease_worker_command(command, state, task_group):
700
+ task_group.cancel_scope.cancel()
701
+ return
702
+
703
+
704
+ async def _handle_lease_worker_command(
705
+ command: _LeaseExtensionCommand,
706
+ state: _LeaseWorkerRuntimeState,
707
+ task_group: TaskGroup,
708
+ ) -> bool:
709
+ if isinstance(command, _LeaseExtensionStart):
710
+ _handle_lease_extension_start(command, state, task_group)
711
+ return False
712
+ if isinstance(command, _LeaseExtensionStop):
713
+ await _handle_lease_extension_stop(command, state)
714
+ return False
715
+
716
+ debug_log("lease.worker_shutdown")
717
+ return True
718
+
719
+
720
+ def _handle_lease_extension_start(
721
+ command: _LeaseExtensionStart,
722
+ state: _LeaseWorkerRuntimeState,
723
+ task_group: TaskGroup,
724
+ ) -> None:
725
+ try:
726
+ _start_lease_renewal_task(command.request, state, task_group)
727
+ finally:
728
+ _signal_lease_start_scheduled(command)
729
+
730
+
731
+ def _signal_lease_start_scheduled(command: _LeaseExtensionStart) -> None:
732
+ try:
733
+ _schedule_lease_start_signal(command)
734
+ except Exception as exc:
735
+ logger.debug("failed to signal queue lease renewal start scheduling", exc_info=exc)
736
+ debug_log(
737
+ "lease.start_signal_failed",
738
+ exception_class=exc.__class__.__name__,
739
+ exception_message=str(exc),
740
+ )
741
+
742
+
743
+ def _schedule_lease_start_signal(command: _LeaseExtensionStart) -> None:
744
+ if command.event_loop_token is None:
745
+ command.scheduled_event.set()
746
+ return
747
+ _schedule_event_loop_callback(command.scheduled_event.set, command.event_loop_token)
748
+
749
+
750
+ def _schedule_event_loop_callback(
751
+ callback: Callable[[], None],
752
+ event_loop_token: EventLoopToken,
753
+ ) -> None:
754
+ native_token = event_loop_token.native_token
755
+ if call_soon_threadsafe := getattr(native_token, "call_soon_threadsafe", None):
756
+ call_soon_threadsafe(callback)
757
+ return
758
+ if run_sync_soon := getattr(native_token, "run_sync_soon", None):
759
+ run_sync_soon(callback)
760
+ return
761
+ anyio.from_thread.run_sync(callback, token=event_loop_token)
762
+
763
+
764
+ def _start_lease_renewal_task(
765
+ request: _LeaseExtensionRequest,
766
+ state: _LeaseWorkerRuntimeState,
767
+ task_group: TaskGroup,
768
+ ) -> None:
769
+ if request.token in state.stopped:
770
+ state.stopped.pop(request.token, None)
771
+ return
772
+ if request.token in state.active:
773
+ return
774
+ renewal_task = _LeaseRenewalTask(
775
+ request=request,
776
+ cancel_scope=anyio.CancelScope(),
777
+ stop_waiters=[],
778
+ )
779
+ state.active[request.token] = renewal_task
780
+ task_group.start_soon(_run_lease_renewal_task, renewal_task, state)
781
+
782
+
783
+ async def _handle_lease_extension_stop(
784
+ command: _LeaseExtensionStop,
785
+ state: _LeaseWorkerRuntimeState,
786
+ ) -> None:
787
+ renewal_task = state.active.pop(command.token, None)
788
+ if renewal_task is not None:
789
+ if command.done_send is not None or command.done_event is not None:
790
+ renewal_task.stop_waiters.append(command)
791
+ renewal_task.cancel_scope.cancel()
792
+ return
793
+
794
+ _remember_stopped_lease_token(command.token, state)
795
+ await _signal_lease_stop_complete(command)
796
+
797
+
798
+ def _remember_stopped_lease_token(
799
+ token: _LeaseRenewalToken,
800
+ state: _LeaseWorkerRuntimeState,
801
+ ) -> None:
802
+ state.stopped[token] = None
803
+ state.stopped.move_to_end(token)
804
+ while len(state.stopped) > _STOPPED_LEASE_TOKEN_CACHE_SIZE:
805
+ state.stopped.popitem(last=False)
806
+
807
+
808
+ async def _signal_lease_stop_complete(command: _LeaseExtensionStop) -> None:
809
+ if command.done_send is not None:
810
+ try:
811
+ await command.done_send.send(None)
812
+ except Exception as exc: # noqa: BLE001
813
+ _log_lease_stop_signal_failure(exc)
814
+ if command.done_event is not None:
815
+ try:
816
+ if command.event_loop_token is None:
817
+ command.done_event.set()
818
+ else:
819
+ _schedule_event_loop_callback(command.done_event.set, command.event_loop_token)
820
+ except Exception as exc: # noqa: BLE001
821
+ _log_lease_stop_signal_failure(exc)
822
+
823
+
824
+ def _log_lease_stop_signal_failure(exc: BaseException) -> None:
825
+ logger.debug("failed to signal queue lease renewal stop completion", exc_info=exc)
826
+ debug_log(
827
+ "lease.stop_signal_failed",
828
+ exception_class=exc.__class__.__name__,
829
+ exception_message=str(exc),
830
+ )
831
+
832
+
833
+ def reset_lease_renewal_worker_for_tests() -> None:
834
+ """Stop the background lease renewal worker and clear cached clients."""
835
+ thread = _lease_worker_state.thread
836
+ if _lease_worker_state.command_send is not None:
837
+ _send_lease_command(_LeaseWorkerShutdown())
838
+ if thread is not None:
839
+ thread.join(timeout=5)
840
+ with _lease_worker_lock:
841
+ _lease_worker_state.command_send = None
842
+ _lease_worker_state.worker_token = None
843
+ _lease_worker_state.thread = None
844
+ _lease_worker_state.started = False
845
+ _lease_worker_ready.clear()
846
+
847
+
848
+ async def _receive_lease_command(
849
+ commands: ObjectReceiveStream[_LeaseExtensionCommand],
850
+ ) -> _LeaseExtensionCommand:
851
+ return await commands.receive()
852
+
853
+
854
+ def _is_client_error(exc: QueueError) -> bool:
855
+ if isinstance(exc, ThrottledError):
856
+ return False
857
+ status_code = exc.status_code
858
+ if status_code == 408:
859
+ return False
860
+ return status_code is not None and 400 <= status_code < 500
861
+
862
+
863
+ async def _run_lease_renewal_task(
864
+ renewal_task: _LeaseRenewalTask,
865
+ state: _LeaseWorkerRuntimeState,
866
+ ) -> None:
867
+ request = renewal_task.request
868
+ try:
869
+ with renewal_task.cancel_scope:
870
+ while state.active.get(request.token) is renewal_task:
871
+ await anyio.sleep(max(0.0, request.next_extension_at - time.monotonic()))
872
+ if state.active.get(request.token) is not renewal_task:
873
+ return
874
+ await _run_lease_extension(request, _LeaseTaskActiveMap(state.active))
875
+ finally:
876
+ with anyio.CancelScope(shield=True):
877
+ if state.active.get(request.token) is renewal_task:
878
+ state.active.pop(request.token, None)
879
+ for stop_command in renewal_task.stop_waiters:
880
+ await _signal_lease_stop_complete(stop_command)
881
+
882
+
883
+ class _LeaseTaskActiveMap(MutableMapping[_LeaseRenewalToken, _LeaseExtensionRequest]):
884
+ def __init__(self, active: dict[_LeaseRenewalToken, _LeaseRenewalTask]) -> None:
885
+ self._active = active
886
+
887
+ def __getitem__(self, token: _LeaseRenewalToken) -> _LeaseExtensionRequest:
888
+ return self._active[token].request
889
+
890
+ def __setitem__(
891
+ self,
892
+ token: _LeaseRenewalToken,
893
+ request: _LeaseExtensionRequest,
894
+ ) -> None:
895
+ self._active[token].request = request
896
+
897
+ def __delitem__(self, token: _LeaseRenewalToken) -> None:
898
+ del self._active[token]
899
+
900
+ def __iter__(self) -> Iterator[_LeaseRenewalToken]:
901
+ return iter(self._active)
902
+
903
+ def __len__(self) -> int:
904
+ return len(self._active)
905
+
906
+
907
+ async def _run_lease_extension(
908
+ request: _LeaseExtensionRequest,
909
+ active: MutableMapping[_LeaseRenewalToken, _LeaseExtensionRequest],
910
+ ) -> None:
911
+ if request.token not in active:
912
+ return
913
+ debug_log(
914
+ "lease.extension_attempt",
915
+ message_id=request.message.metadata.message_id,
916
+ topic=request.message.metadata.topic,
917
+ consumer_group=request.message.metadata.consumer_group,
918
+ lease_seconds=request.lease_seconds,
919
+ )
920
+ try:
921
+ await request.client._renew_lease( # noqa: SLF001
922
+ request.message,
923
+ request.lease_seconds,
924
+ )
925
+ except QueueError as exc:
926
+ if _is_client_error(exc):
927
+ active.pop(request.token, None)
928
+ debug_log(
929
+ "lease.client_error_stop",
930
+ message_id=request.message.metadata.message_id,
931
+ topic=request.message.metadata.topic,
932
+ consumer_group=request.message.metadata.consumer_group,
933
+ exception_class=exc.__class__.__name__,
934
+ exception_message=str(exc),
935
+ )
936
+ return
937
+ if request.token not in active:
938
+ return
939
+ logger.warning(
940
+ "transient queue lease renewal failure for message %s on topic %s/%s: %s",
941
+ request.message.metadata.message_id,
942
+ request.message.metadata.topic,
943
+ request.message.metadata.consumer_group,
944
+ exc,
945
+ )
946
+ retry_delay = _lease_extension_retry_delay(exc, request.lease_seconds)
947
+ request.next_extension_at = time.monotonic() + retry_delay
948
+ debug_log(
949
+ "lease.transient_retry_scheduled",
950
+ message_id=request.message.metadata.message_id,
951
+ topic=request.message.metadata.topic,
952
+ consumer_group=request.message.metadata.consumer_group,
953
+ retry_delay_seconds=retry_delay,
954
+ exception_class=exc.__class__.__name__,
955
+ exception_message=str(exc),
956
+ )
957
+ return
958
+ except Exception as exc:
959
+ if request.token not in active:
960
+ return
961
+ logger.warning(
962
+ "unexpected queue lease renewal failure for message %s on topic %s/%s",
963
+ request.message.metadata.message_id,
964
+ request.message.metadata.topic,
965
+ request.message.metadata.consumer_group,
966
+ exc_info=exc,
967
+ )
968
+ retry_delay = _lease_extension_retry_delay(exc, request.lease_seconds)
969
+ request.next_extension_at = time.monotonic() + retry_delay
970
+ debug_log(
971
+ "lease.unexpected_retry_scheduled",
972
+ message_id=request.message.metadata.message_id,
973
+ topic=request.message.metadata.topic,
974
+ consumer_group=request.message.metadata.consumer_group,
975
+ retry_delay_seconds=retry_delay,
976
+ exception_class=exc.__class__.__name__,
977
+ exception_message=str(exc),
978
+ )
979
+ return
980
+ if request.token not in active:
981
+ return
982
+ request.next_extension_at = time.monotonic() + _renewal_interval_seconds(request.lease_seconds)
983
+ debug_log(
984
+ "lease.extension_success",
985
+ message_id=request.message.metadata.message_id,
986
+ topic=request.message.metadata.topic,
987
+ consumer_group=request.message.metadata.consumer_group,
988
+ )
989
+
990
+
991
+ def _lease_extension_retry_delay(exc: BaseException, lease_seconds: int) -> float:
992
+ if isinstance(exc, ThrottledError) and exc.retry_after is not None:
993
+ retry_after = max(1.0, float(exc.retry_after))
994
+ return min(retry_after, _renewal_interval_seconds(lease_seconds))
995
+ return _TRANSIENT_LEASE_RETRY_SECONDS
996
+
997
+
998
+ async def finalize_payload_async(payload: Any) -> None:
999
+ if isinstance(payload, (AsyncStreamPayload, AsyncTextStreamPayload)):
1000
+ await payload.afinalize()
1001
+ elif isinstance(payload, (SyncStreamPayload, SyncTextStreamPayload)):
1002
+ payload.finalize()
1003
+
1004
+
1005
+ def finalize_payload_sync(payload: Any) -> None:
1006
+ if isinstance(payload, (SyncStreamPayload, SyncTextStreamPayload)):
1007
+ payload.finalize()