-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make sentinel thread-safe during master failover #909
Conversation
@@ -59,9 +59,6 @@ def read_response(self): | |||
# When talking to a master, a ReadOnlyError when likely | |||
# indicates that the previous master that we're still connected | |||
# to has been demoted to a slave and there's a new master. | |||
# calling disconnect will force the connection to re-query | |||
# sentinel during the next connect() attempt. | |||
self.disconnect() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The connection will get closed when it's returned to the pool.
self.master_address = master_address | ||
elif master_address != self.master_address: | ||
# Master address changed, disconnect all clients in this pool | ||
self.disconnect() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All old connections will get closed when attempting to fetch them from the pool. I could theoretically close connections from _available_connections
here, but I don't know what would the thread-safety implications be. The fact that this code depends on atomic operations in Python is confusing enough.
redis/sentinel.py
Outdated
@@ -125,16 +120,36 @@ def rotate_slaves(self): | |||
pass | |||
raise SlaveNotFoundError('No slave found for %r' % (self.service_name)) | |||
|
|||
def _checkpid(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation in ConnectionPool
is thread-safe and the __init__()
call is not needed, reset()
does everything necessary.
master_address = self.sentinel_manager.discover_master( | ||
self.service_name) | ||
if self.is_master: | ||
if self.master_address is None: | ||
if master_address != self.master_address: | ||
self.master_address = master_address |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related pull request - #847
In the current version, the sentinel code tries to close all connections immediately after discovering there is a new master. This is a problem in multi-threaded environment, because neither `ConnectionPool.disconnect` nor `Connection.disconnect` are thread-safe. If you call `SentinelConnectionPool.disconnect` after master failover, that will close all connections that are potentially used from other threads, causing all kinds of errors. This change avoids that behavior by adding acquire/release checks, so connections that don't belong the current master are never returned to the pool and they are closed instead.
38a0157
to
bbf553c
Compare
This pull request is marked stale. It will be closed in 30 days if it is not updated. |
In the current version, the sentinel code tries to close all
connections immediately after discovering there is a new master.
This is a problem in multi-threaded environment, because
neither
ConnectionPool.disconnect
norConnection.disconnect
arethread-safe. If you call
SentinelConnectionPool.disconnect
after masterfailover, that will close all connections that are potentially used
from other threads, causing all kinds of errors.
This change avoids that behavior by adding acquire/release checks, so
connections that don't belong the current master are never returned
to the pool and they are closed instead.