Skip to content

Commit c4af6dc

Browse files
authored
Merge pull request #407 from bozhodimitrov/main
Examples for episode 579 & 580
2 parents e1e5869 + 4560351 commit c4af6dc

File tree

9 files changed

+412
-0
lines changed

9 files changed

+412
-0
lines changed

sample_code/ep579/README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# [oops I'm the pyuwsgi maintainer now (intermediate)](https://youtu.be/WILYaDNez4g)
2+
3+
today I take you down a rabbit hole of deadlocks and worker reloads and python 3.12 sadness. unfortunately despite the "conclusion" in this video this doesn't actually fix the problem -- more on that in the future!
4+
5+
## Interactive examples
6+
7+
### Python
8+
9+
```python
10+
import pyuwsgi
11+
```
12+
13+
### Bash
14+
15+
Session 1:
16+
17+
```bash
18+
virtualenv venv -ppython3.11
19+
. venv/bin/activate
20+
pip install pyuwsgi
21+
22+
ls venv/lib/python3.11/site-packages/
23+
python
24+
25+
chmod +x t.sh
26+
bash t.sh
27+
kill -9 %1
28+
29+
pip freeze | grep pyuwsgi
30+
deactivate
31+
32+
virtualenv venv -ppython3.12
33+
. venv/bin/activate
34+
pip install pyuwsgi
35+
36+
bash t.sh
37+
```
38+
39+
Session 2:
40+
41+
```bash
42+
yes | head -100 | xargs -P4 --replace curl localhost:9001/health-check/; echo
43+
yes | head -1000 | xargs -P4 --replace curl localhost:9001/health-check/; echo
44+
yes | head -1000 | xargs -P40 --replace curl localhost:9001/health-check/; echo
45+
```

sample_code/ep579/rev01/t.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
UWSGI_AUTO_PROCNAME=true \
2+
UWSGI_BINARY_PATH=$PWD/venv/bin/python3 \
3+
UWSGI_BUFFER_SIZE=32768 \
4+
UWSGI_DIE_ON_TERM=true \
5+
UWSGI_DISABLE_LOGGING=false \
6+
UWSGI_DISABLE_WRITE_EXCEPTION=true \
7+
UWSGI_ENABLE_THREADS=true \
8+
UWSGI_HARAKIRI=600 \
9+
UWSGI_HONOUR_STDIN=true \
10+
UWSGI_HTTP_CHUNKED_INPUT=true \
11+
UWSGI_HTTP_KEEPALIVE=true \
12+
UWSGI_HTTP_SOCKET=127.0.0.1:9001 \
13+
UWSGI_HTTP_TIMEOUT=600 \
14+
UWSGI_IGNORE_SIGPIPE=true \
15+
UWSGI_IGNORE_WRITE_ERRORS=true \
16+
UWSGI_LAZY_APPS=true \
17+
UWSGI_LIMIT_POST=1073741824 \
18+
UWSGI_LOG_X_FORWARDED_FOR=false \
19+
UWSGI_MASTER=true \
20+
UWSGI_MAX_REQUESTS=20 \
21+
UWSGI_MEMORY_REPORT=true \
22+
UWSGI_MODULE=wsgi:application \
23+
UWSGI_NEED_APP=true \
24+
UWSGI_POST_BUFFERING=65536 \
25+
UWSGI_PROCNAME_PREFIX_SPACED=[Sentry] \
26+
UWSGI_PROTOCOL=http \
27+
UWSGI_RELOAD_ON_RSS=600 \
28+
UWSGI_SINGLE_INTERPRETER=true \
29+
UWSGI_THUNDER_LOCK=false \
30+
UWSGI_VACUUM=true \
31+
UWSGI_VIRTUALENV=$PWD/venv \
32+
UWSGI_WORKERS=2 \
33+
UWSGI_THREADS=2 \
34+
UWSGI_WSGI_FILE=wsgi.py \
35+
uwsgi

