Package: ya.resourcepool
Yet Another Resource PoolA configurable resource pool. TheResourcePoolclass can allocate resources using a configurable function,
store them for later use, and also free them if the pool gets too large.All the code examples assume:>>> from ya.resourcepool import *
>>> import threading
>>>
>>> class R(int):
...     lock = threading.Lock()
...     current = 0  # last generated number
...     deallocated = []
...
...     def __new__(cls, *args, **kwds):
...         with R.lock:
...             R.current += 1
...             instance = super().__new__(cls, R.current)
...         return instance
...
...     def __init__(self):
...         self.alive = True
...
...     def close(self):
...         self.alive = False
...         R.deallocated.append(self)
...         return self
...
...     def use(self):
...         print(f'Using resource {self}')
...         return selfUsage examplesThe most basic example only configures the method of allocation of the resource and usesResourcePool.__call__()to get a resource from the pool:>>> pool = ResourcePool(alloc=R)
>>> with pool() as obj:
...     obj.use()
Using resource ...This assumes that the resource can be simply garbage collected, and that an unlimited
amount can be allocated.Note thatResourcePool.__call__()is supposed to be used as a context manager. If that is not needed,
the methodsResourcePool.pop()andResourcePool.push()can be used:>>> pool = ResourcePool(alloc=R)
>>> obj = pool.pop()
>>> obj.use()
Using resource ...
>>> pool.push(obj)Note thatResourcePool.pop()transfers ownership of the resource to the client code, and it is the client’s
responsibility to either clean up the resource or to return it to the pool usingResourcePool.push().TheResourcePool.push()method can also be used to add new resource to the pool:>>> pool = ResourcePool()  # no alloc argument
>>> obj = R()              # create/allocate a resource
>>> pool.push(obj)         # push without preceding popTheResourcePool.__call__()method is actually implemented in terms ofpopandpush.Limited resource setsIf there is only a limited amount of resource instances available, a set can be provided with theinitargument:>>> resources = [R()]
>>> pool = ResourcePool(init=resources)
>>> with pool() as obj:
...     obj.use()
Using resource ...In that case, no new resources will be allocated, and only the ones given in the initializer will be used.By default, trying to get a resource from a limited pool will raise an exception of typeResourcePoolEmptyif there is no free resource available at the moment:>>> pool = ResourcePool(init=[])  # note the empty list
>>> with pool() as obj:
...     obj.use()
Traceback (most recent call last):
   ...
ya.resourcepool.ResourcePoolEmptyAlternatively, a timeout in seconds can be given:>>> pool = ResourcePool()  # an empty list is actually the default for init
>>> with pool(timeout=5) as obj:
...     obj.use()
Traceback (most recent call last):
   ...
ya.resourcepool.ResourcePoolEmptyThis will raise the exception only after the given timeout of five seconds. Note that the timeout is a
floating point number, so fractions of seconds are possible. A timeout of zero or less will block forever
or until a resource becomes available.Theinitargument can be combined with theallocargument:>>> resources = [R()]
>>> pool = ResourcePool(init=resources, alloc=R)
>>> with pool() as obj:
...     obj.use()
Using resource ...This will use the initial resource list and only allocate new ones if the initial resources are exhausted.Resource deallocationResources will usually have to be deallocated at some point. The function to do this can be
given with thedeallocinitializer argument:>>> pool = ResourcePool(alloc=R, dealloc=R.close)
>>> with pool() as obj:
...     obj.use()
Using resource ...This will callR.close()when the pool gets garbage collected for all resources currently managed by the pool.Resource retention policySurplus resources can be deallocated by giveng themaxsizeargument to the pool initializer:>>> pool = ResourcePool(alloc=R, maxsize=100)When amaxsizeargument was given, and the pool size exceeds that number after returning a
resource to the pool, all the surplus will be deallocated. This process will also use the optionaldeallocargument, or will just remove it from the pool and have it garbage collected.There is an additional argumentminsizeto control the amount of resources that will be deallocated
in the overflow case:>>> pool = ResourcePool(alloc=R, maxsize=100, minsize=50)This will reduce the pool size to 50 by deallocating surplus resources when the size exceeds 100 after
apushoperation.An additional argumentmaxagecan be used to set the maximum time a resource shall be kept in the
pool. Theminsizeargument can be used to guarantee a minimal set of pooled resources,
regardless of age.Resource alive checkIt is possible to check the status of any pooled resources before returning them frompop. This
can be configured using thecheckargument:>>> pool = ResourcePool(alloc=R, check=lambda resource: resource.alive)
>>> with pool() as obj:
...     obj.use()
Using resource ...The object given incheckmust be a callable that takes a resource instance and returns a truthy
value. It will be called for a result value candidate ofpopbefore it is returned, and if the
result is convertible toFalse, then the resource is considered dead and will be discarded
without calling anydeallocprocedure.popwill then continue trying to get a valid resource.Shooting yourself in the footIt is possible to block a thread indefinitely by having an empty fixed-size pool and using a timeout of 0:>>> pool = ResourcePool()
>>>
>>> def allocate(pool):
...     pool.push(R())
>>>
>>> threading.Timer(5, allocate, (pool,)).start()
>>>
>>> with pool(timeout=0) as obj:
...     obj.use()
Using resource ...This code would block forever without theTimerthread that adds a new object to the pool after 5 seconds.