redis-message-queue 8.2.7__tar.gz → 8.2.8__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.
Files changed (26) hide show
  1. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/PKG-INFO +14 -8
  2. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/README.md +13 -7
  3. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/pyproject.toml +2 -2
  4. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/_config.py +16 -3
  5. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/_exceptions.py +3 -2
  6. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/_redis_gateway.py +2 -1
  7. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/asyncio/_redis_gateway.py +2 -1
  8. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/asyncio/redis_message_queue.py +3 -2
  9. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/redis_message_queue.py +3 -2
  10. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/.gitignore +0 -0
  11. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/LICENSE +0 -0
  12. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/__init__.py +0 -0
  13. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/_abstract_redis_gateway.py +0 -0
  14. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/_callable_utils.py +0 -0
  15. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/_event.py +0 -0
  16. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/_payload_limits.py +0 -0
  17. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/_queue_key_manager.py +0 -0
  18. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/_redis_cluster.py +0 -0
  19. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/_stored_message.py +0 -0
  20. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/asyncio/__init__.py +0 -0
  21. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/asyncio/_abstract_redis_gateway.py +0 -0
  22. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/interrupt_handler/__init__.py +0 -0
  23. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/interrupt_handler/_event_driven.py +0 -0
  24. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/interrupt_handler/_implementation.py +0 -0
  25. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/redis_message_queue/interrupt_handler/_interface.py +0 -0
  26. {redis_message_queue-8.2.7 → redis_message_queue-8.2.8}/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.7
3
+ Version: 8.2.8
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.7,<9.0.0"
37
+ pip install "redis-message-queue>=8.2.8,<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
- Without a visibility timeout, messages already moved to `processing` remain there indefinitely after a consumer crash and are not redelivered, even if the crash happened before your handler started running.
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`, there is no automatic reclaim
477
- path. For low-visibility-timeout workloads, prefer an explicit `drain()` /
478
- `aclose()` during shutdown so local pending claim IDs are recovered before
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
@@ -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.7,<9.0.0"
14
+ pip install "redis-message-queue>=8.2.8,<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
- Without a visibility timeout, messages already moved to `processing` remain there indefinitely after a consumer crash and are not redelivered, even if the crash happened before your handler started running.
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`, there is no automatic reclaim
454
- path. For low-visibility-timeout workloads, prefer an explicit `drain()` /
455
- `aclose()` during shutdown so local pending claim IDs are recovered before
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "redis-message-queue"
3
- version = "8.2.7"
3
+ version = "8.2.8"
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.7"
51
+ current_version = "8.2.8"
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: compensate by returning message to pending
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
- local lease_token = redis.call('GET', KEYS[5])
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.pcall('RPUSH', KEYS[1], stored)
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 and
55
- compensated by returning the message to pending before surfacing this error.
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
 
@@ -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: {_decode_lua_error(result[1])}",
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: {_decode_lua_error(result[1])}",
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",
@@ -607,8 +607,9 @@ class RedisMessageQueue:
607
607
  ):
608
608
  """Create a queue bound to an async Redis client or custom gateway.
609
609
 
610
- ``visibility_timeout_seconds`` defaults to 300. Set it to ``None`` to
611
- disable lease-based crash recovery; messages left in ``processing`` by a
610
+ ``visibility_timeout_seconds`` defaults to 300. To disable
611
+ lease-based crash recovery, set both ``visibility_timeout_seconds=None``
612
+ and ``max_delivery_count=None``; messages left in ``processing`` by a
612
613
  crashed worker are then not reclaimed automatically.
613
614
 
614
615
  ``visibility_timeout_seconds`` is a Redis server-time lease, not a
@@ -566,8 +566,9 @@ class RedisMessageQueue:
566
566
  ):
567
567
  """Create a queue bound to a Redis client or custom gateway.
568
568
 
569
- ``visibility_timeout_seconds`` defaults to 300. Set it to ``None`` to
570
- disable lease-based crash recovery; messages left in ``processing`` by a
569
+ ``visibility_timeout_seconds`` defaults to 300. To disable
570
+ lease-based crash recovery, set both ``visibility_timeout_seconds=None``
571
+ and ``max_delivery_count=None``; messages left in ``processing`` by a
571
572
  crashed worker are then not reclaimed automatically.
572
573
 
573
574
  ``visibility_timeout_seconds`` is a Redis server-time lease, not a