rebuild.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. # -*- test-case-name: twisted.test.test_rebuild -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. *Real* reloading support for Python.
  6. """
  7. # System Imports
  8. import sys
  9. import types
  10. import time
  11. import linecache
  12. from imp import reload
  13. # Sibling Imports
  14. from twisted.python import log, reflect
  15. from twisted.python._oldstyle import _oldStyle
  16. lastRebuild = time.time()
  17. @_oldStyle
  18. class Sensitive:
  19. """
  20. A utility mixin that's sensitive to rebuilds.
  21. This is a mixin for classes (usually those which represent collections of
  22. callbacks) to make sure that their code is up-to-date before running.
  23. """
  24. lastRebuild = lastRebuild
  25. def needRebuildUpdate(self):
  26. yn = (self.lastRebuild < lastRebuild)
  27. return yn
  28. def rebuildUpToDate(self):
  29. self.lastRebuild = time.time()
  30. def latestVersionOf(self, anObject):
  31. """
  32. Get the latest version of an object.
  33. This can handle just about anything callable; instances, functions,
  34. methods, and classes.
  35. """
  36. t = type(anObject)
  37. if t == types.FunctionType:
  38. return latestFunction(anObject)
  39. elif t == types.MethodType:
  40. if anObject.im_self is None:
  41. return getattr(anObject.im_class, anObject.__name__)
  42. else:
  43. return getattr(anObject.im_self, anObject.__name__)
  44. elif t == types.InstanceType:
  45. # Kick it, if it's out of date.
  46. getattr(anObject, 'nothing', None)
  47. return anObject
  48. elif t == types.ClassType:
  49. return latestClass(anObject)
  50. else:
  51. log.msg('warning returning anObject!')
  52. return anObject
  53. _modDictIDMap = {}
  54. def latestFunction(oldFunc):
  55. """
  56. Get the latest version of a function.
  57. """
  58. # This may be CPython specific, since I believe jython instantiates a new
  59. # module upon reload.
  60. dictID = id(oldFunc.func_globals)
  61. module = _modDictIDMap.get(dictID)
  62. if module is None:
  63. return oldFunc
  64. return getattr(module, oldFunc.__name__)
  65. def latestClass(oldClass):
  66. """
  67. Get the latest version of a class.
  68. """
  69. module = reflect.namedModule(oldClass.__module__)
  70. newClass = getattr(module, oldClass.__name__)
  71. newBases = [latestClass(base) for base in newClass.__bases__]
  72. try:
  73. # This makes old-style stuff work
  74. newClass.__bases__ = tuple(newBases)
  75. return newClass
  76. except TypeError:
  77. if newClass.__module__ == "__builtin__":
  78. # __builtin__ members can't be reloaded sanely
  79. return newClass
  80. ctor = getattr(newClass, '__metaclass__', type)
  81. return ctor(newClass.__name__, tuple(newBases), dict(newClass.__dict__))
  82. class RebuildError(Exception):
  83. """
  84. Exception raised when trying to rebuild a class whereas it's not possible.
  85. """
  86. def updateInstance(self):
  87. """
  88. Updates an instance to be current.
  89. """
  90. try:
  91. self.__class__ = latestClass(self.__class__)
  92. except TypeError:
  93. if hasattr(self.__class__, '__slots__'):
  94. raise RebuildError("Can't rebuild class with __slots__ on Python < 2.6")
  95. else:
  96. raise
  97. def __getattr__(self, name):
  98. """
  99. A getattr method to cause a class to be refreshed.
  100. """
  101. if name == '__del__':
  102. raise AttributeError("Without this, Python segfaults.")
  103. updateInstance(self)
  104. log.msg("(rebuilding stale %s instance (%s))" % (reflect.qual(self.__class__), name))
  105. result = getattr(self, name)
  106. return result
  107. def rebuild(module, doLog=1):
  108. """
  109. Reload a module and do as much as possible to replace its references.
  110. """
  111. global lastRebuild
  112. lastRebuild = time.time()
  113. if hasattr(module, 'ALLOW_TWISTED_REBUILD'):
  114. # Is this module allowed to be rebuilt?
  115. if not module.ALLOW_TWISTED_REBUILD:
  116. raise RuntimeError("I am not allowed to be rebuilt.")
  117. if doLog:
  118. log.msg('Rebuilding %s...' % str(module.__name__))
  119. ## Safely handle adapter re-registration
  120. from twisted.python import components
  121. components.ALLOW_DUPLICATES = True
  122. d = module.__dict__
  123. _modDictIDMap[id(d)] = module
  124. newclasses = {}
  125. classes = {}
  126. functions = {}
  127. values = {}
  128. if doLog:
  129. log.msg(' (scanning %s): ' % str(module.__name__))
  130. for k, v in d.items():
  131. if type(v) == types.ClassType:
  132. # Failure condition -- instances of classes with buggy
  133. # __hash__/__cmp__ methods referenced at the module level...
  134. if v.__module__ == module.__name__:
  135. classes[v] = 1
  136. if doLog:
  137. log.logfile.write("c")
  138. log.logfile.flush()
  139. elif type(v) == types.FunctionType:
  140. if v.func_globals is module.__dict__:
  141. functions[v] = 1
  142. if doLog:
  143. log.logfile.write("f")
  144. log.logfile.flush()
  145. elif isinstance(v, type):
  146. if v.__module__ == module.__name__:
  147. newclasses[v] = 1
  148. if doLog:
  149. log.logfile.write("o")
  150. log.logfile.flush()
  151. values.update(classes)
  152. values.update(functions)
  153. fromOldModule = values.__contains__
  154. newclasses = newclasses.keys()
  155. classes = classes.keys()
  156. functions = functions.keys()
  157. if doLog:
  158. log.msg('')
  159. log.msg(' (reload %s)' % str(module.__name__))
  160. # Boom.
  161. reload(module)
  162. # Make sure that my traceback printing will at least be recent...
  163. linecache.clearcache()
  164. if doLog:
  165. log.msg(' (cleaning %s): ' % str(module.__name__))
  166. for clazz in classes:
  167. if getattr(module, clazz.__name__) is clazz:
  168. log.msg("WARNING: class %s not replaced by reload!" % reflect.qual(clazz))
  169. else:
  170. if doLog:
  171. log.logfile.write("x")
  172. log.logfile.flush()
  173. clazz.__bases__ = ()
  174. clazz.__dict__.clear()
  175. clazz.__getattr__ = __getattr__
  176. clazz.__module__ = module.__name__
  177. if newclasses:
  178. import gc
  179. for nclass in newclasses:
  180. ga = getattr(module, nclass.__name__)
  181. if ga is nclass:
  182. log.msg("WARNING: new-class %s not replaced by reload!" % reflect.qual(nclass))
  183. else:
  184. for r in gc.get_referrers(nclass):
  185. if getattr(r, '__class__', None) is nclass:
  186. r.__class__ = ga
  187. if doLog:
  188. log.msg('')
  189. log.msg(' (fixing %s): ' % str(module.__name__))
  190. modcount = 0
  191. for mk, mod in sys.modules.items():
  192. modcount = modcount + 1
  193. if mod == module or mod is None:
  194. continue
  195. if not hasattr(mod, '__file__'):
  196. # It's a builtin module; nothing to replace here.
  197. continue
  198. if hasattr(mod, '__bundle__'):
  199. # PyObjC has a few buggy objects which segfault if you hash() them.
  200. # It doesn't make sense to try rebuilding extension modules like
  201. # this anyway, so don't try.
  202. continue
  203. changed = 0
  204. for k, v in mod.__dict__.items():
  205. try:
  206. hash(v)
  207. except Exception:
  208. continue
  209. if fromOldModule(v):
  210. if type(v) == types.ClassType:
  211. if doLog:
  212. log.logfile.write("c")
  213. log.logfile.flush()
  214. nv = latestClass(v)
  215. else:
  216. if doLog:
  217. log.logfile.write("f")
  218. log.logfile.flush()
  219. nv = latestFunction(v)
  220. changed = 1
  221. setattr(mod, k, nv)
  222. else:
  223. # Replace bases of non-module classes just to be sure.
  224. if type(v) == types.ClassType:
  225. for base in v.__bases__:
  226. if fromOldModule(base):
  227. latestClass(v)
  228. if doLog and not changed and ((modcount % 10) ==0) :
  229. log.logfile.write(".")
  230. log.logfile.flush()
  231. components.ALLOW_DUPLICATES = False
  232. if doLog:
  233. log.msg('')
  234. log.msg(' Rebuilt %s.' % str(module.__name__))
  235. return module