-
Notifications
You must be signed in to change notification settings - Fork 184
Riak ConnectionManager implemented with gevent.queue.Queue #142
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
Changes from all commits
9a4115e
5e743fb
c772cf6
7397117
d04cc25
41a7c25
4889eae
d52dfc4
2c03f18
8e2f40f
2e301e1
b21a631
15ef8fc
b901844
4f4eaa8
9c2e5f1
b249d58
f6367c1
a6cb9a7
d16dac1
5af6457
a10df14
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,9 +17,12 @@ | |
| """ | ||
|
|
||
| import httplib | ||
| import socket | ||
| import contextlib | ||
| import functools | ||
| import socket | ||
| from gevent import monkey | ||
|
|
||
| from gevent.queue import Queue | ||
|
|
||
| class ConnectionManager(object): | ||
|
|
||
|
|
@@ -28,76 +31,50 @@ class ConnectionManager(object): | |
| # Must have a close() method | ||
| connection_class = None | ||
|
|
||
| def __init__(self, hostports=[]): | ||
| def __init__(self, hostports, pool_size=10): | ||
| # We want a private copy of this list: either to detach the argument | ||
| # default, or to detach from the caller's list. | ||
| self.hostports = hostports[:] | ||
|
|
||
| # Open a connection to each specified host/port. On single-threaded | ||
| # systems, this will create a round-robin across all specified servers. | ||
| # When multi-threaded, this will give us an initial set for all the | ||
| # threads to work with (and more will be created, according to demand). | ||
| self.conns = [self.connection_class(host, port) | ||
| for host, port in hostports] | ||
|
|
||
| def add_hostport(self, host, port): | ||
| self.hostports.append((host, port)) | ||
|
|
||
| # Open an initial connection. For single-threaded, this adds to the | ||
| # round-robin pool. On multi-threaded, it simply gives us an extra | ||
| # connectiong for the load-balancing across the servers. | ||
| self.conns.append(self.connection_class(host, port)) | ||
|
|
||
| def remove_host(self, host, port=None): | ||
| if port is None: | ||
| self.hostports = [(h, p) for h, p in self.hostports | ||
| if h != host] | ||
| if type(hostports) is list: | ||
| if len(hostports) > pool_size: | ||
| raise Exception("pool_size cannot be larger than hostports") | ||
|
|
||
| self.pool_size = pool_size | ||
| self.hostports = hostports | ||
|
|
||
| queue_limit = pool_size * len(hostports) if type(hostports) is list else pool_size | ||
| self.queue = Queue(queue_limit) | ||
|
|
||
| # Patch httplib if we are using that, also patch the sockets | ||
| monkey.patch_all(select=False, os=False, ssl=False, subprocess=False) | ||
|
|
||
| if type(self.hostports) is list: | ||
| for host, port in self.hostports: | ||
| for i in range(0, pool_size): | ||
| self.queue.put(self.connection_class(host, port)) | ||
| else: | ||
| self.hostports.remove((host, port)) | ||
|
|
||
| # Now that the host/port pair has been removed from self.hostports, | ||
| # no connections on this pair will be added in .giveback(). Thus, the | ||
| # existing connections are all that may exist at this time. We'll | ||
| # snapshot the list, and look for offending connections, then try and | ||
| # remove them, being wary that race conditions may remove them before | ||
| # we can remove it. | ||
| for conn in self.conns[:]: | ||
| if conn.host == host and (port is None or conn.port == port): | ||
| try: | ||
| self.conns.remove(conn) | ||
| except ValueError: | ||
| # Another thread removed the connection. It won't be coming back, | ||
| # so we have nothing to do here. | ||
| pass | ||
| else: | ||
| # If the connection was still present (no ValueError), then we | ||
| # should go ahead and close it down. | ||
| conn.close() | ||
|
|
||
| # Just in case somebody uses a host/port combo and typos... | ||
| remove_hostport = remove_host | ||
|
|
||
| def take(self): | ||
| if len(self.conns) == 0: | ||
| # RACE: in a multi-threaded environment, a conn might arrive in | ||
| # self.conns, but... no biggy. If we're bouncing up against | ||
| # needing a new connection, then we'll just create one. | ||
| return self._new_connection() | ||
|
|
||
| # RACE: self.conns might empty out right now, so we need to protect | ||
| # our access to it. | ||
| try: | ||
| # round-robin: take from the front, we'll append when it comes back | ||
| return self.conns.pop(0) | ||
| except IndexError: | ||
| return self._new_connection() | ||
|
|
||
| def giveback(self, conn): | ||
| # Connections using a host/port pair that is NOT in self.hostports | ||
| # should be ignored. Likely, remove_host() was called while this | ||
| # connection was borrowed for some work. | ||
| if (conn.host, conn.port) in self.hostports: | ||
| self.conns.append(conn) | ||
| for i in range(0, pool_size): | ||
| self.queue.put(self.connection_class(hostports[0], hostports[1])) | ||
|
|
||
| def checkout(self): | ||
| """Checkout a connection from the queue - block if we have | ||
| none in the queue till one arrives.""" | ||
|
|
||
| return self.queue.get() | ||
|
|
||
| def checkin(self, conn): | ||
| """Checkin a connection back into the queue. | ||
|
|
||
| If it doesn't exist in the hostports list then | ||
| close it. | ||
| """ | ||
|
|
||
| # If we are tring to checkin a connection and the pool has | ||
| # already been re-filled, throw away the connection silently | ||
| if self.queue.qsize() >= self.pool_size: | ||
| conn.close() | ||
|
|
||
| if ((type(self.hostports) is list) and (conn.host, conn.port) in self.hostports) or (conn.host, conn.port) == self.hostports: | ||
| self.queue.put(conn) | ||
| else: | ||
| # Proactively close the connection. The caller won't know whether | ||
| # we put it into our list, or left the connection for the caller | ||
|
|
@@ -106,45 +83,22 @@ def giveback(self, conn): | |
|
|
||
| @contextlib.contextmanager | ||
| def withconn(self): | ||
| conn = self.take() | ||
| """Context managaer method.""" | ||
| conn = self.checkout() | ||
| try: | ||
| yield conn | ||
| finally: | ||
| self.giveback(conn) | ||
|
|
||
| def _new_connection(self): | ||
| if len(self.hostports) == 0: | ||
| raise NoHostsDefined() | ||
|
|
||
| # Grab the first host/port combo. We'll put this at the end, so that | ||
| # we do a round-robin on the host/port pairs. | ||
| host, port = self.hostports[0] | ||
| conn = self.connection_class(host, port) | ||
|
|
||
| if len(self.hostports) == 1: | ||
| # No rotation needed. | ||
| return conn | ||
|
|
||
| # Be careful about rotating. We want to append before removing, so that | ||
| # we never hit a len==0 race condition (which could prevent the creation | ||
| # of needed connections). | ||
| self.hostports.append((host, port)) | ||
|
|
||
| # RACE: another thread may have appended the same host/port pair. We | ||
| # will add another pair. Each thread will remove one (either [0], or | ||
| # one that had been appened), resulting in a correct state of a single | ||
| # pair in the list. | ||
| # RACE: another thread may get the host/port pair from hostports[0] | ||
| # before we have a chance to remove it. We don't need precision | ||
| # round-robin behavior; just something close. | ||
| # RACE: another thread may have removed hostports[0] (which we are | ||
| # also trying to remove), but it will have placed another copy at | ||
| # the end before doing so. We have also added a host/port pair, and | ||
| # will remove one, leaving the list in a correct state. | ||
| self.hostports.remove((host, port)) | ||
|
|
||
| return conn | ||
|
|
||
| self.checkin(conn) | ||
|
|
||
| def new(self): | ||
| """Create a new connection if we have room for it.""" | ||
|
|
||
| if self.queue.qsize() >= self.pool_size: | ||
| raise Exception("the queue is already full") | ||
|
|
||
| host, port = self.hostports[0] if type(self.hostports) is list else self.hostports | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why only select the first host?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's how you guys originally had it - it was too late for me to come up with something else... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, honestly I would just do it right rather than rely on what we currently have, the connection manager has lots of problems. ;)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't mind cleaning it up; it was just 4AM in the morning and I was doing the bare minimum :-p |
||
|
|
||
| self.queue.put(self.connection_class(host, port)) | ||
|
|
||
| class Socket(object): | ||
|
|
||
|
|
@@ -172,9 +126,9 @@ def close(self): | |
|
|
||
| class FactoryConnectionManager(ConnectionManager): | ||
|
|
||
| def __init__(self, connection_class, hostports=[]): | ||
| def __init__(self, connection_class, hostports, pool_size): | ||
| self.connection_class = connection_class | ||
| ConnectionManager.__init__(self, hostports) | ||
| ConnectionManager.__init__(self, hostports, pool_size) | ||
|
|
||
|
|
||
| def cm_using(connection_class): | ||
|
|
||
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.
Is it strictly necessary that the pool be the same size as the list of hosts? M:N seems perfectly reasonable to me.
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.
No it is not necessary - if you think M:N is an acceptable way to handle it I can do it that way; problem with providing a pool size of "10" could be that if you have a four node list (or larger) - that's 40 connections if the default pool_size isn't changed. Which isn't bad per-se but that many connections seems like over-kill to me?
Maybe I'm being too conservative with that...
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.
Oh, i misread the code. The exception message should be rewritten to say "hostports cannot be larger than pool_size". That makes more sense, but maybe it should still be
pool_size * len(hostports), wherepool_sizeis small. That way you don't get certain hosts arbitrarily preferred over others.