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