thds.core 1.45.20250818180904__py3-none-any.whl → 1.45.20250821162245__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of thds.core might be problematic. Click here for more details.
- thds/core/thread_debug.py +114 -0
- {thds_core-1.45.20250818180904.dist-info → thds_core-1.45.20250821162245.dist-info}/METADATA +1 -1
- {thds_core-1.45.20250818180904.dist-info → thds_core-1.45.20250821162245.dist-info}/RECORD +6 -5
- {thds_core-1.45.20250818180904.dist-info → thds_core-1.45.20250821162245.dist-info}/WHEEL +0 -0
- {thds_core-1.45.20250818180904.dist-info → thds_core-1.45.20250821162245.dist-info}/entry_points.txt +0 -0
- {thds_core-1.45.20250818180904.dist-info → thds_core-1.45.20250821162245.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
import types
|
|
6
|
+
import typing as ty
|
|
7
|
+
|
|
8
|
+
from thds.core import log
|
|
9
|
+
|
|
10
|
+
logger = log.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _format_frame(frame: types.FrameType) -> str:
|
|
14
|
+
"""Format a stack frame as a single readable line."""
|
|
15
|
+
return f"{frame.f_code.co_filename}:{frame.f_lineno} in {frame.f_code.co_name}"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_thread_stack_frames(thread: threading.Thread) -> list[str]:
|
|
19
|
+
"""Get stack frames for a specific thread."""
|
|
20
|
+
frames = list[str]()
|
|
21
|
+
|
|
22
|
+
if thread.ident is None:
|
|
23
|
+
logger.warning('Thread "%s" has no identifier; cannot get stack frames.', thread.name)
|
|
24
|
+
return frames
|
|
25
|
+
|
|
26
|
+
# Get the frame for this thread
|
|
27
|
+
current_frames = sys._current_frames()
|
|
28
|
+
frame = current_frames.get(thread.ident)
|
|
29
|
+
|
|
30
|
+
if frame is None:
|
|
31
|
+
logger.warning("unable to get stack frames for thread %s (id: %s)")
|
|
32
|
+
return frames
|
|
33
|
+
|
|
34
|
+
# Get the path of this debug module
|
|
35
|
+
debug_module_path = os.path.abspath(__file__)
|
|
36
|
+
|
|
37
|
+
# Walk the stack
|
|
38
|
+
while frame:
|
|
39
|
+
if not (debug_module_path and os.path.abspath(frame.f_code.co_filename) == debug_module_path):
|
|
40
|
+
|
|
41
|
+
frames.append(_format_frame(frame))
|
|
42
|
+
frame = frame.f_back
|
|
43
|
+
return frames
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _get_thread_info(thread: threading.Thread) -> dict[str, ty.Any]:
|
|
47
|
+
"""Extract thread information including stack trace."""
|
|
48
|
+
stack_frames = _get_thread_stack_frames(thread)
|
|
49
|
+
return {
|
|
50
|
+
"thread_id": thread.ident,
|
|
51
|
+
"thread_name": thread.name,
|
|
52
|
+
"stack_frames": stack_frames,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _is_thread_pool_thread(thread: threading.Thread) -> bool:
|
|
57
|
+
"""Check if thread appears to belong to a ThreadPoolExecutor."""
|
|
58
|
+
tpe_patterns = [
|
|
59
|
+
r"ThreadPoolExecutor-\d+_\d+", # Default naming
|
|
60
|
+
r".*-\d+_\d+", # Custom prefix with TPE suffix pattern
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
for pattern in tpe_patterns:
|
|
64
|
+
if re.match(pattern, thread.name):
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _find_potential_parent_threads() -> list[dict[str, ty.Any]]:
|
|
71
|
+
"""Find threads that could be parents of the current thread pool thread."""
|
|
72
|
+
potential_parents = []
|
|
73
|
+
current_thread = threading.current_thread()
|
|
74
|
+
|
|
75
|
+
for thread in threading.enumerate():
|
|
76
|
+
# Skip self
|
|
77
|
+
if thread == current_thread:
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
# Skip daemon threads
|
|
81
|
+
if thread.daemon:
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
# Skip other thread pool threads
|
|
85
|
+
if _is_thread_pool_thread(thread):
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
thread_info = _get_thread_info(thread)
|
|
89
|
+
potential_parents.append(thread_info)
|
|
90
|
+
|
|
91
|
+
return potential_parents
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def capture_thread_context() -> dict[str, ty.Any]:
|
|
95
|
+
"""
|
|
96
|
+
Capture stack trace for current thread and potential parent threads.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Dict with 'current_thread' info and 'potential_parents' list.
|
|
100
|
+
"""
|
|
101
|
+
current_thread = threading.current_thread()
|
|
102
|
+
|
|
103
|
+
# Always capture current thread info
|
|
104
|
+
current_info = _get_thread_info(current_thread)
|
|
105
|
+
|
|
106
|
+
# Only look for parents if we think we're in a thread pool
|
|
107
|
+
potential_parents = []
|
|
108
|
+
if _is_thread_pool_thread(current_thread):
|
|
109
|
+
potential_parents = _find_potential_parent_threads()
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
"current_thread": current_info,
|
|
113
|
+
"potential_parents": potential_parents,
|
|
114
|
+
}
|
|
@@ -41,6 +41,7 @@ thds/core/scaling.py,sha256=f7CtdgK0sN6nroTq5hLAkG8xwbWhbCZUULstSKjoxO0,1615
|
|
|
41
41
|
thds/core/scope.py,sha256=9RWWCFRqsgjTyH6rzRm_WnO69N_sEBRaykarc2PAnBY,10834
|
|
42
42
|
thds/core/source_serde.py,sha256=X4c7LiT3VidejqtTel9YB6dWGB3x-ct39KF9E50Nbx4,139
|
|
43
43
|
thds/core/stack_context.py,sha256=17lPOuYWclUpZ-VXRkPgI4WbiMzq7_ZY6Kj1QK_1oNo,1332
|
|
44
|
+
thds/core/thread_debug.py,sha256=ox0XmZQxkwoeIVyM6-BV4PNic5VbucJF3GhjkmaP5UQ,3240
|
|
44
45
|
thds/core/thunks.py,sha256=p1OvMBJ4VGMsD8BVA7zwPeAp0L3y_nxVozBF2E78t3M,1053
|
|
45
46
|
thds/core/timer.py,sha256=aOpNP-wHKaKs6ONK5fOtIOgx00FChVZquG4PeaEYH_k,5376
|
|
46
47
|
thds/core/tmp.py,sha256=jA8FwDbXo3hx8o4kRjAlkwpcI77X86GY4Sktkps29ho,3166
|
|
@@ -73,8 +74,8 @@ thds/core/sqlite/structured.py,sha256=SvZ67KcVcVdmpR52JSd52vMTW2ALUXmlHEeD-VrzWV
|
|
|
73
74
|
thds/core/sqlite/types.py,sha256=oUkfoKRYNGDPZRk29s09rc9ha3SCk2SKr_K6WKebBFs,1308
|
|
74
75
|
thds/core/sqlite/upsert.py,sha256=BmKK6fsGVedt43iY-Lp7dnAu8aJ1e9CYlPVEQR2pMj4,5827
|
|
75
76
|
thds/core/sqlite/write.py,sha256=z0219vDkQDCnsV0WLvsj94keItr7H4j7Y_evbcoBrWU,3458
|
|
76
|
-
thds_core-1.45.
|
|
77
|
-
thds_core-1.45.
|
|
78
|
-
thds_core-1.45.
|
|
79
|
-
thds_core-1.45.
|
|
80
|
-
thds_core-1.45.
|
|
77
|
+
thds_core-1.45.20250821162245.dist-info/METADATA,sha256=VsnlsTYqIWz8IhH0Ol1fV6SNMna2E-u7chdnqJ9WOeI,2216
|
|
78
|
+
thds_core-1.45.20250821162245.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
79
|
+
thds_core-1.45.20250821162245.dist-info/entry_points.txt,sha256=bOCOVhKZv7azF3FvaWX6uxE6yrjK6FcjqhtxXvLiFY8,161
|
|
80
|
+
thds_core-1.45.20250821162245.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
|
|
81
|
+
thds_core-1.45.20250821162245.dist-info/RECORD,,
|
|
File without changes
|
{thds_core-1.45.20250818180904.dist-info → thds_core-1.45.20250821162245.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{thds_core-1.45.20250818180904.dist-info → thds_core-1.45.20250821162245.dist-info}/top_level.txt
RENAMED
|
File without changes
|