saferef.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. # -*- coding: utf-8 -*-
  2. """Safe weakrefs, originally from :pypi:`pyDispatcher`.
  3. Provides a way to safely weakref any function, including bound methods (which
  4. aren't handled by the core weakref module).
  5. """
  6. from __future__ import absolute_import, unicode_literals
  7. import sys
  8. import traceback
  9. import weakref
  10. from celery.five import python_2_unicode_compatible
  11. __all__ = ['safe_ref']
  12. PY3 = sys.version_info[0] == 3
  13. def safe_ref(target, on_delete=None): # pragma: no cover
  14. """Return a *safe* weak reference to a callable target
  15. Arguments:
  16. target (Any): The object to be weakly referenced, if it's a
  17. bound method reference, will create a :class:`BoundMethodWeakref`,
  18. otherwise creates a simple :class:`weakref.ref`.
  19. on_delete (Callable): If provided, will have a hard reference stored
  20. to the callable to be called after the safe reference
  21. goes out of scope with the reference object, (either a
  22. :class:`weakref.ref` or a :class:`BoundMethodWeakref`) as argument.
  23. """
  24. if getattr(target, '__self__', None) is not None:
  25. # Turn a bound method into a BoundMethodWeakref instance.
  26. # Keep track of these instances for lookup by disconnect().
  27. assert hasattr(target, '__func__'), \
  28. """safe_ref target {0!r} has __self__, but no __func__: \
  29. don't know how to create reference""".format(target)
  30. return get_bound_method_weakref(target=target,
  31. on_delete=on_delete)
  32. if callable(on_delete):
  33. return weakref.ref(target, on_delete)
  34. else:
  35. return weakref.ref(target)
  36. @python_2_unicode_compatible
  37. class BoundMethodWeakref: # pragma: no cover
  38. """'Safe' and reusable weak references to instance methods.
  39. BoundMethodWeakref objects provide a mechanism for
  40. referencing a bound method without requiring that the
  41. method object itself (which is normally a transient
  42. object) is kept alive. Instead, the BoundMethodWeakref
  43. object keeps weak references to both the object and the
  44. function which together define the instance method.
  45. Attributes:
  46. key (str): the identity key for the reference, calculated
  47. by the class's :meth:`calculate_key` method applied to the
  48. target instance method.
  49. deletion_methods (Sequence[Callable]): Callables taking
  50. single argument, a reference to this object which
  51. will be called when *either* the target object or
  52. target function is garbage collected (i.e. when
  53. this object becomes invalid). These are specified
  54. as the on_delete parameters of :func:`safe_ref` calls.
  55. weak_self (weakref.ref): weak reference to the target object.
  56. weak_fun (weakref.ref): weak reference to the target function
  57. _all_instances (weakref.WeakValueDictionary):
  58. class attribute pointing to all live
  59. BoundMethodWeakref objects indexed by the class's
  60. `calculate_key(target)` method applied to the target
  61. objects. This weak value dictionary is used to
  62. short-circuit creation so that multiple references
  63. to the same (object, function) pair produce the
  64. same BoundMethodWeakref instance.
  65. """
  66. _all_instances = weakref.WeakValueDictionary()
  67. def __new__(cls, target, on_delete=None, *arguments, **named):
  68. """Create new instance or return current instance
  69. Note:
  70. Basically this method of construction allows us to
  71. short-circuit creation of references to already-
  72. referenced instance methods. The key corresponding
  73. to the target is calculated, and if there is already
  74. an existing reference, that is returned, with its
  75. deletionMethods attribute updated. Otherwise the
  76. new instance is created and registered in the table
  77. of already-referenced methods.
  78. """
  79. key = cls.calculate_key(target)
  80. current = cls._all_instances.get(key)
  81. if current is not None:
  82. current.deletion_methods.append(on_delete)
  83. return current
  84. else:
  85. base = super(BoundMethodWeakref, cls).__new__(cls)
  86. cls._all_instances[key] = base
  87. base.__init__(target, on_delete, *arguments, **named)
  88. return base
  89. def __init__(self, target, on_delete=None):
  90. """Return a weak-reference-like instance for a bound method.
  91. Arguments:
  92. target (Any): The instance-method target for the weak
  93. reference, must have `__self__` and `__func__` attributes
  94. and be reconstructable via::
  95. target.__func__.__get__(target.__self__)
  96. which is true of built-in instance methods.
  97. on_delete (Callable): Optional callback which will be called
  98. when this weak reference ceases to be valid
  99. (i.e. either the object or the function is garbage
  100. collected). Should take a single argument,
  101. which will be passed a pointer to this object.
  102. """
  103. def remove(weak, self=self):
  104. """Set self.is_dead to true when method or instance is destroyed"""
  105. methods = self.deletion_methods[:]
  106. del(self.deletion_methods[:])
  107. try:
  108. del(self.__class__._all_instances[self.key])
  109. except KeyError:
  110. pass
  111. for function in methods:
  112. try:
  113. if callable(function):
  114. function(self)
  115. except Exception as exc:
  116. try:
  117. traceback.print_exc()
  118. except AttributeError:
  119. print('Exception during saferef {0} cleanup function '
  120. '{1}: {2}'.format(self, function, exc))
  121. self.deletion_methods = [on_delete]
  122. self.key = self.calculate_key(target)
  123. self.weak_self = weakref.ref(target.__self__, remove)
  124. self.weak_fun = weakref.ref(target.__func__, remove)
  125. self.self_name = str(target.__self__)
  126. self.fun_name = str(target.__func__.__name__)
  127. def calculate_key(cls, target):
  128. """Calculate the reference key for this reference
  129. Returns:
  130. Tuple[int, int]: Currently this is a two-tuple of
  131. the `id()`'s of the target object and the target
  132. function respectively.
  133. """
  134. return id(target.__self__), id(target.__func__)
  135. calculate_key = classmethod(calculate_key)
  136. def __str__(self):
  137. return '{0}( {1}.{2} )'.format(
  138. type(self).__name__,
  139. self.self_name,
  140. self.fun_name,
  141. )
  142. def __repr__(self):
  143. return str(self)
  144. def __bool__(self):
  145. """Whether we are still a valid reference"""
  146. return self() is not None
  147. __nonzero__ = __bool__ # py2
  148. if not PY3:
  149. def __cmp__(self, other):
  150. """Compare with another reference"""
  151. if not isinstance(other, self.__class__):
  152. return cmp(self.__class__, type(other)) # noqa
  153. return cmp(self.key, other.key) # noqa
  154. def __call__(self):
  155. """Return a strong reference to the bound method
  156. If the target cannot be retrieved, then will
  157. return None, otherwise return a bound instance
  158. method for our object and function.
  159. Note:
  160. You may call this method any number of times,
  161. as it does not invalidate the reference.
  162. """
  163. target = self.weak_self()
  164. if target is not None:
  165. function = self.weak_fun()
  166. if function is not None:
  167. return function.__get__(target)
  168. class BoundNonDescriptorMethodWeakref(BoundMethodWeakref): # pragma: no cover
  169. """A specialized :class:`BoundMethodWeakref`, for platforms where
  170. instance methods are not descriptors.
  171. Warning:
  172. It assumes that the function name and the target attribute name are
  173. the same, instead of assuming that the function is a descriptor.
  174. This approach is equally fast, but not 100% reliable because
  175. functions can be stored on an attribute named differenty than the
  176. function's name such as in::
  177. >>> class A(object):
  178. ... pass
  179. >>> def foo(self):
  180. ... return 'foo'
  181. >>> A.bar = foo
  182. But this shouldn't be a common use case. So, on platforms where methods
  183. aren't descriptors (such as Jython) this implementation has the
  184. advantage of working in the most cases.
  185. """
  186. def __init__(self, target, on_delete=None):
  187. """Return a weak-reference-like instance for a bound method.
  188. Arguments:
  189. target (Any): the instance-method target for the weak
  190. reference, must have `__self__` and `__func__` attributes
  191. and be reconstructable via::
  192. target.__func__.__get__(target.__self__)
  193. which is true of built-in instance methods.
  194. on_delete (Callable): Optional callback which will be called
  195. when this weak reference ceases to be valid
  196. (i.e. either the object or the function is garbage
  197. collected). Should take a single argument,
  198. which will be passed a pointer to this object.
  199. """
  200. assert getattr(target.__self__, target.__name__) == target
  201. super(BoundNonDescriptorMethodWeakref, self).__init__(target,
  202. on_delete)
  203. def __call__(self):
  204. """Return a strong reference to the bound method
  205. If the target cannot be retrieved, then will
  206. return None, otherwise return a bound instance
  207. method for our object and function.
  208. Note:
  209. You may call this method any number of times,
  210. as it does not invalidate the reference.
  211. """
  212. target = self.weak_self()
  213. if target is not None:
  214. function = self.weak_fun()
  215. if function is not None:
  216. # Using curry() would be another option, but it erases the
  217. # "signature" of the function. That is, after a function is
  218. # curried, the inspect module can't be used to determine how
  219. # many arguments the function expects, nor what keyword
  220. # arguments it supports, and pydispatcher needs this
  221. # information.
  222. return getattr(target, function.__name__)
  223. def get_bound_method_weakref(target, on_delete): # pragma: no cover
  224. """Instantiates the appropiate :class:`BoundMethodWeakRef`, depending
  225. on the details of the underlying class method implementation."""
  226. if hasattr(target, '__get__'):
  227. # target method is a descriptor, so the default implementation works:
  228. return BoundMethodWeakref(target=target, on_delete=on_delete)
  229. else:
  230. # no luck, use the alternative implementation:
  231. return BoundNonDescriptorMethodWeakref(
  232. target=target, on_delete=on_delete)