You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

109 lines
4.2 KiB

  1. import Queue
  2. import threading
  3. import sys
  4. import decorator
  5. import inspect
  6. import types
  7. import functools
  8. # This file provides a class that will wrap an object and serialize
  9. # all calls to its methods. All calls to that object will be queued
  10. # and executed from a single thread, regardless of which thread makes
  11. # the call.
  12. # Based partially on http://stackoverflow.com/questions/2642515/
  13. class SerializerThread(threading.Thread):
  14. """Thread that retrieves call information from the queue, makes the
  15. call, and returns the results."""
  16. def __init__(self, classname, call_queue):
  17. threading.Thread.__init__(self)
  18. self.call_queue = call_queue
  19. def run(self):
  20. while True:
  21. result_queue, func, args, kwargs = self.call_queue.get()
  22. # Terminate if result_queue is None
  23. if result_queue is None:
  24. return
  25. exception = None
  26. result = None
  27. try:
  28. result = func(*args, **kwargs) # wrapped
  29. except:
  30. exception = sys.exc_info()
  31. # Ensure we delete these before returning a result, so
  32. # we don't unncessarily hold onto a reference while
  33. # we're waiting for the next call.
  34. del func, args, kwargs
  35. result_queue.put((exception, result))
  36. del exception, result
  37. def serializer_proxy(obj_or_type):
  38. """Wrap the given object or type in a SerializerObjectProxy.
  39. Returns a SerializerObjectProxy object that proxies all method
  40. calls to the object, as well as attribute retrievals.
  41. The proxied requests, including instantiation, are performed in a
  42. single thread and serialized between caller threads.
  43. """
  44. class SerializerCallProxy(object):
  45. def __init__(self, call_queue, func, objectproxy):
  46. self.call_queue = call_queue
  47. self.func = func
  48. # Need to hold a reference to object proxy so it doesn't
  49. # go away (and kill the thread) until after get called.
  50. self.objectproxy = objectproxy
  51. def __call__(self, *args, **kwargs):
  52. result_queue = Queue.Queue()
  53. self.call_queue.put((result_queue, self.func, args, kwargs))
  54. ( exc_info, result ) = result_queue.get()
  55. if exc_info is None:
  56. return result
  57. else:
  58. raise exc_info[0], exc_info[1], exc_info[2]
  59. class SerializerObjectProxy(object):
  60. def __init__(self, obj_or_type, *args, **kwargs):
  61. self.__object = obj_or_type
  62. try:
  63. if type(obj_or_type) in (types.TypeType, types.ClassType):
  64. classname = obj_or_type.__name__
  65. else:
  66. classname = obj_or_type.__class__.__name__
  67. except AttributeError: # pragma: no cover
  68. classname = "???"
  69. self.__call_queue = Queue.Queue()
  70. self.__thread = SerializerThread(classname, self.__call_queue)
  71. self.__thread.daemon = True
  72. self.__thread.start()
  73. self._thread_safe = True
  74. def __getattr__(self, key):
  75. if key.startswith("_SerializerObjectProxy__"): # pragma: no cover
  76. raise AttributeError
  77. attr = getattr(self.__object, key)
  78. if not callable(attr):
  79. getter = SerializerCallProxy(self.__call_queue, getattr, self)
  80. return getter(self.__object, key)
  81. r = SerializerCallProxy(self.__call_queue, attr, self)
  82. return r
  83. def __call__(self, *args, **kwargs):
  84. """Call this to instantiate the type, if a type was passed
  85. to serializer_proxy. Otherwise, pass the call through."""
  86. ret = SerializerCallProxy(self.__call_queue,
  87. self.__object, self)(*args, **kwargs)
  88. if type(self.__object) in (types.TypeType, types.ClassType):
  89. # Instantiation
  90. self.__object = ret
  91. return self
  92. return ret
  93. def __del__(self):
  94. self.__call_queue.put((None, None, None, None))
  95. self.__thread.join()
  96. return SerializerObjectProxy(obj_or_type)