Skip to content

Commit a5247c3

Browse files
committed
Continue previous threading issue
1 parent 7dc1ec5 commit a5247c3

File tree

1 file changed

+78
-74
lines changed

1 file changed

+78
-74
lines changed

python_issue.py

Lines changed: 78 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,68 @@
1+
import resource
12
import sys
23
import os
34
import threading
45
import time
56
import traceback
67
import gc
78

9+
10+
11+
# To Compile Python
12+
"""
13+
mkdir -p /home/odoo/Documents/Pythons/py12 &&
14+
./configure --prefix=/home/odoo/Documents/Pythons/py12 --enable-optimizations &&
15+
make --quiet clean &&
16+
make --quiet -j 10 &&
17+
make --quiet altinstall
18+
"""
19+
20+
"""
21+
./configure --enable-optimizations &&
22+
make --quiet clean &&
23+
make --quiet -j 10
24+
"""
25+
26+
27+
828
"""
929
Current explaination:
1030
11-
When one `handler` Thread is hard kill between after `_start_new_thread` and before `_started.set()` (inside `_bootstrap_inner`):
12-
Two issues happens
13-
- The first issue, is that `threading.enumerate` returns it, but it shouldn't since it is not alive:
14-
That's because `_limbo` is not clean if hard kill (finally clause of `_bootstrap_inner` is bypass). Weird but not blocking
31+
When one `handler` Thread is killed (not enough memory) between after starting the new thread (thread_run C code)
32+
and before `_started.set()` Python (inside `_bootstrap_inner` or just before in the C threaded code):
33+
34+
Leading into deadlock situation the serving Thread wait the `_started` to be set, but
35+
it will never be since the specific Thread is dead... Also if this dead Thread remains in
36+
the _limbo dictionary (small inconsistency since it will be return in case of threading.enumarate()).
37+
38+
When the thread is killed inside before `_started.set()`, we always get this weird error message:
39+
"Exception ignored in thread started by: <A method>"
40+
This comes from the `thread_run` (C code) calling `_PyErr_WriteUnraisableMsg`
41+
At first glance, it seems that the ignored Exception is a MemoryError that skip part of the `_bootstrap_inner` method
42+
43+
What we tried:
44+
- Increasing the stack_size to force Python to only accept create new Thread when more
45+
memory is available on the stack. That's doesn't seems to work even with a hug number...
46+
Does this method even work ?? Signature is odd and the doc is incomplete ??
47+
48+
- Catch the MemoryError, to sleep + gc after in order to force free memory => doesn't work better
49+
50+
- Biscept on stable version, but is is reproductible:
51+
- Python 3.8.0b1: Reproductible but seems more rare?
52+
- Our version 3.12.3: Reproductible
53+
- Python 3.12.11+: Reproductible
54+
- Python 3.13.7+: Reproductible
55+
- Python 3.14.0rc2+: Reproductible
56+
- Python main/3.15.0a0: Reproductible
1557
16-
- The Thread of `serving` is stuck waiting that self._started of the killed Thread becomes free, but it will never be ...
58+
- Find a magic solution to bypass `self._started.wait()`. The only 'working' solution is to add
59+
a timeout (1 sec) on it that recheck the status of the Thread. But is fragile, because a timeout
60+
is always a trade-off (after 1 sec is long and still not ensure that the Thread has been effectively
61+
killed).
1762
63+
64+
Deadlock traceback
65+
------------------
1866
File "/home/odoo/Documents/Pythons/py12_11/lib/python3.12/threading.py", line 1033, in _bootstrap
1967
self._bootstrap_inner()
2068
File "/home/odoo/Documents/Pythons/py12_11/lib/python3.12/threading.py", line 1078, in _bootstrap_inner
@@ -30,103 +78,59 @@
3078
File "/home/odoo/Documents/Pythons/py12_11/lib/python3.12/threading.py", line 355, in wait
3179
waiter.acquire()
3280
33-
Because the Thread is hard kill by the OS, should it release his lock and then setting _started ???
34-
81+
TODO:
82+
- Check inside the ignored Exception with a debugger C
83+
- Check works stack_size
84+
-
3585
3686
"""
87+
threading.stack_size(33000)
3788

89+
print("PID: ", os.getpid(), " Stack Size ", threading.stack_size())
3890

91+
# print("Sleep 10 sec")
92+
# time.sleep(10)
3993

