November 25th, 2015

404

thread_timeout

Я его писал-писал, писал-писал, а на выходе имею, что в реальной ситуации зависшей enclosure процесс отрабатывает таймаут (as planned), но не завершается и остаётся болтаться в неумершем состоянии (которое даже по ctrl-z с консольки не свернуть, not as planned).

Видимо, надо демонизировать тред (можно ли демонизировать тред в отрыве от приложения?). Но пост-фактум уже поздно (всё залипло), а до - теряется тонкая духовная связь с stdin/out.

Видимо, надо делать хук на выход и демонизировать программу перед выходом.

То есть не:
@thread_timeout(10)
def hung():
  hung_please()

hung()
sys.exit(),

а:

hung()
thread_timeout.bgexit()


Ща пойду писать код для bgexit (и проверять в боевых условиях, что эта идея работает)...
404

Отцепиться от терминала

Задачка становится всё веселее - я не могу отцепиться от терминала.

Суть проблемы:

Есть функция:
def hung():
path='/sys/devices/pci0000:80/0000:80:07.0/0000:85:00.0/host5/port-5:1/expander-5:1/port-5:1:21/end_device-5:1:21/target5:0:46/5:0:46:0/enclosure/5:0:46:0/Slot 09/fault'
file(path,'r').read()

Которая в реальной боевой испорченной системе вызывает статус TASK_ININTERRUPTBLE у процесса. После получения этого статуса процесс не реагирует ни на какие сигналы (не получает их). Ни SIGKILL, ни SIGHUP, ни SIGTSTP, ни SIGSTOP.

Сейчас сделано: декоратор, который запускает эту функцию в отдельном треде, ждёт завершения вызванного, или по таймауту рейзит эксепшен. Эксепшен может быть пойман, после этого можно делать что угодно, но завершить процесс невозможно.

Что нужно: отцепиться от терминала по этому эксепшену.

Вопрос: как? В обычном коде я могу сам себе послать TSTP и всё становится бело и пушисто. Но в случае выполнения hung() я теряю все сигналы. И, судя по всему, не только сигналы.

Вот пример самого хардкорного кода, который мне удалось написать (я пробовал _exit(), но он тоже не работает).

import signal
import thread_timeout
import os
import sys
import ctypes

@thread_timeout.thread_timeout(1)
def hung():
        path='/sys/devices/..../Slot 09/fault' # см выше
        file(path,'r').read()

try:
        hung()
except thread_timeout.FailedKillExecTimeout:
        print "failed"
        ctypes.CDLL("libc.so.6").daemon()


И вот этот вот код отлично работает, если read() заменить на sleep(100000). Процесс демонизируется, отцепляется от консоли. А при залипшем треде - не демонизируется.

Кстати, поборники "хорошотредовых языков программирования", если вы на чём-то типа go мне напишете эквивалент этого кода, то я могу попробовать проверить его в боевых условиях на сервере с залипшим IO. Мне моя интуиция говорит, что будет примерно то же самое.

В чём вопрос-то: а что именно делает функция daemon()? Всякие закрытия stdin/out мне понятны. Мне интересен момент, когда башу возвращается управление. Для этого нужно, видимо, завершить основной тред, да?

А этого-то как раз я и не могу сделать в питоне.

Ща попробую на ассемблере сисколл вызвать...
404

Я сдаюсь

ctypes.CDLL("libc.so.6").syscall(1,0,0)

Всюду работает, а для полузависшего процесса - нет. Но ведь я могу в скрине его спокойно отцепить. Как? КАК?

Пока что мне в голову не приходит ничего лучше, чем организация в bg до вызова плохого кода. ТО есть daemon() до того, как что-то делать. Но это очень фигово, потому что вывод будет, грубо говоря, асинхронным (то есть мы пишем в терминал уже не имея control terminal'а), что чревато всеобщей ненавистью. Ща пробую...

Да, так работает.

ctypes.CDLL("libc.so.6").daemon() 
try:
        hung()
except thread_timeout.FailedKillExecTimeout:
        print "failed"


Но это же не выход, а порнография кака-то.

(на всякий случай, повторю, что вот так вот - не работает):


try:
        hung()
except thread_timeout.FailedKillExecTimeout:
        ctypes.CDLL("libc.so.6").daemon() 
        print "failed"
404

Убейте меня кто-нибудь...

Роскомнадзор: это призыв к суициду с детальным описанием метода.

MAIN_PID = os.getpid()
pid = ctypes.CDLL("libc.so.6").fork()
if pid == 0:
        try:
                hung()
        except thread_timeout.FailedKillExecTimeout:
                print "timeout, terminating"
                os.kill(MAIN_PID, 9)
                raise SystemExit
        do_something()
        os.kill(MAIN_PID, 9)
else:
        while True:
                time.sleep(100000000)


Пересказ на словах:

1) Запоминаем PID основного процесса.
2) Форкаемся
3) Основной процесс при этом уходит в бесконечный цикл, но обрабатывает сигналы. Он же держит консоль баша. Завершается после kill -9.
4) Потомок: пытается выполнить плохую функцию. В отдельном треде.
5) Если функция нормально завершилась, то просто продолжает работать. Когда заканчивает - посылает kill -9 в родителя и завершается.
6) Если мы поймали таймаут выполнения (то есть "всё плохо"), то: посылает kill -9 в родителя и пытается выйти (на практике - зависает навсегда).

И да, оно возвращает управление в консоль и т.д. Вероятнее всего, для чистоты эксперимента надо сделать обработчик сигналов, который все сигналы передаёт потомку.



Какой пиздец. Какой невероятный, измученный жизнью пиздец. И да, это сейчас поползёт в продакшен, потому что жить-то надо. И, насколько я понимаю, это официальное решение проблемы. Потому что syscall (1) не завершает процесс при неубиваемом процессе. И как отцепиться от баша - не понятно.

Вот именно эта часть мне не понятна. Что именно происходит при SIGTSTP? Как процесс уходит в bg для баша? Что процессу надо сделать, чтобы стать "bg" для баша?
404

code review request

Я знаю, это жопа, и так жить нельзя, но а) жопа дана нам в ощущениях б) жить надо.

Просьба посмотреть на этот код и рассказать мне почему он говно (кроме экзестенциальной части):

(начиная с функции continue_in_fork() и далее)

https://github.com/amarao/thread_timeout/blob/double_fork/thread_timeout/__init__.py#L148

Пример использования:
https://github.com/amarao/thread_timeout/blob/double_fork/fork_test.py

(По нажатию Ctrl-C сигнал ловится родителем, родитель перепосылает сигнал ребёнку, ребёнок отрабатывает atexit() и перепосылает сигнал родителю, который по SIGTERM завершается).

Как для такого писать адекватные тесты - не знаю.