sample_code/ep579/rev01/wsgi.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
def application(environ, start_response):
2+
status = '200 OK'
3+
output = b'!'
4+
5+
response_headers = [
6+
('Content-Type', 'text/plain'),
7+
('Content-Length', str(len(output))),
8+
]
9+
start_response(status, response_headers)
10+
11+
return [output]

sample_code/ep579/rev02/t.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
UWSGI_AUTO_PROCNAME=true \
2+
UWSGI_BINARY_PATH=$PWD/venv/bin/python3 \
3+
UWSGI_BUFFER_SIZE=32768 \
4+
UWSGI_DIE_ON_TERM=true \
5+
UWSGI_DISABLE_LOGGING=false \
6+
UWSGI_DISABLE_WRITE_EXCEPTION=true \
7+
UWSGI_ENABLE_THREADS=true \
8+
UWSGI_HARAKIRI=600 \
9+
UWSGI_HONOUR_STDIN=true \
10+
UWSGI_HTTP_CHUNKED_INPUT=true \
11+
UWSGI_HTTP_KEEPALIVE=true \
12+
UWSGI_HTTP_SOCKET=127.0.0.1:9001 \
13+
UWSGI_HTTP_TIMEOUT=600 \
14+
UWSGI_IGNORE_SIGPIPE=true \
15+
UWSGI_IGNORE_WRITE_ERRORS=true \
16+
UWSGI_LAZY_APPS=true \
17+
UWSGI_LIMIT_POST=1073741824 \
18+
UWSGI_LOG_X_FORWARDED_FOR=false \
19+
UWSGI_MASTER=true \
20+
UWSGI_MAX_REQUESTS=20 \
21+
UWSGI_MEMORY_REPORT=true \
22+
UWSGI_MODULE=wsgi:application \
23+
UWSGI_NEED_APP=true \
24+
UWSGI_POST_BUFFERING=65536 \
25+
UWSGI_PROCNAME_PREFIX_SPACED=[Sentry] \
26+
UWSGI_PROTOCOL=http \
27+
UWSGI_RELOAD_ON_RSS=600 \
28+
UWSGI_SINGLE_INTERPRETER=true \
29+
UWSGI_THUNDER_LOCK=false \
30+
UWSGI_VACUUM=true \
31+
UWSGI_VIRTUALENV=$PWD/venv \
32+
UWSGI_WORKERS=2 \
33+
UWSGI_WSGI_FILE=wsgi.py \
34+
uwsgi

