Команда dump, отладка зависаний, анализ deadlock и long-running функций
Когда программа зависла, один снимок стека стоит тысячи догадок.
Команда dump делает мгновенный снимок стека вызовов всех потоков Python-процесса. Это ваш инструмент для отладки зависаний, deadlock и понимания «что программа делает прямо сейчас».
py-spy dump --pid <PID>Пример вывода:
Thread 0x7FF895C3A5C0 (active)
"MainThread"
Func: process_data, File: my_app.py, Line: 45
Func: fetch_from_api, File: api.py, Line: 23
Func: time.sleep, File: :builtin, Line: 0
Thread 0x7FF895C3B700 (idle)
"Worker-1"
Func: task_queue.get, File: queue.py, Line: 176
Func: worker_loop, File: worker.py, Line: 34
Для каждого потока вы видите:
threading.setName())Важно: Стек показывается «снизу вверх» — текущая функция внизу, вызвавшая её выше, и так далее до корня.
Пользователь ждёт, сервер не отвечает, процесс не потребляет CPU.
# Найдите PID
ps aux | grep my_app
# Сделайте снимок
py-spy dump --pid 12345Что искать:
time.sleep() — программа ждётthreading.Lock.acquire() — ожидание блокировкиsocket.recv() — ожидание сетиДва потока ждут друг друга.
Пример dump при deadlock:
Thread 0x7FF895C3A5C0 (active)
"Thread-1"
Func: lock_b.acquire, File: threading.py, Line: 234
Func: task_a, File: tasks.py, Line: 12
Thread 0x7FF895C3B700 (active)
"Thread-2"
Func: lock_a.acquire, File: threading.py, Line: 234
Func: task_b, File: tasks.py, Line: 28
Диагноз:
Программа работает, но иногда выдаёт ошибку. Сделайте dump в момент перед ошибкой:
# В одном терминале запустите программу
python my_app.py
# В другом — сделайте снимок
py-spy dump --pid 12345Flask-приложение перестало отвечать на запросы. CPU не потребляет.
lsof -i :5000
# или
ps aux | grep flaskpy-spy dump --pid 12345Thread 0x7FF895C3A5C0 (active)
"MainThread"
Func: recv, File: socket.py, Line: 598
Func: recv_into, File: socket.py, Line: 639
Func: read, File: ssl.py, Line: 1234
Func: request, File: requests.py, Line: 567
Func: fetch_external_api, File: app.py, Line: 89
Func: handle_request, File: app.py, Line: 34
Диагноз: Приложение ждёт ответ от внешнего API. Нет таймаута на сокете.
Добавьте таймаут:
# Было
response = requests.get(url)
# Стало
response = requests.get(url, timeout=5)Скрипт работает уже час, хотя должен завершиться за минуту.
ps aux | grep my_scriptpy-spy dump --pid 12345Thread 0x7FF895C3A5C0 (active)
"MainThread"
Func: process_items, File: my_script.py, Line: 45
Func: <module>, File: my_script.py, Line: 12
Функция process_items в строке 45 — это внутри цикла.
Возможные причины:
# Было — ошибка!
def process_items(items):
i = 0
while i < len(items):
process(items[i])
# Забыли i += 1!
# Стало
def process_items(items):
i = 0
while i < len(items):
process(items[i])
i += 1 # Добавили инкремент| Характеристика | dump | top | record |
|---|---|---|---|
| Режим | Мгновенный снимок | Реальное время | Запись в файл |
| Длительность | 0 секунд (мгновенно) | Непрерывно | 30 секунд по умолчанию |
| Лучше для | Отладка зависаний | Live-мониторинг | Поиск узких мест |
| Показывает | Точный стек сейчас | Агрегированные данные | Статистика за время |
Когда что использовать:
Для отслеживания изменений сделайте несколько снимков:
# Снимок каждые 2 секунды, 5 раз
for i in {1..5}; do
echo "=== Snapshot $i ==="
py-spy dump --pid 12345
sleep 2
donepy-spy dump --pid 12345 > stack_dump.txtУдобно для отправки коллегам или анализа позже.
dump показывает все потоки Python. Это особенно полезно для:
from concurrent.futures import ThreadPoolExecutor
import time
def task(n):
time.sleep(1)
return n * 2
with ThreadPoolExecutor(max_workers=4) as executor:
futures = [executor.submit(task, i) for i in range(10)]
time.sleep(10) # Долгоdump во время выполнения:
Thread 0x7FF895C3A5C0 (idle)
"MainThread"
Func: wait, File: concurrent/futures/_base.py, Line: 345
Thread 0x7FF895C3B700 (active)
"ThreadPoolExecutor-0_0"
Func: task, File: my_script.py, Line: 5
Thread 0x7FF895C3C800 (active)
"ThreadPoolExecutor-0_1"
Func: task, File: my_script.py, Line: 5
Видно: main ждёт завершения futures, рабочие потоки выполняют task.
Только Python: Не показывает C-стек (используйте --native для этого, но это уже не dump).
Один момент времени: Не показывает историю. Для истории используйте record.
Не работает с замороженными бинарниками: PyInstaller, cx_Freeze могут не иметь отладочной информации.
Func: acquire, File: threading.py, Line: 234
Func: process_shared_resource, File: app.py, Line: 67
Func: get, File: queue.py, Line: 176
Func: worker, File: worker.py, Line: 23
Func: recv, File: socket.py, Line: 598
Func: http_request, File: http.py, Line: 123
Func: wait, File: subprocess.py, Line: 1234
Func: run_command, File: utils.py, Line: 45
Thread 0x7FF895C3A5C0 (active)
"MainThread"
Func: process, File: my_script.py, Line: 34 # Одна и та же строка
Func: <module>, File: my_script.py, Line: 12
import threading
import time
lock_a = threading.Lock()
lock_b = threading.Lock()
def task1():
with lock_a:
time.sleep(0.1)
with lock_b:
print("Task 1 done")
def task2():
with lock_b:
time.sleep(0.1)
with lock_a:
print("Task 2 done")
t1 = threading.Thread(target=task1)
t2 = threading.Thread(target=task2)
t1.start()
t2.start()
t1.join()
t2.join()py-spy dump --pid <PID>Ключевая идея: dump — это «рентгеновский снимок» вашей программы. Один снимок может мгновенно показать причину зависания, deadlock или бесконечного цикла.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.