From 9236431d9a82f9d7d5d2d8090b797de08c493faf Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Sun, 6 Aug 2023 14:52:30 +1000 Subject: [PATCH 1/6] Switch to |-optional --- trio/_core/_thread_cache.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/trio/_core/_thread_cache.py b/trio/_core/_thread_cache.py index cc272fc92c..7e936d809b 100644 --- a/trio/_core/_thread_cache.py +++ b/trio/_core/_thread_cache.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import ctypes import ctypes.util import sys @@ -17,18 +19,16 @@ def _to_os_thread_name(name: str) -> bytes: # used to construct the method used to set os thread name, or None, depending on platform. # called once on import -def get_os_thread_name_func() -> Optional[Callable[[Optional[int], str], None]]: - def namefunc(setname: Callable[[int, bytes], int], ident: Optional[int], name: str): +def get_os_thread_name_func() -> Callable[[int | None, str], None] | None: + def namefunc(setname: Callable[[int, bytes], int], ident: int | None, name: str) -> None: # Thread.ident is None "if it has not been started". Unclear if that can happen # with current usage. if ident is not None: # pragma: no cover setname(ident, _to_os_thread_name(name)) - # namefunc on mac also takes an ident, even if pthread_setname_np doesn't/can't use it + # namefunc on Mac also takes an ident, even if pthread_setname_np doesn't/can't use it # so the caller don't need to care about platform. - def darwin_namefunc( - setname: Callable[[bytes], int], ident: Optional[int], name: str - ): + def darwin_namefunc(setname: Callable[[bytes], int], ident: int | None, name: str) -> None: # I don't know if Mac can rename threads that hasn't been started, but default # to no to be on the safe side. if ident is not None: # pragma: no cover From 0c646e88a78498771ece296ae8d989b0f87c75ec Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Sun, 6 Aug 2023 14:54:29 +1000 Subject: [PATCH 2/6] Type _core._thread_cache --- pyproject.toml | 1 + trio/_core/_thread_cache.py | 46 ++++++++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 90bc98f64f..097d43dade 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ module = [ "trio._core._entry_queue", "trio._core._local", "trio._core._unbounded_queue", + "trio._core._thread_cache", "trio._deprecate", "trio._dtls", "trio._file_io", diff --git a/trio/_core/_thread_cache.py b/trio/_core/_thread_cache.py index 7e936d809b..85a7778e6a 100644 --- a/trio/_core/_thread_cache.py +++ b/trio/_core/_thread_cache.py @@ -7,10 +7,12 @@ from functools import partial from itertools import count from threading import Lock, Thread -from typing import Callable, Optional, Tuple +from typing import Any, Callable, Generic, TypeVar import outcome +T = TypeVar("T") + def _to_os_thread_name(name: str) -> bytes: # ctypes handles the trailing \00 @@ -20,7 +22,9 @@ def _to_os_thread_name(name: str) -> bytes: # used to construct the method used to set os thread name, or None, depending on platform. # called once on import def get_os_thread_name_func() -> Callable[[int | None, str], None] | None: - def namefunc(setname: Callable[[int, bytes], int], ident: int | None, name: str) -> None: + def namefunc( + setname: Callable[[int, bytes], int], ident: int | None, name: str + ) -> None: # Thread.ident is None "if it has not been started". Unclear if that can happen # with current usage. if ident is not None: # pragma: no cover @@ -28,7 +32,9 @@ def namefunc(setname: Callable[[int, bytes], int], ident: int | None, name: str) # namefunc on Mac also takes an ident, even if pthread_setname_np doesn't/can't use it # so the caller don't need to care about platform. - def darwin_namefunc(setname: Callable[[bytes], int], ident: int | None, name: str) -> None: + def darwin_namefunc( + setname: Callable[[bytes], int], ident: int | None, name: str + ) -> None: # I don't know if Mac can rename threads that hasn't been started, but default # to no to be on the safe side. if ident is not None: # pragma: no cover @@ -110,9 +116,13 @@ def darwin_namefunc(setname: Callable[[bytes], int], ident: int | None, name: st name_counter = count() -class WorkerThread: - def __init__(self, thread_cache): - self._job: Optional[Tuple[Callable, Callable, str]] = None +class WorkerThread(Generic[T]): + def __init__(self, thread_cache: ThreadCache) -> None: + self._job: tuple[ + Callable[[], T], + Callable[[outcome.Outcome[T]], object], + str | None, + ] | None = None self._thread_cache = thread_cache # This Lock is used in an unconventional way. # @@ -130,7 +140,7 @@ def __init__(self, thread_cache): set_os_thread_name(self._thread.ident, self._default_name) self._thread.start() - def _handle_job(self): + def _handle_job(self) -> None: # Handle job in a separate method to ensure user-created # objects are cleaned up in a consistent manner. assert self._job is not None @@ -161,7 +171,7 @@ def _handle_job(self): print("Exception while delivering result of thread", file=sys.stderr) traceback.print_exception(type(e), e, e.__traceback__) - def _work(self): + def _work(self) -> None: while True: if self._worker_lock.acquire(timeout=IDLE_TIMEOUT): # We got a job @@ -185,10 +195,16 @@ def _work(self): class ThreadCache: - def __init__(self): - self._idle_workers = {} - - def start_thread_soon(self, fn, deliver, name: Optional[str] = None): + def __init__(self) -> None: + self._idle_workers: dict[WorkerThread[Any], None] = {} + + def start_thread_soon( + self, + fn: Callable[[], T], + deliver: Callable[[outcome.Outcome[T]], object], + name: str | None = None, + ) -> None: + worker: WorkerThread[T] try: worker, _ = self._idle_workers.popitem() except KeyError: @@ -200,7 +216,11 @@ def start_thread_soon(self, fn, deliver, name: Optional[str] = None): THREAD_CACHE = ThreadCache() -def start_thread_soon(fn, deliver, name: Optional[str] = None): +def start_thread_soon( + fn: Callable[[], T], + deliver: Callable[[outcome.Outcome[T]], object], + name: str | None = None, +) -> None: """Runs ``deliver(outcome.capture(fn))`` in a worker thread. Generally ``fn`` does some blocking work, and ``deliver`` delivers the From 9933a83ddf4c5982f2e9155b96d5683fd90c112f Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Sun, 6 Aug 2023 15:04:44 +1000 Subject: [PATCH 3/6] Update type completeness file --- trio/_tests/verify_types.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/trio/_tests/verify_types.json b/trio/_tests/verify_types.json index ac2cfbd197..5befa79513 100644 --- a/trio/_tests/verify_types.json +++ b/trio/_tests/verify_types.json @@ -7,11 +7,11 @@ "warningCount": 0 }, "typeCompleteness": { - "completenessScore": 0.9154704944178629, + "completenessScore": 0.9170653907496013, "exportedSymbolCounts": { "withAmbiguousType": 0, - "withKnownType": 574, - "withUnknownType": 53 + "withKnownType": 575, + "withUnknownType": 52 }, "ignoreUnknownTypesFromImports": true, "missingClassDocStringCount": 1, @@ -102,7 +102,6 @@ "trio.lowlevel.reschedule", "trio.lowlevel.spawn_system_task", "trio.lowlevel.start_guest_run", - "trio.lowlevel.start_thread_soon", "trio.lowlevel.temporarily_detach_coroutine_object", "trio.lowlevel.wait_readable", "trio.lowlevel.wait_writable", From b719d8b521978e37154caa58ef1020deb9013b32 Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Tue, 8 Aug 2023 14:14:38 +1000 Subject: [PATCH 4/6] Temporarily ignore Any from missing outcome stubs --- trio/_core/_thread_cache.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/trio/_core/_thread_cache.py b/trio/_core/_thread_cache.py index 85a7778e6a..aabd6698f9 100644 --- a/trio/_core/_thread_cache.py +++ b/trio/_core/_thread_cache.py @@ -118,7 +118,7 @@ def darwin_namefunc( class WorkerThread(Generic[T]): def __init__(self, thread_cache: ThreadCache) -> None: - self._job: tuple[ + self._job: tuple[ # type: ignore[no-any-unimported] Callable[[], T], Callable[[outcome.Outcome[T]], object], str | None, @@ -198,7 +198,7 @@ class ThreadCache: def __init__(self) -> None: self._idle_workers: dict[WorkerThread[Any], None] = {} - def start_thread_soon( + def start_thread_soon( # type: ignore[no-any-unimported] self, fn: Callable[[], T], deliver: Callable[[outcome.Outcome[T]], object], @@ -216,7 +216,7 @@ def start_thread_soon( THREAD_CACHE = ThreadCache() -def start_thread_soon( +def start_thread_soon( # type: ignore[no-any-unimported] fn: Callable[[], T], deliver: Callable[[outcome.Outcome[T]], object], name: str | None = None, From 69f26825b193ae3afa1e0e962bb905d1f6a04719 Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Tue, 8 Aug 2023 14:16:48 +1000 Subject: [PATCH 5/6] Change typevar name --- trio/_core/_thread_cache.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/trio/_core/_thread_cache.py b/trio/_core/_thread_cache.py index aabd6698f9..d8f5a94c5b 100644 --- a/trio/_core/_thread_cache.py +++ b/trio/_core/_thread_cache.py @@ -11,7 +11,7 @@ import outcome -T = TypeVar("T") +RetT = TypeVar("RetT") def _to_os_thread_name(name: str) -> bytes: @@ -116,11 +116,11 @@ def darwin_namefunc( name_counter = count() -class WorkerThread(Generic[T]): +class WorkerThread(Generic[RetT]): def __init__(self, thread_cache: ThreadCache) -> None: self._job: tuple[ # type: ignore[no-any-unimported] - Callable[[], T], - Callable[[outcome.Outcome[T]], object], + Callable[[], RetT], + Callable[[outcome.Outcome[RetT]], object], str | None, ] | None = None self._thread_cache = thread_cache @@ -200,11 +200,11 @@ def __init__(self) -> None: def start_thread_soon( # type: ignore[no-any-unimported] self, - fn: Callable[[], T], - deliver: Callable[[outcome.Outcome[T]], object], + fn: Callable[[], RetT], + deliver: Callable[[outcome.Outcome[RetT]], object], name: str | None = None, ) -> None: - worker: WorkerThread[T] + worker: WorkerThread[RetT] try: worker, _ = self._idle_workers.popitem() except KeyError: @@ -217,8 +217,8 @@ def start_thread_soon( # type: ignore[no-any-unimported] def start_thread_soon( # type: ignore[no-any-unimported] - fn: Callable[[], T], - deliver: Callable[[outcome.Outcome[T]], object], + fn: Callable[[], RetT], + deliver: Callable[[outcome.Outcome[RetT]], object], name: str | None = None, ) -> None: """Runs ``deliver(outcome.capture(fn))`` in a worker thread. From 055728c9742fafc994c58e39309a20d37355d2ee Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Tue, 8 Aug 2023 16:36:25 +1000 Subject: [PATCH 6/6] Make docs build succeed --- docs/source/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index a5abeb0dca..39eda12ad5 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -67,6 +67,7 @@ ("py:class", "types.FrameType"), ("py:class", "P.args"), ("py:class", "P.kwargs"), + ("py:class", "RetT"), # TODO: figure out if you can link this to SSL ("py:class", "Context"), # TODO: temporary type