40-
41-
42-
43-
print("PID: ", os.getpid())
44-
print("Sleep 10 sec")
45-
time.sleep(1)
46-
47-
48-
49-
#
50-
51-
52-
94+
resource.setrlimit(resource.RLIMIT_AS, (1_000_000_000, 1_500_000_000))
5395

5496
def memory_error():
5597
memory_issue_list = []
5698
while True:
57-
memory_issue_list.append("a" * 10_000)
99+
memory_issue_list.append("a" * 150_000)
58100

59101
def handler():
102+
time.sleep(0.001)
60103
try:
61104
memory_error()
62105
except MemoryError:
63-
print("MemoryError - stop")
106+
print(f"MemoryError in {threading.current_thread()}")
64107
# gc.collect()
65-
108+
# time.sleep(0.1)
66109

67110
def serving():
68111
for _ in range(100):
69-
t = threading.Thread(target=handler)
70-
print('Start Sub-Thread')
71-
t.start()
112+
try:
113+
t = threading.Thread(target=handler)
114+
print(f'Start Thread: {t}')
115+
t.start()
116+
except RuntimeError:
117+
print('RuntimeError', {t})
72118

73119
print('Start Serving')
74120
serving_thread = threading.Thread(target=serving, daemon=True)
75121
serving_thread.start()
76122

123+
124+
time.sleep(10)
77125
while serving_thread.is_alive():
78-
time.sleep(2)
79126
for alive_thread in threading.enumerate():
80127
print(alive_thread, alive_thread.is_alive(), alive_thread.ident, alive_thread.native_id)
81128
if not alive_thread.is_alive() and serving_thread.is_alive():
82129
print(f"Serving thread info: {serving_thread._started=} | {serving_thread._started._flag=}")
83130
print(f"Alive but dead thread: {alive_thread._started=} | {alive_thread._started._flag=}")
84131

85-
frames_serving_thread = sys._current_frames()[serving_thread.ident]
86-
traceback.print_stack(frames_serving_thread)
87-
88-
# File "/home/odoo/Documents/Pythons/py14/lib/python3.14/threading.py", line 1043, in _bootstrap
89-
# self._bootstrap_inner()
90-
# File "/home/odoo/Documents/Pythons/py14/lib/python3.14/threading.py", line 1081, in _bootstrap_inner
91-
# self._context.run(self.run)
92-
# File "/home/odoo/Documents/Pythons/py14/lib/python3.14/threading.py", line 1023, in run
93-
# self._target(*self._args, **self._kwargs)
94-
# File "/home/odoo/Documents/dev/./odoo_scripts/python_issue.py", line 17, in serving
95-
# t.start()
96-
# File "/home/odoo/Documents/Pythons/py14/lib/python3.14/threading.py", line 1010, in start
97-
# self._started.wait() # Will set ident and native_id
98-
# File "/home/odoo/Documents/Pythons/py14/lib/python3.14/threading.py", line 669, in wait
99-
# signaled = self._cond.wait(timeout)
100-
# File "/home/odoo/Documents/Pythons/py14/lib/python3.14/threading.py", line 369, in wait
101-
# waiter.acquire()
102-
103-
104-
"""
105-
git checkout main &&
106-
mkdir -p /home/odoo/Documents/Pythons/pymain &&
107-
make --quiet clean &&
108-
./configure --prefix=/home/odoo/Documents/Pythons/pymain --enable-optimizations &&
109-
make --quiet -j 10 &&
110-
make --quiet altinstall
111-
"""
112-
113-
"""
114-
make --quiet clean &&
115-
./configure --enable-optimizations &&
116-
make --quiet -j 10
117-
"""
118-
119-
120-
121-
# ./configure --prefix=/home/odoo/Documents/Pythons/py14 --enable-optimizations
122-
123-
# Python 3.8.0b1: Reproductible but seems more rare?
124-
125-
# Our version 3.12.3: Reproductible
126-
127-
# Python 3.12.11+: Reproductible
128-
# Python 3.13.7+: Reproductible
129-
# Python 3.14.0rc2+: Reproductible
130-
131-
# Python main/3.15.0a0: Reproductible
132+
if serving_thread.ident in sys._current_frames():
133+
frames_serving_thread = sys._current_frames()[serving_thread.ident]
134+
traceback.print_stack(frames_serving_thread)
132135

136+
time.sleep(10)

0 commit comments

Comments
 (0)