Files
deb-python-eventlet/eventlet/pools.py

146 lines
4.1 KiB
Python

import collections
from eventlet import api
from eventlet import coros
class FanFailed(RuntimeError):
pass
class SomeFailed(FanFailed):
pass
class AllFailed(FanFailed):
pass
class Pool(object):
"""
When using the pool, if you do a get, you should **always** do a
:meth:`put`.
The pattern is::
thing = self.pool.get()
try:
thing.method()
finally:
self.pool.put(thing)
The maximum size of the pool can be modified at runtime via the
:attr:`max_size` attribute. Adjusting this number does not affect existing
items checked out of the pool, nor on any waiters who are waiting for an
item to free up. Some indeterminate number of :meth:`get`/:meth:`put`
cycles will be necessary before the new maximum size truly matches the
actual operation of the pool.
"""
def __init__(self, min_size=0, max_size=4, order_as_stack=False):
""" Pre-populates the pool with *min_size* items. Sets a hard limit to
the size of the pool -- it cannot contain any more items than
*max_size*, and if there are already *max_size* items 'checked out' of
the pool, the pool will cause any getter to cooperatively yield until an
item is put in.
*order_as_stack* governs the ordering of the items in the free pool.
If ``False`` (the default), the free items collection (of items that
were created and were put back in the pool) acts as a round-robin,
giving each item approximately equal utilization. If ``True``, the
free pool acts as a FILO stack, which preferentially re-uses items that
have most recently been used.
"""
self.min_size = min_size
self.max_size = max_size
self.order_as_stack = order_as_stack
self.current_size = 0
self.channel = coros.queue(0)
self.free_items = collections.deque()
for x in xrange(min_size):
self.current_size += 1
self.free_items.append(self.create())
def get(self):
"""Return an item from the pool, when one is available
"""
if self.free_items:
return self.free_items.popleft()
if self.current_size < self.max_size:
created = self.create()
self.current_size += 1
return created
return self.channel.wait()
try:
from contextlib import contextmanager
@contextmanager
def item(self):
""" Get an object out of the pool, for use with with statement.
>>> from eventlet import pools
>>> pool = pools.TokenPool(max_size=4)
>>> with pool.item() as obj:
... print "got token"
...
got token
>>> pool.free()
4
"""
obj = self.get()
try:
yield obj
finally:
self.put(obj)
except ImportError:
pass
def put(self, item):
"""Put an item back into the pool, when done
"""
if self.current_size > self.max_size:
self.current_size -= 1
return
if self.waiting():
self.channel.send(item)
else:
if self.order_as_stack:
self.free_items.appendleft(item)
else:
self.free_items.append(item)
def resize(self, new_size):
"""Resize the pool
"""
self.max_size = new_size
def free(self):
"""Return the number of free items in the pool.
"""
return len(self.free_items) + self.max_size - self.current_size
def waiting(self):
"""Return the number of routines waiting for a pool item.
"""
return self.channel.waiting()
def create(self):
"""Generate a new pool item
"""
raise NotImplementedError("Implement in subclass")
class Token(object):
pass
class TokenPool(Pool):
"""A pool which gives out tokens, an object indicating that
the person who holds the token has a right to consume some
limited resource.
"""
def create(self):
return Token()