forked from grnet/objpool
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path__init__.py
More file actions
487 lines (383 loc) · 16.4 KB
/
Copy path__init__.py
File metadata and controls
487 lines (383 loc) · 16.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# Copyright 2011-2012 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
"""Classes to support pools of arbitrary objects.
The :class:`ObjectPool` class in this module abstracts a pool
of arbitrary objects. Subclasses need to define the details regarding
creation, destruction, allocation and release of their specific objects.
"""
# This should work under gevent, because gevent monkey patches 'threading'
# if not, we can detect if running under gevent, e.g. using
# if 'gevent' in sys.modules:
# from gevent.coros import Semaphore
# else:
# from threading import Semaphore
from threading import Semaphore, Lock
from os import getpid
__all__ = ['ObjectPool', 'ObjectPoolError',
'PoolLimitError', 'PoolVerificationError']
import logging
log = logging.getLogger(__name__)
class ObjectPoolError(Exception):
pass
class PoolLimitError(ObjectPoolError):
pass
class PoolVerificationError(ObjectPoolError):
pass
class ObjectPool(object):
"""Generic Object Pool.
The pool consists of an object set and an allocation semaphore.
pool_get() gets an allocation from the semaphore
and an object from the pool set.
pool_put() releases an allocation to the semaphore
and puts an object back to the pool set.
Subclasses must implement these thread-safe hooks:
_pool_create()
used as a subclass hook to auto-create new objects in pool_get().
_pool_verify()
verifies objects before they are returned by pool_get()
_pool_cleanup()
cleans up and verifies objects before their return by pool_put().
While allocations are strictly accounted for and limited by
the semaphore, objects are expendable:
The hook provider and the caller are solely responsible for object
handling.
pool_get() may create an object if there is none in the pool set.
pool_get() may return no object, leaving object creation to the caller.
pool_put() may return no object to the pool set.
Objects to pool_put() to the pool need not be those from pool_get().
Objects to pool_get() need not be those from pool_put().
Callers beware:
- The pool limit size must be greater than the total working set of
objects, otherwise it will hang. When in doubt, use an impossibly large
size limit. Since the pool grows on demand, this will not waste
resources. However, in that case, the pool must not be used as a flow
control device (i.e. relying on pool_get() blocking to stop threads), as
the impossibly large pool size limit will defer blocking until too late.
- The pool cannot be shared among processes as the semaphore that it
relies upon will be copied when the new process is being created.
"""
def __init__(self, size=None, create=None, verify=None, cleanup=None):
self._pool_pid = getpid()
try:
self.size = int(size)
assert size >= 1
except:
raise ValueError("Invalid size for pool (positive integer "
"required): %r" % (size,))
self._create_func = create
self._verify_func = verify
self._cleanup_func = cleanup
self._semaphore = Semaphore(size) # Pool grows up to size limit
self._mutex = Lock() # Protect shared _set oject
self._set = set()
log.debug("Initialized pool %r", self)
def __repr__(self):
return ("<pool %d: size=%d, len(_set)=%d, semaphore=%d>" %
(id(self), self.size, len(self._set),
self._semaphore._Semaphore__value))
def pool_get(self, blocking=True, timeout=None, create=True, verify=True):
"""Get an object from the pool.
Get a pool allocation and an object from the pool set.
Raise PoolLimitError if the pool allocation limit has been reached.
If the pool set is empty, create a new object (create==True),
or return None (create==False) and let the caller create it.
All objects returned (except None) are verified.
"""
if self._pool_pid != getpid():
msg = ("You cannot use a pool in a different process "
"than it was created!")
raise AssertionError(msg)
# timeout argument only supported by gevent and py3k variants
# of Semaphore. acquire() will raise TypeError if timeout
# is specified but not supported by the underlying implementation.
log.debug("GET: about to get object from pool %r", self)
kw = {"blocking": blocking}
if timeout is not None:
kw["timeout"] = timeout
sema = self._semaphore
r = sema.acquire(**kw)
if not r:
raise PoolLimitError()
try:
created = 0
while 1:
with self._mutex:
try:
obj = self._set.pop()
except KeyError:
if create:
obj = self._pool_create()
created = 1
else:
obj = None
break
if not self._pool_verify(obj):
if created:
m = "Pool %r cannot verify new object %r" % (self, obj)
raise PoolVerificationError(m)
continue
break
except:
sema.release()
raise
# We keep _semaphore acquired, put() will release it
log.debug("GOT: object %r from pool %r", obj, self)
return obj
def pool_put(self, obj):
"""Put an object back into the pool.
Release an allocation and return an object to the pool.
If obj is None, or _pool_cleanup returns True,
then the allocation is released,
but no object returned to the pool set
"""
log.debug("PUT-BEFORE: about to put object %r back to pool %r",
obj, self)
if obj is not None and not self._pool_cleanup(obj):
with self._mutex:
if obj in self._set:
log.warning("Object %r already in _set of pool %r",
obj, self)
self._set.add(obj)
self._semaphore.release()
log.debug("PUT-AFTER: finished putting object %r back to pool %r",
obj, self)
def pool_create_free(self):
"""Create a free new object that is not put into the pool.
Just for convenience, let the users create objects with
the exact same configuration as those that are used with the pool
"""
obj = self._pool_create_free()
return obj
def _pool_create_free(self):
"""Create a free new object that is not put into the pool.
This should be overriden by pool classes.
Otherwise, it just calls _pool_create().
"""
return self._pool_create()
def _pool_create(self):
"""Create a new object to be used with this pool.
Create a new object to be used with this pool,
should be overriden in subclasses.
Must be thread-safe.
"""
if self._create_func is None:
raise NotImplementedError
return self._create_func()
def _pool_verify(self, obj):
"""Verify an object after getting it from the pool.
If it returns False, the object is discarded
and another one is drawn from the pool.
If the pool is empty, a new object is created.
If the new object fails to verify, pool_get() will fail.
Must be thread-safe.
"""
if self._verify_func is None:
return True
return self._verify_func(obj)
def _pool_cleanup(self, obj):
"""Cleanup an object before being put back into the pool.
Cleanup an object before it can be put back into the pull,
ensure it is in a stable, reusable state.
Must be thread-safe.
"""
if self._cleanup_func is not None:
return self._cleanup_func(obj)
class PooledObject(object):
"""Generic Object Pool context manager and pooled object proxy.
The PooledObject instance acts as a context manager to
be used in a with statement:
with PooledObject(...) as obj:
use(obj)
The with block above is roughly equivalent to:
pooled = PooledObject(...):
try:
obj = pooled.acquire()
assert(obj is pooled.obj)
use(obj)
finally:
pooled.release()
After exiting the with block, or releasing,
the code MUST not use the obj again in any way.
"""
# NOTE: We need all definitions at class-level
# to avoid infinite __gettatr__() recursion.
# This is also true for subclasses.
# NOTE: Typically you will only need to override
# __init__() and get_pool
# Initialization. Do not customize.
_pool_settings = None
_pool_get_settings = None
_pool_kwargs = None
_pool = None
obj = None
#####################################################
### Subclass attribute customization begins here. ###
_pool_log_prefix = "POOL"
_pool_class = ObjectPool
# default keyword args to pass to pool initialization
_pool_default_settings = (
('size', 25),
)
# keyword args to pass to pool_get
_pool_default_get_settings = (
('blocking', True),
#('timeout', None),
('create', True),
('verify', True),
)
# behavior settings
_pool_attach_context = False
_pool_disable_after_release = True
_pool_ignore_double_release = False
### Subclass attribute customization ends here. ###
#####################################################
def __init__(self, pool_settings=None, get_settings=None,
attach_context=None, disable_after_release=None,
ignore_double_release=None, **kwargs):
"""Initialize a PooledObject instance.
Accept only keyword arguments.
Some of them are filtered for this instance's configuration,
and the rest are saved in ._pool_kwargs for later use.
The filtered keywords are:
pool_settings: keyword args forwarded to pool instance initialization
in get_pool(), on top of the class defaults.
If not given, the remaining keyword args are
forwarded instead.
get_settings: keyword args forwarded to the pool's .pool_get() on top
of the class defaults.
attach_context: boolean overriding the class default.
If True, after getting an object from the pool,
attach self onto it before returning it,
so that the context manager caller can have
access to the manager object within the with: block.
disable_after_release:
boolean overriding the class default.
If True, the PooledObject will not allow a second
acquisition after the first release. For example,
the second with will raise an AssertionError:
manager = PooledObject()
with manager as c:
pass
with manager as c:
pass
ignore_double_release:
boolean overriding the class default.
If True, the PooledObject will allow consecutive
calls to release the underlying pooled object.
Only the first one has an effect.
If False, an AssertionError is raised.
"""
self._pool_kwargs = kwargs
self._pool = None
self.obj = None
_get_settings = dict(self._pool_default_get_settings)
if get_settings is not None:
_get_settings.update(get_settings)
self._pool_get_settings = _get_settings
if attach_context is not None:
self._pool_attach_context = attach_context
if pool_settings is None:
pool_settings = kwargs
_pool_settings = dict(self._pool_default_settings)
_pool_settings.update(**pool_settings)
self._pool_settings = _pool_settings
def get_pool(self):
"""Return a suitable pool object to work with.
Called within .acquire(), it is meant to be
overriden by sublasses, to create a new pool,
or retrieve an existing one, based on the PooledObject
initialization keywords stored in self._pool_kwargs.
"""
pool = self._pool_class(**self._pool_settings)
return pool
### Maybe overriding get_pool() and __init__() above is enough ###
def __repr__(self):
return ("<object %s of class %s: "
"proxy for object (%r) in pool (%r)>" % (
id(self), self.__class__.__name__,
self.obj, self._pool))
__str__ = __repr__
## Proxy the real object. Disabled until needed.
##
##def __getattr__(self, name):
## return getattr(self.obj, name)
##def __setattr__(self, name, value):
## if hasattr(self, name):
## _setattr = super(PooledObject, self).__setattr__
## _setattr(name, value)
## else:
## setattr(self.obj, name, value)
##def __delattr_(self, name):
## _delattr = super(PooledObject, self).__delattr__
## if hasattr(self, name):
## _delattr(self, name)
## else:
## delattr(self.obj, name)
def __enter__(self):
return self.acquire()
def __exit__(self, exc_type, exc_value, trace):
return self.release()
def acquire(self):
log.debug("%s Acquiring (context: %r)", self._pool_log_prefix, self)
pool = self._pool
if pool is False:
m = "%r: has been released. No further pool access allowed." % (
self,)
raise AssertionError(m)
if pool is not None:
m = "Double acquire in %r" % self
raise AssertionError(m)
pool = self.get_pool()
self._pool = pool
obj = pool.pool_get(**self._pool_get_settings)
if self._pool_attach_context:
obj._pool_context = self
self.obj = obj
log.debug("%s Acquired %r", self._pool_log_prefix, obj)
return obj
def release(self):
log.debug("%s Releasing (context: %r)", self._pool_log_prefix, self)
pool = self._pool
if pool is None:
m = "%r: no pool" % (self,)
raise AssertionError(m)
obj = self.obj
if obj is None:
if self._pool_ignore_double_release:
return
m = "%r: no object. Double release?" % (self,)
raise AssertionError(m)
pool.pool_put(obj)
self.obj = None
if self._pool_disable_after_release:
self._pool = False