redis-message-queue 8.2.7__tar.gz → 8.2.9__tar.gz
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.
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/PKG-INFO +35 -21
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/README.md +34 -20
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/pyproject.toml +2 -2
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_config.py +16 -3
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_exceptions.py +3 -2
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_redis_gateway.py +2 -1
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/asyncio/_redis_gateway.py +2 -1
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/asyncio/redis_message_queue.py +13 -8
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/redis_message_queue.py +17 -8
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/.gitignore +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/LICENSE +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/__init__.py +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_abstract_redis_gateway.py +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_callable_utils.py +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_event.py +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_payload_limits.py +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_queue_key_manager.py +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_redis_cluster.py +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_stored_message.py +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/asyncio/__init__.py +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/asyncio/_abstract_redis_gateway.py +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/interrupt_handler/__init__.py +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/interrupt_handler/_event_driven.py +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/interrupt_handler/_implementation.py +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/interrupt_handler/_interface.py +0 -0
- {redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redis-message-queue
|
|
3
|
-
Version: 8.2.
|
|
3
|
+
Version: 8.2.9
|
|
4
4
|
Summary: Python message queuing with Redis and message deduplication
|
|
5
5
|
Project-URL: Homepage, https://github.com/Elijas/redis-message-queue
|
|
6
6
|
Project-URL: Repository, https://github.com/Elijas/redis-message-queue
|
|
@@ -34,7 +34,7 @@ Description-Content-Type: text/markdown
|
|
|
34
34
|
**Lightweight Python message queuing with Redis and built-in publish-side deduplication.** Deduplicate publishes within a TTL window, with optional crash recovery — across any number of producers and consumers.
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
pip install "redis-message-queue>=8.2.
|
|
37
|
+
pip install "redis-message-queue>=8.2.9,<9.0.0"
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
Requires Redis server >= 6.2.
|
|
@@ -129,9 +129,12 @@ All features are optional and can be enabled or disabled as needed.
|
|
|
129
129
|
| Configuration | Delivery guarantee |
|
|
130
130
|
|---|---|
|
|
131
131
|
| Default (`visibility_timeout_seconds=300`) | **At-least-once** — expired messages are reclaimed and redelivered |
|
|
132
|
-
| With `visibility_timeout_seconds=None` | **At-most-once** — a consumer crash loses the in-flight message |
|
|
132
|
+
| With `visibility_timeout_seconds=None, max_delivery_count=None` | **At-most-once** — a consumer crash loses the in-flight message |
|
|
133
133
|
|
|
134
134
|
See [Crash recovery with visibility timeout](#crash-recovery-with-visibility-timeout) for details and tradeoffs.
|
|
135
|
+
Because delivery-count limits depend on visibility-timeout reclaim, disabling
|
|
136
|
+
lease-based crash recovery requires setting both `visibility_timeout_seconds=None`
|
|
137
|
+
and `max_delivery_count=None`.
|
|
135
138
|
|
|
136
139
|
> **Important:** Handler exceptions are terminal. This library is a payload
|
|
137
140
|
> queue, not a task framework: raising inside `process_message()` does not
|
|
@@ -290,7 +293,10 @@ queue = RedisMessageQueue(
|
|
|
290
293
|
|
|
291
294
|
The callback is **advisory** — it may fire briefly after a successful `process_message` exit when a final renewal coincided with the success path. Use it for metrics or alerting, not as a correctness signal. For the async queue (`redis_message_queue.asyncio`), the callback may also be `async def`.
|
|
292
295
|
|
|
293
|
-
|
|
296
|
+
With `visibility_timeout_seconds=None, max_delivery_count=None`, messages
|
|
297
|
+
already moved to `processing` remain there indefinitely after a consumer crash
|
|
298
|
+
and are not redelivered, even if the crash happened before your handler
|
|
299
|
+
started running.
|
|
294
300
|
|
|
295
301
|
Visibility deadlines use Redis server time (`TIME`), not Python process time.
|
|
296
302
|
A forward step in the Redis server clock can make a live lease appear expired
|
|
@@ -473,10 +479,10 @@ without `aclose()`, or sync processes killed mid-handler, can leave the message
|
|
|
473
479
|
and its processing/lease metadata in Redis until a later consumer claim path
|
|
474
480
|
triggers visibility-timeout reclaim. With visibility timeouts enabled, this is
|
|
475
481
|
the designed at-least-once recovery path: the message is delayed by the lease,
|
|
476
|
-
not lost. With `visibility_timeout_seconds=None`,
|
|
477
|
-
path. For low-visibility-timeout workloads,
|
|
478
|
-
`aclose()` during shutdown so local pending claim
|
|
479
|
-
process exit.
|
|
482
|
+
not lost. With `visibility_timeout_seconds=None, max_delivery_count=None`,
|
|
483
|
+
there is no automatic reclaim path. For low-visibility-timeout workloads,
|
|
484
|
+
prefer an explicit `drain()` / `aclose()` during shutdown so local pending claim
|
|
485
|
+
IDs are recovered before process exit.
|
|
480
486
|
|
|
481
487
|
`drain()` / `aclose()` timeouts are measured with Python monotonic clocks, but
|
|
482
488
|
any lease deadlines they recover were created from Redis server time. The same
|
|
@@ -842,6 +848,11 @@ Pre-commit and mid-flight exceptions:
|
|
|
842
848
|
exceptions. Under an ambiguous lost response, Redis may have committed
|
|
843
849
|
despite the exception. Treat them as "operation did not succeed from the
|
|
844
850
|
caller's perspective", not "Redis did not commit".
|
|
851
|
+
- Visibility-timeout claim-store write failures raise
|
|
852
|
+
`ClaimStoreFailedError` and emit `claim/failure`. When the compensating
|
|
853
|
+
return-to-pending write succeeds, the payload is back in pending; if
|
|
854
|
+
return-to-pending also fails, the payload remains in processing so there is
|
|
855
|
+
still a live queue copy.
|
|
845
856
|
|
|
846
857
|
#### Drain events
|
|
847
858
|
|
|
@@ -869,11 +880,6 @@ The following operations have no `on_event` surface by design:
|
|
|
869
880
|
preserves queue safety on Cluster `CROSSSLOT` rejection but cannot be
|
|
870
881
|
observed through `on_event`. Operators watching key-TTL behavior or Redis
|
|
871
882
|
slow logs can detect orphans.
|
|
872
|
-
- **VT claim-store OOM compensation:** if the visibility-timeout Lua script
|
|
873
|
-
cannot store the claim result, it removes the message from processing, pushes
|
|
874
|
-
it back to pending, and returns `false`. Python translates that into
|
|
875
|
-
`claim_empty/skipped`, the same shape as an empty poll. This is intentional
|
|
876
|
-
fail-safe behavior; the message is not lost.
|
|
877
883
|
- **`drop_oldest` evictions:** when publish backpressure uses
|
|
878
884
|
`pending_overload_policy="drop_oldest"`, the oldest pending message is
|
|
879
885
|
discarded before the new message is enqueued. The successful enqueue emits
|
|
@@ -884,14 +890,22 @@ The following operations have no `on_event` surface by design:
|
|
|
884
890
|
terminal operation's failure event. There is no per-attempt event for those
|
|
885
891
|
paths.
|
|
886
892
|
|
|
887
|
-
The public exception hierarchy is rooted at `RedisMessageQueueError`.
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
`
|
|
892
|
-
`
|
|
893
|
-
|
|
894
|
-
|
|
893
|
+
The public exception hierarchy is rooted at `RedisMessageQueueError`. The
|
|
894
|
+
current exported queue-owned exception classes are:
|
|
895
|
+
|
|
896
|
+
- `RedisMessageQueueError` (base)
|
|
897
|
+
- `ClaimStoreFailedError` - visibility-timeout claim metadata could not be stored
|
|
898
|
+
- `ConfigurationError` - invalid constructor args; also a `ValueError`
|
|
899
|
+
- `DrainFailedError` - drain pending-claim recovery failed
|
|
900
|
+
- `GatewayContractError` - custom gateway protocol violation; also a `TypeError`
|
|
901
|
+
- `LuaScriptError` - Lua `redis.error_reply(...)`; also a redis-py `ResponseError`
|
|
902
|
+
- `MalformedStoredMessageError` - stored value is not a valid RMQ envelope
|
|
903
|
+
- `PayloadTooLargeError` - serialized payload exceeds `max_payload_bytes`; also a `ValueError`
|
|
904
|
+
- `PayloadTooDeepError` - payload nesting exceeds `max_payload_depth`; also a `ValueError`
|
|
905
|
+
- `QueueBackpressureError` - `pending_overload_policy="raise"` rejected enqueue
|
|
906
|
+
- `QueueDrainedError` - `publish()` called after explicit drain/aclose
|
|
907
|
+
- `CleanupFailedError` - cleanup after handler completion failed
|
|
908
|
+
- `RetryBudgetExhaustedError` - Redis retry budget exhausted; also a redis-py `RedisError`
|
|
895
909
|
|
|
896
910
|
## Known limitations
|
|
897
911
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
**Lightweight Python message queuing with Redis and built-in publish-side deduplication.** Deduplicate publishes within a TTL window, with optional crash recovery — across any number of producers and consumers.
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
pip install "redis-message-queue>=8.2.
|
|
14
|
+
pip install "redis-message-queue>=8.2.9,<9.0.0"
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Requires Redis server >= 6.2.
|
|
@@ -106,9 +106,12 @@ All features are optional and can be enabled or disabled as needed.
|
|
|
106
106
|
| Configuration | Delivery guarantee |
|
|
107
107
|
|---|---|
|
|
108
108
|
| Default (`visibility_timeout_seconds=300`) | **At-least-once** — expired messages are reclaimed and redelivered |
|
|
109
|
-
| With `visibility_timeout_seconds=None` | **At-most-once** — a consumer crash loses the in-flight message |
|
|
109
|
+
| With `visibility_timeout_seconds=None, max_delivery_count=None` | **At-most-once** — a consumer crash loses the in-flight message |
|
|
110
110
|
|
|
111
111
|
See [Crash recovery with visibility timeout](#crash-recovery-with-visibility-timeout) for details and tradeoffs.
|
|
112
|
+
Because delivery-count limits depend on visibility-timeout reclaim, disabling
|
|
113
|
+
lease-based crash recovery requires setting both `visibility_timeout_seconds=None`
|
|
114
|
+
and `max_delivery_count=None`.
|
|
112
115
|
|
|
113
116
|
> **Important:** Handler exceptions are terminal. This library is a payload
|
|
114
117
|
> queue, not a task framework: raising inside `process_message()` does not
|
|
@@ -267,7 +270,10 @@ queue = RedisMessageQueue(
|
|
|
267
270
|
|
|
268
271
|
The callback is **advisory** — it may fire briefly after a successful `process_message` exit when a final renewal coincided with the success path. Use it for metrics or alerting, not as a correctness signal. For the async queue (`redis_message_queue.asyncio`), the callback may also be `async def`.
|
|
269
272
|
|
|
270
|
-
|
|
273
|
+
With `visibility_timeout_seconds=None, max_delivery_count=None`, messages
|
|
274
|
+
already moved to `processing` remain there indefinitely after a consumer crash
|
|
275
|
+
and are not redelivered, even if the crash happened before your handler
|
|
276
|
+
started running.
|
|
271
277
|
|
|
272
278
|
Visibility deadlines use Redis server time (`TIME`), not Python process time.
|
|
273
279
|
A forward step in the Redis server clock can make a live lease appear expired
|
|
@@ -450,10 +456,10 @@ without `aclose()`, or sync processes killed mid-handler, can leave the message
|
|
|
450
456
|
and its processing/lease metadata in Redis until a later consumer claim path
|
|
451
457
|
triggers visibility-timeout reclaim. With visibility timeouts enabled, this is
|
|
452
458
|
the designed at-least-once recovery path: the message is delayed by the lease,
|
|
453
|
-
not lost. With `visibility_timeout_seconds=None`,
|
|
454
|
-
path. For low-visibility-timeout workloads,
|
|
455
|
-
`aclose()` during shutdown so local pending claim
|
|
456
|
-
process exit.
|
|
459
|
+
not lost. With `visibility_timeout_seconds=None, max_delivery_count=None`,
|
|
460
|
+
there is no automatic reclaim path. For low-visibility-timeout workloads,
|
|
461
|
+
prefer an explicit `drain()` / `aclose()` during shutdown so local pending claim
|
|
462
|
+
IDs are recovered before process exit.
|
|
457
463
|
|
|
458
464
|
`drain()` / `aclose()` timeouts are measured with Python monotonic clocks, but
|
|
459
465
|
any lease deadlines they recover were created from Redis server time. The same
|
|
@@ -819,6 +825,11 @@ Pre-commit and mid-flight exceptions:
|
|
|
819
825
|
exceptions. Under an ambiguous lost response, Redis may have committed
|
|
820
826
|
despite the exception. Treat them as "operation did not succeed from the
|
|
821
827
|
caller's perspective", not "Redis did not commit".
|
|
828
|
+
- Visibility-timeout claim-store write failures raise
|
|
829
|
+
`ClaimStoreFailedError` and emit `claim/failure`. When the compensating
|
|
830
|
+
return-to-pending write succeeds, the payload is back in pending; if
|
|
831
|
+
return-to-pending also fails, the payload remains in processing so there is
|
|
832
|
+
still a live queue copy.
|
|
822
833
|
|
|
823
834
|
#### Drain events
|
|
824
835
|
|
|
@@ -846,11 +857,6 @@ The following operations have no `on_event` surface by design:
|
|
|
846
857
|
preserves queue safety on Cluster `CROSSSLOT` rejection but cannot be
|
|
847
858
|
observed through `on_event`. Operators watching key-TTL behavior or Redis
|
|
848
859
|
slow logs can detect orphans.
|
|
849
|
-
- **VT claim-store OOM compensation:** if the visibility-timeout Lua script
|
|
850
|
-
cannot store the claim result, it removes the message from processing, pushes
|
|
851
|
-
it back to pending, and returns `false`. Python translates that into
|
|
852
|
-
`claim_empty/skipped`, the same shape as an empty poll. This is intentional
|
|
853
|
-
fail-safe behavior; the message is not lost.
|
|
854
860
|
- **`drop_oldest` evictions:** when publish backpressure uses
|
|
855
861
|
`pending_overload_policy="drop_oldest"`, the oldest pending message is
|
|
856
862
|
discarded before the new message is enqueued. The successful enqueue emits
|
|
@@ -861,14 +867,22 @@ The following operations have no `on_event` surface by design:
|
|
|
861
867
|
terminal operation's failure event. There is no per-attempt event for those
|
|
862
868
|
paths.
|
|
863
869
|
|
|
864
|
-
The public exception hierarchy is rooted at `RedisMessageQueueError`.
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
`
|
|
869
|
-
`
|
|
870
|
-
|
|
871
|
-
|
|
870
|
+
The public exception hierarchy is rooted at `RedisMessageQueueError`. The
|
|
871
|
+
current exported queue-owned exception classes are:
|
|
872
|
+
|
|
873
|
+
- `RedisMessageQueueError` (base)
|
|
874
|
+
- `ClaimStoreFailedError` - visibility-timeout claim metadata could not be stored
|
|
875
|
+
- `ConfigurationError` - invalid constructor args; also a `ValueError`
|
|
876
|
+
- `DrainFailedError` - drain pending-claim recovery failed
|
|
877
|
+
- `GatewayContractError` - custom gateway protocol violation; also a `TypeError`
|
|
878
|
+
- `LuaScriptError` - Lua `redis.error_reply(...)`; also a redis-py `ResponseError`
|
|
879
|
+
- `MalformedStoredMessageError` - stored value is not a valid RMQ envelope
|
|
880
|
+
- `PayloadTooLargeError` - serialized payload exceeds `max_payload_bytes`; also a `ValueError`
|
|
881
|
+
- `PayloadTooDeepError` - payload nesting exceeds `max_payload_depth`; also a `ValueError`
|
|
882
|
+
- `QueueBackpressureError` - `pending_overload_policy="raise"` rejected enqueue
|
|
883
|
+
- `QueueDrainedError` - `publish()` called after explicit drain/aclose
|
|
884
|
+
- `CleanupFailedError` - cleanup after handler completion failed
|
|
885
|
+
- `RetryBudgetExhaustedError` - Redis retry budget exhausted; also a redis-py `RedisError`
|
|
872
886
|
|
|
873
887
|
## Known limitations
|
|
874
888
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "redis-message-queue"
|
|
3
|
-
version = "8.2.
|
|
3
|
+
version = "8.2.9"
|
|
4
4
|
description = "Python message queuing with Redis and message deduplication"
|
|
5
5
|
authors = [{ name = "Elijas", email = "4084885+Elijas@users.noreply.github.com" }]
|
|
6
6
|
readme = "README.md"
|
|
@@ -48,7 +48,7 @@ default-groups = ["dev", "test"]
|
|
|
48
48
|
##############################
|
|
49
49
|
|
|
50
50
|
[tool.bumpversion]
|
|
51
|
-
current_version = "8.2.
|
|
51
|
+
current_version = "8.2.9"
|
|
52
52
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
53
53
|
serialize = ["{major}.{minor}.{patch}"]
|
|
54
54
|
search = "{current_version}"
|
|
@@ -891,10 +891,11 @@ local dead_lettered_events = {}
|
|
|
891
891
|
local claim_store_failed_sentinel = string.char(0) .. '__rmq_claim_store_failed__'
|
|
892
892
|
|
|
893
893
|
local function store_claim_and_return(stored)
|
|
894
|
-
-- pcall guards against OOM mid-write:
|
|
894
|
+
-- pcall guards against OOM mid-write: fail fast while preserving a live payload copy.
|
|
895
|
+
local lease_token = nil
|
|
895
896
|
local ok, result = pcall(function()
|
|
896
897
|
redis.call('INCR', KEYS[5])
|
|
897
|
-
|
|
898
|
+
lease_token = redis.call('GET', KEYS[5])
|
|
898
899
|
local claim_payload = cjson.encode({stored, lease_token})
|
|
899
900
|
redis.call('ZADD', KEYS[3], now_ms + tonumber(ARGV[1]), stored)
|
|
900
901
|
redis.call('HSET', KEYS[4], stored, lease_token)
|
|
@@ -906,8 +907,20 @@ local function store_claim_and_return(stored)
|
|
|
906
907
|
end)
|
|
907
908
|
if not ok then
|
|
908
909
|
redis.call('HINCRBY', KEYS[6], stored, -1)
|
|
910
|
+
local return_result = redis.pcall('RPUSH', KEYS[1], stored)
|
|
911
|
+
if type(return_result) == 'table' and return_result['err'] then
|
|
912
|
+
local failure = tostring(result) .. '; return-to-pending failed: ' .. tostring(return_result['err'])
|
|
913
|
+
return {claim_store_failed_sentinel, failure, stored}
|
|
914
|
+
end
|
|
909
915
|
redis.call('LREM', KEYS[2], 1, stored)
|
|
910
|
-
redis.
|
|
916
|
+
redis.call('ZREM', KEYS[3], stored)
|
|
917
|
+
redis.call('HDEL', KEYS[4], stored)
|
|
918
|
+
redis.call('DEL', KEYS[8])
|
|
919
|
+
redis.call('HDEL', KEYS[10], ARGV[4])
|
|
920
|
+
if lease_token then
|
|
921
|
+
redis.call('HDEL', KEYS[9], lease_token)
|
|
922
|
+
redis.call('HDEL', KEYS[11], lease_token)
|
|
923
|
+
end
|
|
911
924
|
return {claim_store_failed_sentinel, tostring(result), stored}
|
|
912
925
|
end
|
|
913
926
|
return result
|
|
@@ -51,8 +51,9 @@ class CleanupFailedError(RedisMessageQueueError):
|
|
|
51
51
|
class ClaimStoreFailedError(RedisMessageQueueError):
|
|
52
52
|
"""Raised when the VT-claim Lua store_claim_and_return pcall failed.
|
|
53
53
|
|
|
54
|
-
The script decremented the speculative delivery_count increment
|
|
55
|
-
|
|
54
|
+
The script decremented the speculative delivery_count increment. It returns
|
|
55
|
+
the message to pending when that write succeeds; if that write fails, it
|
|
56
|
+
leaves the message in processing so the payload still has a live queue copy.
|
|
56
57
|
"""
|
|
57
58
|
|
|
58
59
|
|
{redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_redis_gateway.py
RENAMED
|
@@ -987,7 +987,8 @@ class RedisGateway(AbstractRedisGateway):
|
|
|
987
987
|
stored_message = result[2] if len(result) > 2 else None
|
|
988
988
|
message_id = extract_stored_message_id(stored_message) if isinstance(stored_message, (str, bytes)) else None
|
|
989
989
|
raise ClaimStoreFailedError(
|
|
990
|
-
f"VT claim store failed after delivery_count rollback:
|
|
990
|
+
f"VT claim store failed after delivery_count rollback and payload preservation: "
|
|
991
|
+
f"{_decode_lua_error(result[1])}",
|
|
991
992
|
queue=from_queue,
|
|
992
993
|
message_id=message_id,
|
|
993
994
|
operation="claim",
|
|
@@ -967,7 +967,8 @@ class RedisGateway(AbstractRedisGateway):
|
|
|
967
967
|
stored_message = result[2] if len(result) > 2 else None
|
|
968
968
|
message_id = extract_stored_message_id(stored_message) if isinstance(stored_message, (str, bytes)) else None
|
|
969
969
|
raise ClaimStoreFailedError(
|
|
970
|
-
f"VT claim store failed after delivery_count rollback:
|
|
970
|
+
f"VT claim store failed after delivery_count rollback and payload preservation: "
|
|
971
|
+
f"{_decode_lua_error(result[1])}",
|
|
971
972
|
queue=from_queue,
|
|
972
973
|
message_id=message_id,
|
|
973
974
|
operation="claim",
|
|
@@ -87,6 +87,12 @@ def _warning_exception_name(exc: BaseException) -> str:
|
|
|
87
87
|
return type(exc).__name__
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
def _warn_runtime_warning(message: str, *, stacklevel: int) -> None:
|
|
91
|
+
with warnings.catch_warnings():
|
|
92
|
+
warnings.simplefilter("always", RuntimeWarning)
|
|
93
|
+
warnings.warn(message, RuntimeWarning, stacklevel=stacklevel + 1)
|
|
94
|
+
|
|
95
|
+
|
|
90
96
|
def _find_non_string_dict_keys(value: object) -> list[object]:
|
|
91
97
|
non_str_keys: list[object] = []
|
|
92
98
|
seen: set[int] = set()
|
|
@@ -607,8 +613,9 @@ class RedisMessageQueue:
|
|
|
607
613
|
):
|
|
608
614
|
"""Create a queue bound to an async Redis client or custom gateway.
|
|
609
615
|
|
|
610
|
-
``visibility_timeout_seconds`` defaults to 300.
|
|
611
|
-
|
|
616
|
+
``visibility_timeout_seconds`` defaults to 300. To disable
|
|
617
|
+
lease-based crash recovery, set both ``visibility_timeout_seconds=None``
|
|
618
|
+
and ``max_delivery_count=None``; messages left in ``processing`` by a
|
|
612
619
|
crashed worker are then not reclaimed automatically.
|
|
613
620
|
|
|
614
621
|
``visibility_timeout_seconds`` is a Redis server-time lease, not a
|
|
@@ -1272,7 +1279,7 @@ class RedisMessageQueue:
|
|
|
1272
1279
|
message_id=message_id,
|
|
1273
1280
|
lease_token_hash=lease_token_hash,
|
|
1274
1281
|
)
|
|
1275
|
-
|
|
1282
|
+
_warn_runtime_warning(_STALE_LEASE_NACK_WARNING, stacklevel=2)
|
|
1276
1283
|
except BaseException as cleanup_exc:
|
|
1277
1284
|
# The handler exception is the user-visible failure; cleanup failure is secondary.
|
|
1278
1285
|
logger.exception("Failed to clean up message from processing queue")
|
|
@@ -1285,10 +1292,9 @@ class RedisMessageQueue:
|
|
|
1285
1292
|
error=cleanup_exc,
|
|
1286
1293
|
duration_ms=_duration_ms(cleanup_started_at),
|
|
1287
1294
|
)
|
|
1288
|
-
|
|
1295
|
+
_warn_runtime_warning(
|
|
1289
1296
|
f"Cleanup raised after handler exception ({_warning_exception_name(cleanup_exc)}); "
|
|
1290
1297
|
"see logs for both tracebacks",
|
|
1291
|
-
RuntimeWarning,
|
|
1292
1298
|
stacklevel=2,
|
|
1293
1299
|
)
|
|
1294
1300
|
raise
|
|
@@ -1348,7 +1354,7 @@ class RedisMessageQueue:
|
|
|
1348
1354
|
message_id=message_id,
|
|
1349
1355
|
lease_token_hash=lease_token_hash,
|
|
1350
1356
|
)
|
|
1351
|
-
|
|
1357
|
+
_warn_runtime_warning(_STALE_LEASE_ACK_WARNING, stacklevel=2)
|
|
1352
1358
|
finished_without_error = True
|
|
1353
1359
|
finally:
|
|
1354
1360
|
if lease_heartbeat is not None:
|
|
@@ -1437,9 +1443,8 @@ class RedisMessageQueue:
|
|
|
1437
1443
|
exception_type=type(exc).__name__,
|
|
1438
1444
|
error=exc,
|
|
1439
1445
|
)
|
|
1440
|
-
|
|
1446
|
+
_warn_runtime_warning(
|
|
1441
1447
|
f"Failed to trim queue {destination_queue} ({type(exc).__name__}); list may exceed max_*_length",
|
|
1442
|
-
RuntimeWarning,
|
|
1443
1448
|
stacklevel=3,
|
|
1444
1449
|
)
|
|
1445
1450
|
|
{redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/redis_message_queue.py
RENAMED
|
@@ -100,6 +100,12 @@ def _warning_exception_name(exc: BaseException) -> str:
|
|
|
100
100
|
return type(exc).__name__
|
|
101
101
|
|
|
102
102
|
|
|
103
|
+
def _warn_runtime_warning(message: str, *, stacklevel: int) -> None:
|
|
104
|
+
with warnings.catch_warnings():
|
|
105
|
+
warnings.simplefilter("always", RuntimeWarning)
|
|
106
|
+
warnings.warn(message, RuntimeWarning, stacklevel=stacklevel + 1)
|
|
107
|
+
|
|
108
|
+
|
|
103
109
|
def _find_non_string_dict_keys(value: object) -> list[object]:
|
|
104
110
|
non_str_keys: list[object] = []
|
|
105
111
|
seen: set[int] = set()
|
|
@@ -339,12 +345,16 @@ def _close_or_cancel_awaitable(awaitable: object) -> None:
|
|
|
339
345
|
try:
|
|
340
346
|
close()
|
|
341
347
|
return
|
|
348
|
+
except asyncio.CancelledError:
|
|
349
|
+
pass
|
|
342
350
|
except Exception:
|
|
343
351
|
pass
|
|
344
352
|
cancel = getattr(awaitable, "cancel", None)
|
|
345
353
|
if callable(cancel):
|
|
346
354
|
try:
|
|
347
355
|
cancel()
|
|
356
|
+
except asyncio.CancelledError:
|
|
357
|
+
pass
|
|
348
358
|
except Exception:
|
|
349
359
|
pass
|
|
350
360
|
|
|
@@ -566,8 +576,9 @@ class RedisMessageQueue:
|
|
|
566
576
|
):
|
|
567
577
|
"""Create a queue bound to a Redis client or custom gateway.
|
|
568
578
|
|
|
569
|
-
``visibility_timeout_seconds`` defaults to 300.
|
|
570
|
-
|
|
579
|
+
``visibility_timeout_seconds`` defaults to 300. To disable
|
|
580
|
+
lease-based crash recovery, set both ``visibility_timeout_seconds=None``
|
|
581
|
+
and ``max_delivery_count=None``; messages left in ``processing`` by a
|
|
571
582
|
crashed worker are then not reclaimed automatically.
|
|
572
583
|
|
|
573
584
|
``visibility_timeout_seconds`` is a Redis server-time lease, not a
|
|
@@ -1234,7 +1245,7 @@ class RedisMessageQueue:
|
|
|
1234
1245
|
message_id=message_id,
|
|
1235
1246
|
lease_token_hash=lease_token_hash,
|
|
1236
1247
|
)
|
|
1237
|
-
|
|
1248
|
+
_warn_runtime_warning(_STALE_LEASE_NACK_WARNING, stacklevel=2)
|
|
1238
1249
|
except BaseException as cleanup_exc:
|
|
1239
1250
|
# The handler exception is the user-visible failure; cleanup failure is secondary.
|
|
1240
1251
|
logger.exception("Failed to clean up message from processing queue")
|
|
@@ -1247,10 +1258,9 @@ class RedisMessageQueue:
|
|
|
1247
1258
|
error=cleanup_exc,
|
|
1248
1259
|
duration_ms=_duration_ms(cleanup_started_at),
|
|
1249
1260
|
)
|
|
1250
|
-
|
|
1261
|
+
_warn_runtime_warning(
|
|
1251
1262
|
f"Cleanup raised after handler exception ({_warning_exception_name(cleanup_exc)}); "
|
|
1252
1263
|
"see logs for both tracebacks",
|
|
1253
|
-
RuntimeWarning,
|
|
1254
1264
|
stacklevel=2,
|
|
1255
1265
|
)
|
|
1256
1266
|
raise
|
|
@@ -1309,7 +1319,7 @@ class RedisMessageQueue:
|
|
|
1309
1319
|
message_id=message_id,
|
|
1310
1320
|
lease_token_hash=lease_token_hash,
|
|
1311
1321
|
)
|
|
1312
|
-
|
|
1322
|
+
_warn_runtime_warning(_STALE_LEASE_ACK_WARNING, stacklevel=2)
|
|
1313
1323
|
finally:
|
|
1314
1324
|
if lease_heartbeat is not None:
|
|
1315
1325
|
lease_heartbeat.stop()
|
|
@@ -1399,9 +1409,8 @@ class RedisMessageQueue:
|
|
|
1399
1409
|
exception_type=type(exc).__name__,
|
|
1400
1410
|
error=exc,
|
|
1401
1411
|
)
|
|
1402
|
-
|
|
1412
|
+
_warn_runtime_warning(
|
|
1403
1413
|
f"Failed to trim queue {destination_queue} ({type(exc).__name__}); list may exceed max_*_length",
|
|
1404
|
-
RuntimeWarning,
|
|
1405
1414
|
stacklevel=3,
|
|
1406
1415
|
)
|
|
1407
1416
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_callable_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_payload_limits.py
RENAMED
|
File without changes
|
{redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_queue_key_manager.py
RENAMED
|
File without changes
|
{redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_redis_cluster.py
RENAMED
|
File without changes
|
{redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/_stored_message.py
RENAMED
|
File without changes
|
{redis_message_queue-8.2.7 → redis_message_queue-8.2.9}/redis_message_queue/asyncio/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|