stdpy: Annotate threading functions (#1765)

This commit is contained in:
WMOkiishi 2025-08-02 08:43:50 -06:00 committed by GitHub
parent f00cec70e6
commit 0e1a3f2ab7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 107 additions and 67 deletions

View File

@ -5,6 +5,8 @@ in some compilation models, Panda's threading constructs are
incompatible with the OS-provided threads used by Python's thread
module. """
from __future__ import annotations
__all__ = [
'error', 'LockType',
'start_new_thread',
@ -19,6 +21,9 @@ __all__ = [
from panda3d import core
import sys
from collections.abc import Callable, Iterable, Mapping
from typing import Any
if sys.platform == "win32":
TIMEOUT_MAX = float(0xffffffff // 1000)
else:
@ -41,12 +46,12 @@ class LockType:
allows a different thread to release the lock than the one that
acquired it. """
def __init__(self):
def __init__(self) -> None:
self.__lock = core.Mutex('PythonLock')
self.__cvar = core.ConditionVar(self.__lock)
self.__locked = False
def acquire(self, waitflag = 1, timeout = -1):
def acquire(self, waitflag: bool = True, timeout: float = -1) -> bool:
self.__lock.acquire()
try:
if self.__locked and not waitflag:
@ -65,7 +70,7 @@ class LockType:
finally:
self.__lock.release()
def release(self):
def release(self) -> None:
self.__lock.acquire()
try:
if not self.__locked:
@ -90,18 +95,23 @@ class LockType:
_counter = 0
def _newname(template="Thread-%d"):
def _newname(template: str = "Thread-%d") -> str:
global _counter
_counter = _counter + 1
return template % _counter
_threads = {}
_threads: dict[int, tuple[core.Thread, dict[int, dict[str, Any]], Any | None]] = {}
_nextThreadId = 0
_threadsLock = core.Mutex('thread._threadsLock')
def start_new_thread(function, args, kwargs = {}, name = None):
def start_new_thread(
function: Callable[..., object],
args: Iterable[Any],
kwargs: Mapping[str, Any] = {},
name: str | None = None,
) -> int:
def threadFunc(threadId, function = function, args = args, kwargs = kwargs):
try:
try:
@ -132,7 +142,7 @@ def start_new_thread(function, args, kwargs = {}, name = None):
_threadsLock.release()
def _add_thread(thread, wrapper):
def _add_thread(thread: core.Thread, wrapper: Any) -> int:
""" Adds the indicated core.Thread object, with the indicated Python
wrapper, to the thread list. Returns the new thread ID. """
@ -150,7 +160,7 @@ def _add_thread(thread, wrapper):
_threadsLock.release()
def _get_thread_wrapper(thread, wrapperClass):
def _get_thread_wrapper(thread: core.Thread, wrapperClass: Callable[[core.Thread, int], Any]) -> Any:
""" Returns the thread wrapper for the indicated thread. If there
is not one, creates an instance of the indicated wrapperClass
instead. """
@ -222,7 +232,7 @@ def _get_thread_locals(thread, i):
_threadsLock.release()
def _remove_thread_id(threadId):
def _remove_thread_id(threadId: int) -> None:
""" Removes the thread with the indicated ID from the thread list. """
# On interpreter shutdown, Python may set module globals to None.
@ -254,7 +264,7 @@ def allocate_lock() -> LockType:
return LockType()
def get_ident():
def get_ident() -> int:
return core.Thread.getCurrentThread().this

View File

@ -21,12 +21,17 @@ easier to use and understand.
It is permissible to mix-and-match both threading and threading2
within the same application. """
from __future__ import annotations
from panda3d import core
from direct.stdpy import thread as _thread
import sys as _sys
import weakref
from collections.abc import Callable, Iterable, Mapping
from typing import Any, NoReturn
__all__ = [
'Thread',
'Lock', 'RLock',
@ -54,10 +59,14 @@ class ThreadBase:
""" A base class for both Thread and ExternalThread in this
module. """
def __init__(self):
name: str
ident: int
daemon: bool
def __init__(self) -> None:
pass
def getName(self):
def getName(self) -> str:
return self.name
def isDaemon(self):
@ -92,7 +101,15 @@ class Thread(ThreadBase):
object. The wrapper is designed to emulate Python's own
threading.Thread object. """
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, daemon=None):
def __init__(
self,
group: None = None,
target: Callable[..., object] | None = None,
name: str | None = None,
args: Iterable[Any] = (),
kwargs: Mapping[str, Any] = {},
daemon: bool | None = None,
) -> None:
ThreadBase.__init__(self)
assert group is None
@ -131,7 +148,7 @@ class Thread(ThreadBase):
isAlive = is_alive
def start(self):
def start(self) -> None:
thread = self.__thread
if thread is None or thread.is_started():
raise RuntimeError
@ -147,7 +164,7 @@ class Thread(ThreadBase):
self.__target(*self.__args, **self.__kwargs)
def join(self, timeout = None):
def join(self, timeout: float | None = None) -> None:
# We don't support a timed join here, sorry.
assert timeout is None
thread = self.__thread
@ -157,7 +174,7 @@ class Thread(ThreadBase):
self.__thread = None
_thread._remove_thread_id(self.ident)
def setName(self, name):
def setName(self, name: str) -> None:
self.__dict__['name'] = name
self.__thread.setName(name)
@ -166,7 +183,7 @@ class ExternalThread(ThreadBase):
""" Returned for a Thread object that wasn't created by this
interface. """
def __init__(self, extThread, threadId):
def __init__(self, extThread: core.Thread, threadId: int) -> None:
ThreadBase.__init__(self)
self.__thread = extThread
@ -196,7 +213,7 @@ class ExternalThread(ThreadBase):
class MainThread(ExternalThread):
""" Returned for the MainThread object. """
def __init__(self, extThread, threadId):
def __init__(self, extThread: core.Thread, threadId: int) -> None:
ExternalThread.__init__(self, extThread, threadId)
self.__dict__['daemon'] = False
@ -206,7 +223,7 @@ class Lock(core.Mutex):
The wrapper is designed to emulate Python's own threading.Lock
object. """
def __init__(self, name = "PythonLock"):
def __init__(self, name: str = "PythonLock") -> None:
core.Mutex.__init__(self, name)
@ -224,7 +241,7 @@ class Condition(core.ConditionVar):
object. The wrapper is designed to emulate Python's own
threading.Condition object. """
def __init__(self, lock = None):
def __init__(self, lock: Lock | RLock | None = None) -> None:
if not lock:
lock = Lock()
@ -241,7 +258,7 @@ class Condition(core.ConditionVar):
def release(self):
self.__lock.release()
def wait(self, timeout = None):
def wait(self, timeout: float | None = None) -> None:
if timeout is None:
core.ConditionVar.wait(self)
else:
@ -373,8 +390,9 @@ class Timer(Thread):
self.finished.set()
def _create_thread_wrapper(t, threadId):
def _create_thread_wrapper(t: core.Thread, threadId: int) -> ExternalThread:
""" Creates a thread wrapper for the indicated external thread. """
pyt: ExternalThread
if isinstance(t, core.MainThread):
pyt = MainThread(t, threadId)
else:
@ -383,7 +401,7 @@ def _create_thread_wrapper(t, threadId):
return pyt
def current_thread():
def current_thread() -> ThreadBase:
t = core.Thread.getCurrentThread()
return _thread._get_thread_wrapper(t, _create_thread_wrapper)
@ -430,5 +448,5 @@ def setprofile(func):
_setprofile_func = func
def stack_size(size = None):
def stack_size(size: object = None) -> NoReturn:
raise ThreadError

