Skip to content

Commit 1e903e1

Browse files
Retry initial Redis connection (Fixes #1534) (#1536)
1 parent 2089253 commit 1e903e1

File tree

4 files changed

+40
-32
lines changed

4 files changed

+40
-32
lines changed

src/socketio/async_redis_manager.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ def __init__(self, url='redis://localhost:6379/0', channel='socketio',
6161
super().__init__(channel=channel, write_only=write_only, logger=logger)
6262
self.redis_url = url
6363
self.redis_options = redis_options or {}
64-
self._redis_connect()
64+
self.connected = False
65+
self.redis = None
66+
self.pubsub = None
6567

6668
def _get_redis_module_and_error(self):
6769
parsed_url = urlparse(self.redis_url)
@@ -106,23 +108,23 @@ def _redis_connect(self):
106108
self.redis = module.Redis.from_url(self.redis_url,
107109
**self.redis_options)
108110
self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True)
111+
self.connected = True
109112

110113
async def _publish(self, data): # pragma: no cover
111-
retry = True
112114
_, error = self._get_redis_module_and_error()
113-
while True:
115+
for retries_left in range(1, -1, -1): # 2 attempts
114116
try:
115-
if not retry:
117+
if not self.connected:
116118
self._redis_connect()
117119
return await self.redis.publish(
118120
self.channel, json.dumps(data))
119121
except error as exc:
120-
if retry:
122+
if retries_left > 0:
121123
self._get_logger().error(
122124
'Cannot publish to redis... '
123125
'retrying',
124126
extra={"redis_exception": str(exc)})
125-
retry = False
127+
self.connected = False
126128
else:
127129
self._get_logger().error(
128130
'Cannot publish to redis... '
@@ -133,11 +135,10 @@ async def _publish(self, data): # pragma: no cover
133135

134136
async def _redis_listen_with_retries(self): # pragma: no cover
135137
retry_sleep = 1
136-
connect = False
137138
_, error = self._get_redis_module_and_error()
138139
while True:
139140
try:
140-
if connect:
141+
if not self.connected:
141142
self._redis_connect()
142143
await self.pubsub.subscribe(self.channel)
143144
retry_sleep = 1
@@ -148,15 +149,14 @@ async def _redis_listen_with_retries(self): # pragma: no cover
148149
'retrying in '
149150
f'{retry_sleep} secs',
150151
extra={"redis_exception": str(exc)})
151-
connect = True
152+
self.connected = False
152153
await asyncio.sleep(retry_sleep)
153154
retry_sleep *= 2
154155
if retry_sleep > 60:
155156
retry_sleep = 60
156157

157158
async def _listen(self): # pragma: no cover
158159
channel = self.channel.encode('utf-8')
159-
await self.pubsub.subscribe(self.channel)
160160
async for message in self._redis_listen_with_retries():
161161
if message['channel'] == channel and \
162162
message['type'] == 'message' and 'data' in message:

src/socketio/redis_manager.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ def __init__(self, url='redis://localhost:6379/0', channel='socketio',
8383
super().__init__(channel=channel, write_only=write_only, logger=logger)
8484
self.redis_url = url
8585
self.redis_options = redis_options or {}
86-
self._redis_connect()
86+
self.connected = False
87+
self.redis = None
88+
self.pubsub = None
8789

8890
def initialize(self): # pragma: no cover
8991
super().initialize()
@@ -143,22 +145,22 @@ def _redis_connect(self):
143145
self.redis = module.Redis.from_url(self.redis_url,
144146
**self.redis_options)
145147
self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True)
148+
self.connected = True
146149

147150
def _publish(self, data): # pragma: no cover
148-
retry = True
149151
_, error = self._get_redis_module_and_error()
150-
while True:
152+
for retries_left in range(1, -1, -1): # 2 attempts
151153
try:
152-
if not retry:
154+
if not self.connected:
153155
self._redis_connect()
154156
return self.redis.publish(self.channel, json.dumps(data))
155157
except error as exc:
156-
if retry:
158+
if retries_left > 0:
157159
logger.error(
158160
'Cannot publish to redis... retrying',
159161
extra={"redis_exception": str(exc)}
160162
)
161-
retry = False
163+
self.connected = False
162164
else:
163165
logger.error(
164166
'Cannot publish to redis... giving up',
@@ -168,11 +170,10 @@ def _publish(self, data): # pragma: no cover
168170

169171
def _redis_listen_with_retries(self): # pragma: no cover
170172
retry_sleep = 1
171-
connect = False
172173
_, error = self._get_redis_module_and_error()
173174
while True:
174175
try:
175-
if connect:
176+
if not self.connected:
176177
self._redis_connect()
177178
self.pubsub.subscribe(self.channel)
178179
retry_sleep = 1
@@ -181,15 +182,14 @@ def _redis_listen_with_retries(self): # pragma: no cover
181182
logger.error('Cannot receive from redis... '
182183
f'retrying in {retry_sleep} secs',
183184
extra={"redis_exception": str(exc)})
184-
connect = True
185+
self.connected = False
185186
time.sleep(retry_sleep)
186187
retry_sleep *= 2
187188
if retry_sleep > 60:
188189
retry_sleep = 60
189190

190191
def _listen(self): # pragma: no cover
191192
channel = self.channel.encode('utf-8')
192-
self.pubsub.subscribe(self.channel)
193193
for message in self._redis_listen_with_retries():
194194
if message['channel'] == channel and \
195195
message['type'] == 'message' and 'data' in message:

tests/async/test_redis_manager.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def test_redis_not_installed(self):
1212
async_redis_manager.aioredis = None
1313

1414
with pytest.raises(RuntimeError):
15-
AsyncRedisManager('redis://')
15+
AsyncRedisManager('redis://')._redis_connect()
1616
assert AsyncRedisManager('unix:///var/sock/redis.sock') is not None
1717

1818
async_redis_manager.aioredis = saved_redis
@@ -22,7 +22,7 @@ def test_valkey_not_installed(self):
2222
async_redis_manager.aiovalkey = None
2323

2424
with pytest.raises(RuntimeError):
25-
AsyncRedisManager('valkey://')
25+
AsyncRedisManager('valkey://')._redis_connect()
2626
assert AsyncRedisManager('unix:///var/sock/redis.sock') is not None
2727

2828
async_redis_manager.aiovalkey = saved_valkey
@@ -34,18 +34,18 @@ def test_redis_valkey_not_installed(self):
3434
async_redis_manager.aiovalkey = None
3535

3636
with pytest.raises(RuntimeError):
37-
AsyncRedisManager('redis://')
37+
AsyncRedisManager('redis://')._redis_connect()
3838
with pytest.raises(RuntimeError):
39-
AsyncRedisManager('valkey://')
39+
AsyncRedisManager('valkey://')._redis_connect()
4040
with pytest.raises(RuntimeError):
41-
AsyncRedisManager('unix:///var/sock/redis.sock')
41+
AsyncRedisManager('unix:///var/sock/redis.sock')._redis_connect()
4242

4343
async_redis_manager.aioredis = saved_redis
4444
async_redis_manager.aiovalkey = saved_valkey
4545

4646
def test_bad_url(self):
4747
with pytest.raises(ValueError):
48-
AsyncRedisManager('http://localhost:6379')
48+
AsyncRedisManager('http://localhost:6379')._redis_connect()
4949

5050
def test_redis_connect(self):
5151
urls = [
@@ -72,6 +72,8 @@ def test_redis_connect(self):
7272
]
7373
for url in urls:
7474
c = AsyncRedisManager(url)
75+
assert c.redis is None
76+
c._redis_connect()
7577
assert isinstance(c.redis, redis.asyncio.Redis)
7678

7779
def test_valkey_connect(self):
@@ -102,6 +104,8 @@ def test_valkey_connect(self):
102104
]
103105
for url in urls:
104106
c = AsyncRedisManager(url)
107+
assert c.redis is None
108+
c._redis_connect()
105109
assert isinstance(c.redis, valkey.asyncio.Valkey)
106110

107111
async_redis_manager.aioredis = saved_redis

tests/common/test_redis_manager.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def test_redis_not_installed(self):
1212
redis_manager.redis = None
1313

1414
with pytest.raises(RuntimeError):
15-
RedisManager('redis://')
15+
RedisManager('redis://')._redis_connect()
1616
assert RedisManager('unix:///var/sock/redis.sock') is not None
1717

1818
redis_manager.redis = saved_redis
@@ -22,7 +22,7 @@ def test_valkey_not_installed(self):
2222
redis_manager.valkey = None
2323

2424
with pytest.raises(RuntimeError):
25-
RedisManager('valkey://')
25+
RedisManager('valkey://')._redis_connect()
2626
assert RedisManager('unix:///var/sock/redis.sock') is not None
2727

2828
redis_manager.valkey = saved_valkey
@@ -34,18 +34,18 @@ def test_redis_valkey_not_installed(self):
3434
redis_manager.valkey = None
3535

3636
with pytest.raises(RuntimeError):
37-
RedisManager('redis://')
37+
RedisManager('redis://')._redis_connect()
3838
with pytest.raises(RuntimeError):
39-
RedisManager('valkey://')
39+
RedisManager('valkey://')._redis_connect()
4040
with pytest.raises(RuntimeError):
41-
RedisManager('unix:///var/sock/redis.sock')
41+
RedisManager('unix:///var/sock/redis.sock')._redis_connect()
4242

4343
redis_manager.redis = saved_redis
4444
redis_manager.valkey = saved_valkey
4545

4646
def test_bad_url(self):
4747
with pytest.raises(ValueError):
48-
RedisManager('http://localhost:6379')
48+
RedisManager('http://localhost:6379')._redis_connect()
4949

5050
def test_redis_connect(self):
5151
urls = [
@@ -72,6 +72,8 @@ def test_redis_connect(self):
7272
]
7373
for url in urls:
7474
c = RedisManager(url)
75+
assert c.redis is None
76+
c._redis_connect()
7577
assert isinstance(c.redis, redis.Redis)
7678

7779
def test_valkey_connect(self):
@@ -102,6 +104,8 @@ def test_valkey_connect(self):
102104
]
103105
for url in urls:
104106
c = RedisManager(url)
107+
assert c.redis is None
108+
c._redis_connect()
105109
assert isinstance(c.redis, valkey.Valkey)
106110

107111
redis_manager.redis = saved_redis

0 commit comments

Comments
 (0)