Skip to main content

Resource management for Python objects

Project description

https://github.com/knowikow/ya.resourcepool.py/workflows/tests/badge.svg

Yet Another Resource Pool

A configurable resource pool. The ResourcePool class 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 self

Usage examples

The most basic example only configures the method of allocation of the resource and uses ResourcePool.__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 that ResourcePool.__call__() is supposed to be used as a context manager. If that is not needed, the methods ResourcePool.pop() and ResourcePool.push() can be used:

>>> pool = ResourcePool(alloc=R)
>>> obj = pool.pop()
>>> obj.use()
Using resource ...
>>> pool.push(obj)

Note that ResourcePool.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 using ResourcePool.push().

The ResourcePool.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 pop

The ResourcePool.__call__() method is actually implemented in terms of pop and push.

Limited resource sets

If there is only a limited amount of resource instances available, a set can be provided with the init argument:

>>> 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 type ResourcePoolEmpty if 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.ResourcePoolEmpty

Alternatively, 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.ResourcePoolEmpty

This 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.

The init argument can be combined with the alloc argument:

>>> 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 deallocation

Resources will usually have to be deallocated at some point. The function to do this can be given with the dealloc initializer argument:

>>> pool = ResourcePool(alloc=R, dealloc=R.close)
>>> with pool() as obj:
...     obj.use()
Using resource ...

This will call R.close() when the pool gets garbage collected for all resources currently managed by the pool.

Resource retention policy

Surplus resources can be deallocated by giveng the maxsize argument to the pool initializer:

>>> pool = ResourcePool(alloc=R, maxsize=100)

When a maxsize argument 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 optional dealloc argument, or will just remove it from the pool and have it garbage collected.

There is an additional argument minsize to 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 a push operation.

An additional argument maxage can be used to set the maximum time a resource shall be kept in the pool. The minsize argument can be used to guarantee a minimal set of pooled resources, regardless of age.

Resource alive check

It is possible to check the status of any pooled resources before returning them from pop. This can be configured using the check argument:

>>> pool = ResourcePool(alloc=R, check=lambda resource: resource.alive)
>>> with pool() as obj:
...     obj.use()
Using resource ...

The object given in check must be a callable that takes a resource instance and returns a truthy value. It will be called for a result value candidate of pop before it is returned, and if the result is convertible to False, then the resource is considered dead and will be discarded without calling any dealloc procedure. pop will then continue trying to get a valid resource.

Shooting yourself in the foot

It 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 the Timer thread that adds a new object to the pool after 5 seconds.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ya.resourcepool-0.1.1.tar.gz (9.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ya.resourcepool-0.1.1-py3-none-any.whl (6.4 kB view details)

Uploaded Python 3

File details

Details for the file ya.resourcepool-0.1.1.tar.gz.

File metadata

  • Download URL: ya.resourcepool-0.1.1.tar.gz
  • Upload date:
  • Size: 9.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.24.0 setuptools/49.2.0 requests-toolbelt/0.9.1 tqdm/4.48.2 CPython/3.8.4

File hashes

Hashes for ya.resourcepool-0.1.1.tar.gz
Algorithm Hash digest
SHA256 86ccea2d2e4cde6e2e07bf4c3103d981c6e224bbd5c4c1396b81713c1cce628c
MD5 70b0c999d2130943c067f4d84c2031a6
BLAKE2b-256 319591c391fd35707701cfe4ede6ba5e37cd90bdcb6f4219663d40b0d1227094

See more details on using hashes here.

File details

Details for the file ya.resourcepool-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: ya.resourcepool-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 6.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.24.0 setuptools/49.2.0 requests-toolbelt/0.9.1 tqdm/4.48.2 CPython/3.8.4

File hashes

Hashes for ya.resourcepool-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 4ac4896ea388349e3c2b32ac27fa860a8f1e4ac0d02af0d6224785987775dbd5
MD5 fc7484dbd1ccbb665758f263dfc4a6ca
BLAKE2b-256 ab785c24ca80109bbaa1dc62575fc8dc04e43450e0ad7d2c24601d1de4a5c7e1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page