17

Dead Simple Connection Pooling with Twisted

 3 years ago
source link: https://hynek.me/articles/dead-simple-connection-pooling-with-twisted/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Dead Simple Connection Pooling with Twisted

14 December 2011

There is this common notion, that asynchronous IO is hard and that writing a custom connection pool is even harder. The nice thing however is, that in reality asynchronous IO is just “weird” in the beginning – and that a connection pool using async IO is so simple it hurts.

Recently, I wrote an authentication backend for our nginx IMAP/POP3 load balancer and while it performed fine without any pool magic, I found the bulks of TIME_WAIT connections annoying (we even had to raise the system limit for open connections, twice), so I added a pool.

It took 6 extra lines and maybe 15 minutes.

In my case it was about LDAP access using ldaptor, but it doesn’t really matter so I’ll keep this article agnostic to the type of connection.

I put the LDAP related stuff in a separate class that has an instance variable called connections which is a deque – i.e. a FIFO. Why a FIFO? Since we have enough traffic at any time, we don’t have to worry about timeouts if the connections are added left and popped right – they are simply circled through and therefore kept fresh all the time.

I also added an instance variable called maxIdle that defines the maximal size of idle connections in our pool. You don’t want to have 1,000 connections in your pool just because of a single spike. This does not affect the total number of connections though!

So let’s start with a method to get a new connection:

def getConnection(self):
    try:
        defer.succeed(self.connections.pop())
    except IndexError:
        # create and return a connection

That would be the first three lines of our pooling. It tries to pop a connection from our pool and if there’s none left, we open a new one. If you wanted to limit the number of total active connections, you’d add a counter and a check here.

When you’re done, you want to free your connection again:

def returnConnection(self, connection):
    if len(self.connections) < self.maxIdle:
        self.connections.appendleft(connection)
    else:
        # kill the connection

And that marks the other three pool related lines: we check whether we want to pool the connection too and kill it if not. As there’s absolutely no concurrency or context switches that could lead to inconsistent state – this is really all it takes.


Hynek Schlawack

Hynek Schlawack

In ♥ with 🇪🇺, Python, Go, and networks. Blogger, speaker, PSF fellow, part-time beach bum.

Is my content helpful and/or enjoyable to you? Please consider supporting me! Every bit helps to motivate me in creating more. You can also buy me a coffee on Ko-fi – starting at 3 € and no account required.

If you speak German, please consider getting your domains and web hosting from my employer Variomedia. Without their support my community output would be considerably smaller.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK