Skip to content

Commit e06af2a

Browse files
committed
SentinelConnectionPool plays better with threaded applications.
Prevent the pool from closing sockets on connections that are actively in use by other threads when the master address changes. Connections returned to the pool that are still connected to the old master will be disconnected gracefully. Fixes #1345
1 parent 7973bd9 commit e06af2a

File tree

3 files changed

+53
-14
lines changed

3 files changed

+53
-14
lines changed

CHANGES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
* Restore try/except clauses to __del__ methods. These will be removed
33
in 4.0 when more explicit resource management if enforced. #1339
44
* Update the master_address when Sentinels promote a new master. #847
5+
* Update SentinelConnectionPool to not forcefully disconnect other in-use
6+
connections which can negatively affect threaded applications. #1345
57
* 3.5.2 (May 14, 2020)
68
* Tune the locking in ConnectionPool.get_connection so that the lock is
79
not held while waiting for the socket to establish and validate the

redis/connection.py

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,18 +1230,43 @@ def release(self, connection):
12301230
"Releases the connection back to the pool"
12311231
self._checkpid()
12321232
with self._lock:
1233-
if connection.pid != self.pid:
1233+
try:
1234+
self._in_use_connections.remove(connection)
1235+
except KeyError:
1236+
# Gracefully fail when a connection is returned to this pool
1237+
# that the pool doesn't actually own
1238+
pass
1239+
1240+
if self.owns_connection(connection):
1241+
self._available_connections.append(connection)
1242+
else:
1243+
# pool doesn't own this connection. do not add it back
1244+
# to the pool and decrement the count so that another
1245+
# connection can take its place if needed
1246+
self._created_connections -= 1
1247+
connection.disconnect()
12341248
return
1235-
self._in_use_connections.remove(connection)
1236-
self._available_connections.append(connection)
12371249

1238-
def disconnect(self):
1239-
"Disconnects all connections in the pool"
1250+
def owns_connection(self, connection):
1251+
return connection.pid == self.pid
1252+
1253+
def disconnect(self, inuse_connections=True):
1254+
"""
1255+
Disconnects connections in the pool
1256+
1257+
If ``inuse_connections`` is True, disconnect connections that are
1258+
current in use, potentially by other threads. Otherwise only disconnect
1259+
connections that are idle in the pool.
1260+
"""
12401261
self._checkpid()
12411262
with self._lock:
1242-
all_conns = chain(self._available_connections,
1243-
self._in_use_connections)
1244-
for connection in all_conns:
1263+
if inuse_connections:
1264+
connections = chain(self._available_connections,
1265+
self._in_use_connections)
1266+
else:
1267+
connections = self._available_connections
1268+
1269+
for connection in connections:
12451270
connection.disconnect()
12461271

12471272

@@ -1375,7 +1400,13 @@ def release(self, connection):
13751400
"Releases the connection back to the pool."
13761401
# Make sure we haven't changed process.
13771402
self._checkpid()
1378-
if connection.pid != self.pid:
1403+
if not self.owns_connection(connection):
1404+
# pool doesn't own this connection. do not add it back
1405+
# to the pool. instead add a None value which is a placeholder
1406+
# that will cause the pool to recreate the connection if
1407+
# its needed.
1408+
connection.disconnect()
1409+
self.pool.put_nowait(None)
13791410
return
13801411

13811412
# Put the connection back into the pool.

redis/sentinel.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,22 @@ def reset(self):
9595
self.master_address = None
9696
self.slave_rr_counter = None
9797

98+
def owns_connection(self, connection):
99+
check = not self.is_master or \
100+
(self.is_master and
101+
self.master_address == (connection.host, connection.port))
102+
parent = super(SentinelConnectionPool, self)
103+
return check and parent.owns_connection(connection)
104+
98105
def get_master_address(self):
99106
master_address = self.sentinel_manager.discover_master(
100107
self.service_name)
101108
if self.is_master:
102-
if self.master_address is None:
103-
self.master_address = master_address
104-
elif master_address != self.master_address:
105-
# Master address changed, disconnect all clients in this pool
109+
if self.master_address != master_address:
106110
self.master_address = master_address
107-
self.disconnect()
111+
# disconnect any idle connections so that they reconnect
112+
# to the new master the next time that they are used.
113+
self.disconnect(inuse_connections=False)
108114
return master_address
109115

110116
def rotate_slaves(self):

0 commit comments

Comments
 (0)