Skip to content

Commit d3c663b

Browse files
committed
respect channel layer capacity in RedisChannelLayer.receive_buffer
respect the capacity setting so that the receive_buffer does not grow without bounds see: #212
1 parent 2075071 commit d3c663b

File tree

2 files changed

+32
-1
lines changed

2 files changed

+32
-1
lines changed

channels_redis/core.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import base64
33
import binascii
44
import collections
5+
import functools
56
import hashlib
67
import itertools
78
import logging
@@ -179,6 +180,19 @@ class UnsupportedRedis(Exception):
179180
pass
180181

181182

183+
class BoundedQueue(asyncio.Queue):
184+
def put_nowait(self, item):
185+
if self.full():
186+
# see: https://github.com/django/channels_redis/issues/212
187+
# if we actually get into this code block, it likely means that
188+
# this specific consumer has stopped reading
189+
# if we get into this code block, it's better to drop messages
190+
# that exceed the channel layer capacity than to continue to
191+
# malloc() forever
192+
self.get_nowait()
193+
return super(BoundedQueue, self).put_nowait(item)
194+
195+
182196
class RedisChannelLayer(BaseChannelLayer):
183197
"""
184198
Redis channel layer.
@@ -226,7 +240,9 @@ def __init__(
226240
# Event loop they are trying to receive on
227241
self.receive_event_loop = None
228242
# Buffered messages by process-local channel name
229-
self.receive_buffer = collections.defaultdict(asyncio.Queue)
243+
self.receive_buffer = collections.defaultdict(
244+
functools.partial(BoundedQueue, self.capacity)
245+
)
230246
# Detached channel cleanup tasks
231247
self.receive_cleaners = []
232248
# Per-channel cleanup locks to prevent a receive starting and moving

tests/test_core.py

+15
Original file line numberDiff line numberDiff line change
@@ -627,3 +627,18 @@ def test_custom_group_key_format():
627627
channel_layer = RedisChannelLayer(prefix="test_prefix")
628628
group_name = channel_layer._group_key("test_group")
629629
assert group_name == b"test_prefix:group:test_group"
630+
631+
632+
@pytest.mark.asyncio
633+
async def test_receive_buffer_respects_capacity():
634+
channel_layer = RedisChannelLayer()
635+
buff = channel_layer.receive_buffer['test-group']
636+
for i in range(10000):
637+
buff.put_nowait(i)
638+
639+
capacity = 100
640+
assert channel_layer.capacity == capacity
641+
assert buff.full() is True
642+
assert buff.qsize() == capacity
643+
messages = [buff.get_nowait() for _ in range(capacity)]
644+
assert list(range(9900, 10000)) == messages

0 commit comments

Comments
 (0)