sample_code/ep580/README.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# [debugging and fixing pyuwsgi in python 3.12 (advanced)](https://youtu.be/Y4n2xCIF2Jg)
2+
3+
welcome to a whirlwind tour of bisecting, GIL, strace, gdb, forking, threadstates, undefined behaviour, and deadlocking. I go through the process debugging the most difficult bug I've encountered yet and ultimately how I fixed the problem and understood what changed and why it resulted in this issue!
4+
5+
## Interactive examples
6+
7+
Session 1:
8+
9+
### Bash
10+
11+
```bash
12+
virtualenv venv -ppython3.12
13+
. venv/bin/activate
14+
pip install pyuwsgi==2.0.26
15+
16+
chmod +x t.sh
17+
bash t.sh
18+
pkill -9 -f uwsgi
19+
fg
20+
21+
cd cpython
22+
git bisect start
23+
git bisect good v3.12.0a2
24+
git bisect bad v3.12.0rc1
25+
git bisect run ../venv-bisect/bin/python3 ../bisect-uwsgi.py
26+
27+
git clone git@github.com:lincolnloop/pyuwsgi-wheels
28+
cd pyuwsgi-wheels/uwsgi/
29+
30+
git grep IsInit
31+
nano plugins/python/python_plugin.c
32+
nano plugins/pyuwsgi/pyuwsgi.c
33+
34+
git grep PyEval_RestoreThread
35+
git grep -n PyEval_RestoreThread
36+
37+
git grep uwsgi_fork
38+
nano core/master_utils.c
39+
40+
./venv.uwsgi/bin/pip uninstall uwsgi
41+
./venv.uwsgi/bin/pip install ./uwsgi
42+
43+
chmod +x t.sh
44+
bash t.sh
45+
46+
git -C ../../uwsgi diff -- core/
47+
git -C ../../uwsgi diff -- core/ | git apply
48+
cd ../../
49+
50+
./venv/bin/pip uninstall pyuwsgi
51+
./venv/bin/pip install ./pyuwsgi-wheels/uwsgi/
52+
53+
nano pyuwsgi-wheels/uwsgi/plugins/pyuwsgi/pyuwsgi.c
54+
```
55+
56+
Session 2:
57+
58+
```bash
59+
yes | head -60 | xargs --replace curl localhost:9001/health-check/; echo
60+
```
61+
62+
Session 3:
63+
64+
```bash
65+
which strace
66+
strace --help
67+
68+
ps -ef | grep uwsgi
69+
sudo strace -p <pid>
70+
sudo gdb -p <pid>
71+
72+
# where
73+
# bt
74+
# q
75+
76+
ps -ef | grep uwsgi
77+
sudo gdb -p <pid>
78+
79+
# b plugins/python/gil.c:12
80+
# c
81+
# p _Py_tss_tstate
82+
# c
83+
# q
84+
85+
sudo gdb -p <pid>
86+
87+
# b plugins/python/gil.c:12
88+
# c
89+
# p_Py_tss_tstate->interp->ceval->gil->locked
90+
# p _Py_tss_tstate
91+
# q
92+
93+
sudo gdb -p <pid>
94+
95+
# b plugins/python/gil.c:12
96+
# c
97+
# p _PyRuntime.ceval.gil->locked
98+
# p _Py_tss_tstate
99+
# q
100+
```
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
from __future__ import annotations
2+
import contextlib
3+
import os.path
4+
import shlex
5+
import subprocess
6+
import urllib.request
7+
import sys
8+
import time
9+
import psutil
10+
11+
from typing import Generator, IO
12+
13+
14+
rev = subprocess.check_output(('git', 'rev-parse', 'HEAD'), text=True).strip()
15+
16+
prefix = os.path.abspath('../prefix')
17+
venv = os.path.abspath('../venv')
18+
logs = os.path.abspath('../logs')
19+
uwsgi_src = os.path.abspath('../pyuwsgi-wheels/uwsgi')
20+
os.makedirs(logs, exist_ok=True)
21+
22+
23+
def tee(s: str, *files: IO[str]) -> None:
24+
for f in files:
25+
print(s, file=f, flush=True)
26+
27+
28+
def _cmd_q(
29+
*cmd: str,
30+
env: dict[str, str] | None = None,
31+
cwd: str | None = None,
32+
exit: int = 125,
33+
) -> None:
34+
with open(os.path.join(logs, rev), 'a+') as logfile:
35+
tee(f'+ {shlex.join(cmd)}', logfile, sys.stderr)
36+
ret = subprocess.call(
37+
cmd,
38+
env=env,
39+
cwd=cwd,
40+
stdout=logfile,
41+
stderr=logfile,
42+
)
43+
if ret:
44+
tee(f'=> failed with {ret}', logfile, sys.stderr)
45+
raise SystemExit(exit)
46+
47+
48+
def _build_cpython() -> None:
49+
print('!!! building cpython')
50+
_cmd_q('git', 'clean', '-fxfd')
51+
_cmd_q('./configure', '--prefix', prefix)
52+
_cmd_q('make', '-j5')
53+
_cmd_q('make', 'install')
54+
55+
56+
def _make_venv() -> None:
57+
print('!!! making venv')
58+
_cmd_q('rm', '-rf', venv)
59+
_cmd_q(os.path.join(prefix, 'bin', 'python3'), '-mvenv', venv)
60+
61+
62+
def _install_uwsgi() -> None:
63+
print('!!! installing uwsgi')
64+
_cmd_q(os.path.join(venv, 'bin', 'pip'), 'install', uwsgi_src)
65+
66+
67+
def _req() -> None:
68+
urllib.request.urlopen('http://127.0.0.1:9001/', timeout=1).read()
69+
70+
71+
def _wait_for_ready() -> None:
72+
for _ in range(10):
73+
try:
74+
_req()
75+
except OSError:
76+
print('...waiting for start', file=sys.stderr)
77+
time.sleep(.25)
78+
else:
79+
return
80+
81+
82+
def _child_pids(pid: int) -> tuple[int, ...]:
83+
return tuple(child.pid for child in psutil.Process(pid).children())
84+
85+
86+
@contextlib.contextmanager
87+
def uwsgi_proc() -> Generator[int]:
88+
uwsgi = os.path.join(venv, 'bin', 'uwsgi')
89+
_cmd_q(uwsgi, '--help')
90+
env = {
91+
**os.environ,
92+
'UWSGI_MASTER': 'true',
93+
'UWSGI_BINARY_PATH': os.path.join(venv, 'bin', 'python3'),
94+
'UWSGI_VIRTUALENV': venv,
95+
'UWSGI_WORKERS': '2',
96+
'UWSGI_ENABLE_THREADS': 'true',
97+
'UWSGI_MAX_REQUESTS': '2',
98+
'UWSGI_SINGLE_INTERPRETER': 'true',
99+
'UWSGI_HTTP_SOCKET': 'localhost:9001',
100+
'UWSGI_WSGI_FILE': 'wsgi.py',
101+
'UWSGI_DISABLE_LOGGING': '1',
102+
}
103+
with open(os.path.join(logs, rev), 'a+') as logfile:
104+
tee('+ uwsgi', logfile, sys.stderr)
105+
proc = subprocess.Popen(
106+
(uwsgi, ),
107+
env=env,
108+
cwd='..',
109+
stdout=logfile,
110+
stderr=logfile,
111+
)
112+
_wait_for_ready()
113+
try:
114+
yield proc.pid
115+
finally:
116+
to_kill = [str(proc.pid)]
117+
for child_pid in _child_pids(proc.pid):
118+
to_kill.append(str(child_pid))
119+
subprocess.call(('kill', '-9', *to_kill))
120+
121+
122+
def _run_test() -> int:
123+
with uwsgi_proc() as pid:
124+
children = set(_child_pids(pid))
125+
print(f'started with pid: {pid} {children}!', file=sys.stderr)
126+
while True:
127+
time.sleep(.25)
128+
try:
129+
_req()
130+
except OSError:
131+
cand_children = set(_child_pids(pid))
132+
assert not children & cand_children, (children, cand_children)
133+
print('\ngot deadlock!!')
134+
return 1
135+
else:
136+
cand_children = set(_child_pids(pid))
137+
if cand_children & children:
138+
print('z', flush=True, end='')
139+
continue
140+
else:
141+
print('\nsuccessful request after all children recycled!')
142+
return 0
143+
144+
145+
def main() -> int:
146+
assert os.path.exists('Python'), 'not in cpython'
147+
_build_cpython()
148+
_make_venv()
149+
_install_uwsgi()
150+
return _run_test()
151+
152+
153+
if __name__ == '__main__':
154+
raise SystemExit(main())

sample_code/ep580/rev01/t.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
UWSGI_MASTER=true \
2+
UWSGI_BINARY_PATH=$PWD/venv/bin/python3 \
3+
UWSGI_VIRTUALENV=$PWD/venv \
4+
UWSGI_WORKERS=2 \
5+
UWSGI_ENABLE_THREADS=true \
6+
UWSGI_MAX_REQUESTS=5 \
7+
UWSGI_HTTP_SOCKET=localhost:9001 \
8+
UWSGI_WSGI_FILE=wsgi.py \
9+
UWSGI_DISABLE_LOGGING=1 \
10+
venv/bin/uwsgi

0 commit comments

Comments
 (0)