pool.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. # Copyright 2015 Yahoo.com
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import collections
  15. import contextlib
  16. import logging
  17. import sys
  18. import threading
  19. import time
  20. import six
  21. logger = logging.getLogger(__name__)
  22. class ObjectPool(object):
  23. """A pool of objects that release/creates/destroys as needed."""
  24. def __init__(self, obj_creator,
  25. after_remove=None, max_size=None,
  26. idle_timeout=0,
  27. lock_generator=None):
  28. self._used_objs = collections.deque()
  29. self._free_objs = collections.deque()
  30. self._obj_creator = obj_creator
  31. if lock_generator is None:
  32. self._lock = threading.Lock()
  33. else:
  34. self._lock = lock_generator()
  35. self._after_remove = after_remove
  36. max_size = max_size or 2 ** 31
  37. if not isinstance(max_size, six.integer_types) or max_size < 0:
  38. raise ValueError('"max_size" must be a positive integer')
  39. self.max_size = max_size
  40. self.idle_timeout = idle_timeout
  41. self._idle_clock = time.time if idle_timeout else int
  42. @property
  43. def used(self):
  44. return tuple(self._used_objs)
  45. @property
  46. def free(self):
  47. return tuple(self._free_objs)
  48. @property
  49. def used_count(self):
  50. return len(self._used_objs)
  51. @property
  52. def free_count(self):
  53. return len(self._free_objs)
  54. @contextlib.contextmanager
  55. def get_and_release(self, destroy_on_fail=False):
  56. obj = self.get()
  57. try:
  58. yield obj
  59. except Exception:
  60. exc_info = sys.exc_info()
  61. if not destroy_on_fail:
  62. self.release(obj)
  63. else:
  64. self.destroy(obj)
  65. six.reraise(exc_info[0], exc_info[1], exc_info[2])
  66. self.release(obj)
  67. def get(self):
  68. with self._lock:
  69. # Find a free object, removing any that have idled for too long.
  70. now = self._idle_clock()
  71. while self._free_objs:
  72. obj = self._free_objs.popleft()
  73. if now - obj._last_used <= self.idle_timeout:
  74. break
  75. if self._after_remove is not None:
  76. self._after_remove(obj)
  77. else:
  78. # No free objects, create a new one.
  79. curr_count = len(self._used_objs)
  80. if curr_count >= self.max_size:
  81. raise RuntimeError("Too many objects,"
  82. " %s >= %s" % (curr_count,
  83. self.max_size))
  84. obj = self._obj_creator()
  85. self._used_objs.append(obj)
  86. if self.used_count > 100:
  87. logger.warn('memcache pool stats: used = {}; free = {}; size = {}'.format(
  88. self.used_count, self.free_count, self.max_size))
  89. obj._last_used = now
  90. return obj
  91. def destroy(self, obj, silent=True):
  92. was_dropped = False
  93. with self._lock:
  94. try:
  95. self._used_objs.remove(obj)
  96. was_dropped = True
  97. except ValueError:
  98. if not silent:
  99. raise
  100. if was_dropped and self._after_remove is not None:
  101. self._after_remove(obj)
  102. def release(self, obj, silent=True):
  103. with self._lock:
  104. try:
  105. self._used_objs.remove(obj)
  106. self._free_objs.append(obj)
  107. obj._last_used = self._idle_clock()
  108. except ValueError:
  109. if not silent:
  110. raise
  111. def clear(self):
  112. if self._after_remove is not None:
  113. needs_destroy = []
  114. with self._lock:
  115. needs_destroy.extend(self._used_objs)
  116. needs_destroy.extend(self._free_objs)
  117. self._free_objs.clear()
  118. self._used_objs.clear()
  119. for obj in needs_destroy:
  120. self._after_remove(obj)
  121. else:
  122. with self._lock:
  123. self._free_objs.clear()
  124. self._used_objs.clear()