_dummy_thread.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. """Drop-in replacement for the thread module.
  2. Meant to be used as a brain-dead substitute so that threaded code does
  3. not need to be rewritten for when the thread module is not present.
  4. Suggested usage is::
  5. try:
  6. import _thread
  7. except ImportError:
  8. import _dummy_thread as _thread
  9. """
  10. # Exports only things specified by thread documentation;
  11. # skipping obsolete synonyms allocate(), start_new(), exit_thread().
  12. __all__ = ['error', 'start_new_thread', 'exit', 'get_ident', 'allocate_lock',
  13. 'interrupt_main', 'LockType', 'RLock']
  14. # A dummy value
  15. TIMEOUT_MAX = 2**31
  16. # NOTE: this module can be imported early in the extension building process,
  17. # and so top level imports of other modules should be avoided. Instead, all
  18. # imports are done when needed on a function-by-function basis. Since threads
  19. # are disabled, the import lock should not be an issue anyway (??).
  20. error = RuntimeError
  21. def start_new_thread(function, args, kwargs={}):
  22. """Dummy implementation of _thread.start_new_thread().
  23. Compatibility is maintained by making sure that ``args`` is a
  24. tuple and ``kwargs`` is a dictionary. If an exception is raised
  25. and it is SystemExit (which can be done by _thread.exit()) it is
  26. caught and nothing is done; all other exceptions are printed out
  27. by using traceback.print_exc().
  28. If the executed function calls interrupt_main the KeyboardInterrupt will be
  29. raised when the function returns.
  30. """
  31. if type(args) != type(tuple()):
  32. raise TypeError("2nd arg must be a tuple")
  33. if type(kwargs) != type(dict()):
  34. raise TypeError("3rd arg must be a dict")
  35. global _main
  36. _main = False
  37. try:
  38. function(*args, **kwargs)
  39. except SystemExit:
  40. pass
  41. except:
  42. import traceback
  43. traceback.print_exc()
  44. _main = True
  45. global _interrupt
  46. if _interrupt:
  47. _interrupt = False
  48. raise KeyboardInterrupt
  49. def exit():
  50. """Dummy implementation of _thread.exit()."""
  51. raise SystemExit
  52. def get_ident():
  53. """Dummy implementation of _thread.get_ident().
  54. Since this module should only be used when _threadmodule is not
  55. available, it is safe to assume that the current process is the
  56. only thread. Thus a constant can be safely returned.
  57. """
  58. return 1
  59. def allocate_lock():
  60. """Dummy implementation of _thread.allocate_lock()."""
  61. return LockType()
  62. def stack_size(size=None):
  63. """Dummy implementation of _thread.stack_size()."""
  64. if size is not None:
  65. raise error("setting thread stack size not supported")
  66. return 0
  67. def _set_sentinel():
  68. """Dummy implementation of _thread._set_sentinel()."""
  69. return LockType()
  70. class LockType(object):
  71. """Class implementing dummy implementation of _thread.LockType.
  72. Compatibility is maintained by maintaining self.locked_status
  73. which is a boolean that stores the state of the lock. Pickling of
  74. the lock, though, should not be done since if the _thread module is
  75. then used with an unpickled ``lock()`` from here problems could
  76. occur from this class not having atomic methods.
  77. """
  78. def __init__(self):
  79. self.locked_status = False
  80. def acquire(self, waitflag=None, timeout=-1):
  81. """Dummy implementation of acquire().
  82. For blocking calls, self.locked_status is automatically set to
  83. True and returned appropriately based on value of
  84. ``waitflag``. If it is non-blocking, then the value is
  85. actually checked and not set if it is already acquired. This
  86. is all done so that threading.Condition's assert statements
  87. aren't triggered and throw a little fit.
  88. """
  89. if waitflag is None or waitflag:
  90. self.locked_status = True
  91. return True
  92. else:
  93. if not self.locked_status:
  94. self.locked_status = True
  95. return True
  96. else:
  97. if timeout > 0:
  98. import time
  99. time.sleep(timeout)
  100. return False
  101. __enter__ = acquire
  102. def __exit__(self, typ, val, tb):
  103. self.release()
  104. def release(self):
  105. """Release the dummy lock."""
  106. # XXX Perhaps shouldn't actually bother to test? Could lead
  107. # to problems for complex, threaded code.
  108. if not self.locked_status:
  109. raise error
  110. self.locked_status = False
  111. return True
  112. def locked(self):
  113. return self.locked_status
  114. def __repr__(self):
  115. return "<%s %s.%s object at %s>" % (
  116. "locked" if self.locked_status else "unlocked",
  117. self.__class__.__module__,
  118. self.__class__.__qualname__,
  119. hex(id(self))
  120. )
  121. class RLock(LockType):
  122. """Dummy implementation of threading._RLock.
  123. Re-entrant lock can be aquired multiple times and needs to be released
  124. just as many times. This dummy implemention does not check wheter the
  125. current thread actually owns the lock, but does accounting on the call
  126. counts.
  127. """
  128. def __init__(self):
  129. super().__init__()
  130. self._levels = 0
  131. def acquire(self, waitflag=None, timeout=-1):
  132. """Aquire the lock, can be called multiple times in succession.
  133. """
  134. locked = super().acquire(waitflag, timeout)
  135. if locked:
  136. self._levels += 1
  137. return locked
  138. def release(self):
  139. """Release needs to be called once for every call to acquire().
  140. """
  141. if self._levels == 0:
  142. raise error
  143. if self._levels == 1:
  144. super().release()
  145. self._levels -= 1
  146. # Used to signal that interrupt_main was called in a "thread"
  147. _interrupt = False
  148. # True when not executing in a "thread"
  149. _main = True
  150. def interrupt_main():
  151. """Set _interrupt flag to True to have start_new_thread raise
  152. KeyboardInterrupt upon exiting."""
  153. if _main:
  154. raise KeyboardInterrupt
  155. else:
  156. global _interrupt
  157. _interrupt = True