View File

@ -13,6 +13,8 @@ to import Panda's thread reimplementation instead of the system thread
module, and so it is therefore layered on top of Panda's thread
implementation. """
from __future__ import annotations
import sys as _sys
import atexit as _atexit
@ -21,8 +23,10 @@ from direct.stdpy.thread import stack_size, _newname, _local as local
from panda3d import core
_sleep = core.Thread.sleep
from collections.abc import Callable, Iterable, Mapping
from time import time as _time
from traceback import format_exc as _format_exc
from typing import Any
__all__ = ['get_ident', 'active_count', 'Condition', 'current_thread',
'enumerate', 'main_thread', 'TIMEOUT_MAX',
@ -51,12 +55,12 @@ if __debug__:
class _Verbose(object):
def __init__(self, verbose=None):
def __init__(self, verbose: bool | None = None) -> None:
if verbose is None:
verbose = _VERBOSE
self.__verbose = verbose
def _note(self, format, *args):
def _note(self, format: str, *args: Any) -> None:
if self.__verbose:
format = format % args
format = "%s: %s\n" % (
@ -66,9 +70,9 @@ if __debug__:
else:
# Disable this when using "python -O"
class _Verbose(object): # type: ignore[no-redef]
def __init__(self, verbose=None):
def __init__(self, verbose: bool | None = None) -> None:
pass
def _note(self, *args):
def _note(self, *args) -> None:
pass
# Support for profile and trace hooks
@ -88,15 +92,15 @@ def settrace(func):
Lock = _allocate_lock
def RLock(*args, **kwargs):
return _RLock(*args, **kwargs)
def RLock(verbose: bool | None = None) -> _RLock:
return _RLock(verbose)
class _RLock(_Verbose):
def __init__(self, verbose=None):
def __init__(self, verbose: bool | None = None) -> None:
_Verbose.__init__(self, verbose)
self.__block = _allocate_lock()
self.__owner = None
self.__owner: Thread | None = None
self.__count = 0
def __repr__(self):
@ -105,13 +109,13 @@ class _RLock(_Verbose):
self.__owner and self.__owner.getName(),
self.__count)
def acquire(self, blocking=1):
def acquire(self, blocking: bool = True) -> bool:
me = currentThread()
if self.__owner is me:
self.__count = self.__count + 1
if __debug__:
self._note("%s.acquire(%s): recursive success", self, blocking)
return 1
return True
rc = self.__block.acquire(blocking)
if rc:
self.__owner = me
@ -125,7 +129,7 @@ class _RLock(_Verbose):
__enter__ = acquire
def release(self):
def release(self) -> None:
me = currentThread()
assert self.__owner is me, "release() of un-acquire()d lock"
self.__count = count = self.__count - 1
@ -163,12 +167,12 @@ class _RLock(_Verbose):
return self.__owner is currentThread()
def Condition(*args, **kwargs):
return _Condition(*args, **kwargs)
def Condition(lock: _thread.LockType | _RLock | None = None, verbose: bool | None = None) -> _Condition:
return _Condition(lock, verbose)
class _Condition(_Verbose):
def __init__(self, lock=None, verbose=None):
def __init__(self, lock: _thread.LockType | _RLock | None = None, verbose: bool | None = None) -> None:
_Verbose.__init__(self, verbose)
if lock is None:
lock = RLock()
@ -180,18 +184,18 @@ class _Condition(_Verbose):
# these override the default implementations (which just call
# release() and acquire() on the lock). Ditto for _is_owned().
try:
self._release_save = lock._release_save
self._release_save = lock._release_save # type: ignore[method-assign, union-attr]
except AttributeError:
pass
try:
self._acquire_restore = lock._acquire_restore
self._acquire_restore = lock._acquire_restore # type: ignore[method-assign, union-attr]
except AttributeError:
pass
try:
self._is_owned = lock._is_owned
self._is_owned = lock._is_owned # type: ignore[method-assign, union-attr]
except AttributeError:
pass
self.__waiters = []
self.__waiters: list[_thread.LockType] = []
def __enter__(self):
return self.__lock.__enter__()
@ -202,22 +206,22 @@ class _Condition(_Verbose):
def __repr__(self):
return "<Condition(%s, %d)>" % (self.__lock, len(self.__waiters))
def _release_save(self): # pylint: disable=method-hidden
def _release_save(self) -> Any: # pylint: disable=method-hidden
self.__lock.release() # No state to save
def _acquire_restore(self, x): # pylint: disable=method-hidden
def _acquire_restore(self, x) -> None: # pylint: disable=method-hidden
self.__lock.acquire() # Ignore saved state
def _is_owned(self): # pylint: disable=method-hidden
def _is_owned(self) -> bool: # pylint: disable=method-hidden
# Return True if lock is owned by currentThread.
# This method is called only if __lock doesn't have _is_owned().
if self.__lock.acquire(0):
if self.__lock.acquire(False):
self.__lock.release()
return False
else:
return True
def wait(self, timeout=None):
def wait(self, timeout: float | None = None) -> None:
assert self._is_owned(), "wait() of un-acquire()d lock"
waiter = _allocate_lock()
waiter.acquire()
@ -237,7 +241,7 @@ class _Condition(_Verbose):
endtime = _time() + timeout
delay = 0.0005 # 500 us -> initial delay of 1 ms
while True:
gotit = waiter.acquire(0)
gotit = waiter.acquire(False)
if gotit:
break
remaining = endtime - _time()
@ -258,7 +262,7 @@ class _Condition(_Verbose):
finally:
self._acquire_restore(saved_state)
def notify(self, n=1):
def notify(self, n: int = 1) -> None:
assert self._is_owned(), "notify() of un-acquire()d lock"
__waiters = self.__waiters
waiters = __waiters[:n]
@ -275,7 +279,7 @@ class _Condition(_Verbose):
except ValueError:
pass
def notifyAll(self):
def notifyAll(self) -> None:
self.notify(len(self.__waiters))
@ -381,7 +385,7 @@ class _Event(_Verbose):
# Active thread administration
_active_limbo_lock = _allocate_lock()
_active = {} # maps thread id to Thread object
_active: dict[int, Thread] = {} # maps thread id to Thread object
_limbo = {}
@ -400,8 +404,16 @@ class Thread(_Verbose):
# Protected by _active_limbo_lock.
__registered_atexit = False
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, verbose=None, daemon=None):
def __init__(
self,
group: None = None,
target: Callable[..., object] | None = None,
name: object = None,
args: Iterable[Any] = (),
kwargs: Mapping[str, Any] | None = None,
verbose: bool | None = None,
daemon: bool | None = None,
) -> None:
assert group is None, "group argument must be None for now"
_Verbose.__init__(self, verbose)
if kwargs is None:
@ -422,7 +434,7 @@ class Thread(_Verbose):
# sys.exc_info since it can be changed between instances
self.__stderr = _sys.stderr
def _set_daemon(self):
def _set_daemon(self) -> bool:
# Overridden in _MainThread and _DummyThread
return currentThread().isDaemon()
@ -437,7 +449,7 @@ class Thread(_Verbose):
status = status + " daemon"
return "<%s(%s, %s)>" % (self.__class__.__name__, self.__name, status)
def start(self):
def start(self) -> None:
assert self.__initialized, "Thread.__init__() not called"
assert not self.__started, "thread already started"
if __debug__:
@ -457,11 +469,11 @@ class Thread(_Verbose):
self.__started = True
_sleep(0.000001) # 1 usec, to let the thread run (Solaris hack)
def run(self):
def run(self) -> None:
if self.__target:
self.__target(*self.__args, **self.__kwargs)
def __bootstrap(self):
def __bootstrap(self) -> None:
try:
self.__started = True
_active_limbo_lock.acquire()
@ -497,7 +509,7 @@ class Thread(_Verbose):
# Do the best job possible w/o a huge amt. of code to
# approximate a traceback (code ideas from
# Lib/traceback.py)
exc_type, exc_value, exc_tb = self.__exc_info()
exc_type, exc_value, exc_tb = self.__exc_info() # type: ignore[misc]
try:
self.__stderr.write("Exception in thread " + self.getName() +
" (most likely raised during interpreter shutdown):\n")
@ -523,13 +535,13 @@ class Thread(_Verbose):
except:
pass
def __stop(self):
def __stop(self) -> None:
self.__block.acquire()
self.__stopped = True
self.__block.notifyAll()
self.__block.release()
def __delete(self):
def __delete(self) -> None:
"Remove current thread from the dict of currently running threads."
# Notes about running with dummy_thread:
@ -563,7 +575,7 @@ class Thread(_Verbose):
finally:
_active_limbo_lock.release()
def join(self, timeout=None):
def join(self, timeout: float | None = None) -> None:
assert self.__initialized, "Thread.__init__() not called"
assert self.__started, "cannot join thread before it is started"
assert self is not currentThread(), "cannot join current thread"
@ -592,11 +604,11 @@ class Thread(_Verbose):
finally:
self.__block.release()
def getName(self):
def getName(self) -> str:
assert self.__initialized, "Thread.__init__() not called"
return self.__name
def setName(self, name):
def setName(self, name: object) -> None:
assert self.__initialized, "Thread.__init__() not called"
self.__name = str(name)
@ -606,7 +618,7 @@ class Thread(_Verbose):
isAlive = is_alive
def isDaemon(self):
def isDaemon(self) -> bool:
assert self.__initialized, "Thread.__init__() not called"
return self.__daemonic
@ -688,13 +700,13 @@ class _MainThread(Thread):
class _DummyThread(Thread):
def __init__(self):
def __init__(self) -> None:
Thread.__init__(self, name=_newname("Dummy-%d"), daemon=True)
# Thread.__block consumes an OS-level locking primitive, which
# can never be used by a _DummyThread. Since a _DummyThread
# instance is immortal, that's bad, so release this resource.
del self._Thread__block
del self._Thread__block # type: ignore[attr-defined]
self._Thread__started = True
_active_limbo_lock.acquire()
@ -710,7 +722,7 @@ class _DummyThread(Thread):
# Global API functions
def current_thread():
def current_thread() -> Thread:
try:
return _active[get_ident()]
except KeyError: