Уведомления

Группа в Telegram: @pythonsu

#1 Дек. 3, 2011 08:16:43

corpse
От:
Зарегистрирован: 2010-03-27
Сообщения: 19
Репутация: +  0  -
Профиль   Отправить e-mail  

Корректный запуск через subprocess и работа с потоками

Добрый день!

Копал в нескольких местах, но так и не нашёл чёткого решения проблемы. Итак, есть некая команда для запуска в шелле. Допустим, у меня она генерируется и помещается в переменную cmd. Затем я запускаю полученную команду, жду некоторое время, после чего мне нужно проверить, корректно ли запустилась эта команда, т.е. пуст ли поток вывода. Если он не пуст, мы выполняем одни действия, если всё хорошо, то соответственно другие. Логика проста.

# Запуск
cmd = subprocess.Popen(str(stcmd), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
# Затем я пытаюсь прочитать поток ошибок:
cmderr = cmd.stderr.read()
if cmderr:
print 'ERROR: ', cmderr
else:
print 'All is fine.'
В том случае, если в потоке ошибок что-то есть, то всё работает как нужно. Но если команда запустилась без ошибок, то я буду ждать окончания чтения из потока ошибок вечно. Заруливать поток ошибок в стандартный поток вывода - не вариант, ибо мне нужно эти данные отделить друг от друга. Пользоваться средствами ОС и делать cmd 2>error.log, а потом его читать - как-то не структурно и некрасиво. Есть ли какой-нибудь правильный вариант? Можно ли узнать, есть ли что-то в потоке вывода ошибок без попытки чтения из него?

Попытался сделать через communicate:
                    cmd = subprocess.Popen(str(stcmd+' &'), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
print 'test1'
cmdout, cmderr = cmd.communicate()
if cmderr:
self.__sql__('update vm set status = \'error\', error = \' CMDERROR: ' + str(cmderr) + '\' where vid = ' + str(id) + ';', 0)
return self.error('There are some error, while starting VM:\n' + cmderr)
print 'test2'
Симптомы те же. В случае ошибки всё проходит корректно, т.е. мы выводим в консоль test1, затем возвращаем сообщение об ошибке. А при корректном старте в консоль происходит вывод test1, а до test2 мы так и не доходим, хотя вроде как должны. Т.е. subprocess отрабатывает корректно, а cmd.communicate() при отсутствии данных в stderr так и не выполнется. В чём может быть проблема?

Насколько я читал, там ещё встаёт проблема буферизации по 4кб, т.е. пока буфер не наполнится, мы так же будем при чтении из пайпа ждать до посинения. Странно, что я с этой проблемой не столкнулся. Потому что при наличии данных в потоке вывода, я их получаю при read вне зависимости от их объёма, т.е. даже если вывод менее 1кб, по крайней мере с stderr.



Офлайн

#2 Дек. 3, 2011 09:28:05

o7412369815963
От:
Зарегистрирован: 2009-06-17
Сообщения: 1986
Репутация: +  32  -
Профиль   Отправить e-mail  

Корректный запуск через subprocess и работа с потоками

можно попробовать использовать select: http://www.py-my.ru/post/4bfb3c691d41c846bc00006a

для mssql, можно использовать pyodbc, там выскакивает exception в случае ошибки

Офлайн

#3 Дек. 3, 2011 09:42:05

py.user.next
От:
Зарегистрирован: 2010-04-29
Сообщения: 9730
Репутация: +  843  -
Профиль   Отправить e-mail  

Корректный запуск через subprocess и работа с потоками

import subprocess
cmd = 'echo x > /etc/x.txt'
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
print(p)
if p[1]:
print('err')
elif p[0]:
print('no err')
else:
print('no err, no output')
или там нужно бесконечно работающую программу анализировать ?



Отредактировано (Дек. 3, 2011 09:46:31)

Офлайн

#4 Дек. 3, 2011 10:08:53

corpse
От:
Зарегистрирован: 2010-03-27
Сообщения: 19
Репутация: +  0  -
Профиль   Отправить e-mail  

Корректный запуск через subprocess и работа с потоками

Попробовал следующим образом:

                    print 'test0'
cmd = subprocess.Popen(str(stcmd), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
print 'test1'
if cmd[1]:
print 'We have some error:\n', cmd[1]
elif cmd[0]:
print 'We have some output:\n', cmd[0]
print 'test2'
Результат всё тот же: ‘test0’ и всё, дальше затуп. Вообще, пишу web интерфейс для qemu. Задача запустить дочерний процесс, подождать скажем, полсекунды, посмотреть, есть ли что-то в stdout, stderror и освободить пайп - пусть пишет дальше туда всё, что хочет. Мне важно отследить - запустился ли дочерний процесс и если нет, то почему.

При этом получаю довольно странную картинку под дебианом по lsof:
corpse@white:/opt/esvirt$ lsof -Pnl +M -i4
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
qemu 1817 1000 13u IPv4 5367 0t0 TCP 192.168.2.201:5908 (LISTEN) # запущено руками
qemu 1817 1000 14u IPv4 5383 0t0 TCP 192.168.2.201:5908->192.168.2.19:50072 (ESTABLISHED) # запущено руками
esweb.py 1987 1000 3u IPv4 5659 0t0 TCP 192.168.2.201:8080->192.168.2.19:34314 (ESTABLISHED) # это я вишу на вебинтерфейсе
esweb.py 1987 1000 5u IPv4 5655 0t0 TCP 192.168.2.201:8080 (LISTEN) # это мы слушаем порт и ожидаем соединения от всех остальных
sh 2015 1000 3u IPv4 5659 0t0 TCP 192.168.2.201:8080->192.168.2.19:34314 (ESTABLISHED) # это собственно тот самый shell=True, насколько я понимаю. И cmd.pid я получаю именно от него.
sh 2015 1000 5u IPv4 5655 0t0 TCP 192.168.2.201:8080 (LISTEN) # мне только не совсем понятно, почему тут указываются эти порты и какое отношение имеет запущенный дочерний процесс к тому, что там было запущенно через webop
qemu 2016 1000 3u IPv4 5659 0t0 TCP 192.168.2.201:8080->192.168.2.19:34314 (ESTABLISHED) # Вот и процесс, запущенный из шелла делает вид, что он тоже висит на этих портах видимо это как раз происходит из-за открытых сокетов
qemu 2016 1000 5u IPv4 5655 0t0 TCP 192.168.2.201:8080 (LISTEN)
qemu 2016 1000 16u IPv4 5742 0t0 TCP 192.168.2.201:5901 (LISTEN) # ну а это vnc, который собственно открывает сам процесс qemu
corpse@white:/opt/esvirt$ ps aux | grep qemu
corpse 1817 29.6 3.6 589564 287556 pts/1 Sl 14:43 3:25 qemu --enable-kvm --usbdevice tablet -m 256 -net nic,model=rtl8139,macaddr=00:00:DE:AD:00:08 -net tap,ifname=tap0 -vnc 192.168.2.201:8,password -monitor vc -monitor unix:/opt/esvirt/run/tolstobokov_8.sock,server,nowait -hda images/tolstobokov.qcow2 # запущено руками
corpse 2015 0.0 0.0 3952 564 pts/1 S 14:49 0:00 /bin/sh -c qemu --enable-kvm --usbdevice tablet -m 256 -localtime -cdrom /mnt/share/files/images/other/kolibri.iso -net nic,model=rtl8139 -net tap,ifname=tap1 -vnc 192.168.2.201:01 -monitor vc -monitor unix:./run/kolibri.sock,server,nowait -boot d # Шелл, дёрнутый через subprocess
corpse 2016 2.9 0.3 572152 27896 pts/1 R 14:49 0:10 qemu --enable-kvm --usbdevice tablet -m 256 -localtime -cdrom /mnt/share/files/images/other/kolibri.iso -net nic,model=rtl8139 -net tap,ifname=tap1 -vnc 192.168.2.201:01 -monitor vc -monitor unix:./run/kolibri.sock,server,nowait -boot d # Сам процесс. Причём если убить шелл с пидом 2015, сам процесс с пидом 2016 будет благополучно жив и здоров.

# А это вывод при запуске:
serving on http://192.168.2.201:8080
test0
# На этом месте мы наглухо повисаем, пока жив процесс с пидом 2016



Офлайн

#5 Дек. 3, 2011 10:14:31

corpse
От:
Зарегистрирован: 2010-03-27
Сообщения: 19
Репутация: +  0  -
Профиль   Отправить e-mail  

Корректный запуск через subprocess и работа с потоками

o7412369815963
можно попробовать использовать select: http://www.py-my.ru/post/4bfb3c691d41c846bc00006a

для mssql, можно использовать pyodbc, там выскакивает exception в случае ошибки
Это я попробую завтра, сегодня уже не успеваю. Вроде бы ведь говорят, что уже не кошерно использовать os.popen* и все рекомендуют вместо него использовать сабпроцесс?
Немного не понял, при чём тут mssql. Я в данном случае ничего кроме sqlite не использую.



Офлайн

#6 Дек. 3, 2011 11:43:42

doza_and
От:
Зарегистрирован: 2010-08-15
Сообщения: 4138
Репутация: +  252  -
Профиль   Отправить e-mail  

Корректный запуск через subprocess и работа с потоками

Не очень понял когда завершается ваш подпроцесс. Т.е. в случае ошибки он завершается или будет дальше висеть?
Может ваш подпроцесс действует разумно и в случае ошибок возвращает код ошибки? Тогда check_call вам поможет.

В противном случае приходит в голову только пожертвовать тредом - повесить его слушать ваш stderr в отдельном потоке для неблокирующего чтения см например
http://stackoverflow.com/questions/375427/non-blocking-read-on-a-subprocess-pipe-in-python

Просто перевести sterr в неблокирующий режим не очень надежное решение - вы ведь не знаете сколько времени надо ждать сообщения об ошибке.



Отредактировано (Дек. 3, 2011 11:48:51)

Офлайн

#7 Дек. 3, 2011 14:33:53

o7412369815963
От:
Зарегистрирован: 2009-06-17
Сообщения: 1986
Репутация: +  32  -
Профиль   Отправить e-mail  

Корректный запуск через subprocess и работа с потоками

corpse
o7412369815963
можно попробовать использовать select: http://www.py-my.ru/post/4bfb3c691d41c846bc00006a
Немного не понял, при чём тут mssql. Я в данном случае ничего кроме sqlite не использую.
Да, лучше сабпроцес, там просто пример по применению селектов.

Я б наверно сделал так:
1) Клиент кликает кнопку старт - идет ajax запрос с командой запуск, у клиента меняется статус “запускается…”
2) на серверной части идет запуск python-оболчки, и назначение идентификатора задаче (хотя не обязательно), клиенту отсылается этот ид.
3) python-оболочка запускает процесс и через селекты, либо в разных потоках опрашивает вывод, складывая его в файлы/бд.
4) на клиенте таймер - раз в 5 сек опрашивает сервер на получение логов, сервер отдает логи по ид, клиент их отрисовывает.

если не нужно что-б python-оболочка висела, можно задать ей самоубийство через Х секунд, с помощью сигналов.

итого: в браузере кликаем кнопку старт, где динамически отрисовываются логи, время работы и пр. инфу.

Офлайн

#8 Дек. 3, 2011 20:11:42

corpse
От:
Зарегистрирован: 2010-03-27
Сообщения: 19
Репутация: +  0  -
Профиль   Отправить e-mail  

Корректный запуск через subprocess и работа с потоками

doza_and
Не очень понял когда завершается ваш подпроцесс. Т.е. в случае ошибки он завершается или будет дальше висеть?
Может ваш подпроцесс действует разумно и в случае ошибок возвращает код ошибки? Тогда check_call вам поможет.
В случае ошибки - завершается сразу. Собственно, в этом-то случае никаких проблем нет. Я корректно обрабатываю ошибку и всё хорошо.

doza_and
В противном случае приходит в голову только пожертвовать тредом - повесить его слушать ваш stderr в отдельном потоке для неблокирующего чтения см например
http://stackoverflow.com/questions/3754 … -in-python
О тредах я думал. Хотя теперь у меня возникла мысль о том, что с идеологической точки зрения для этого приложения будет лучше при каждом запуске дочернего процесса делать два уникальных лога с указанием пида нужного процесса и даты - на stdout и stderr и хранить имена файлов в базе, при необходимости читая оттуда. Это будет правильнее, так как при краше приложения дочерние процессы должны выполняться автономно и желательно бы иметь информацию о их работе. Плюс файл с stdout можно будет легко дополнить по необходимости своими данными.

Приложение я перепишу с учётом всего этого, но мне было бы крайне интересно узнать на будущее, как реализовать то, что я пытался сделать сначала. Т.е. запустить некий процесс, посмотреть наличие данных в stderr спустя какое-то краткое время (например, 0.5с), после чего отключиться от stderr и забыть о его существовании в принципе.

doza_and
Просто перевести sterr в неблокирующий режим не очень надежное решение - вы ведь не знаете сколько времени надо ждать сообщения об ошибке.
Предположим, меня интересуют ошибки, происходящие исключительно в момент 0 - 0.5с относительно запуска. После 0.5с гори оно синим пламенем. :) Это можно сделать без тредов?



Офлайн

#9 Дек. 3, 2011 20:16:11

o7412369815963
От:
Зарегистрирован: 2009-06-17
Сообщения: 1986
Репутация: +  32  -
Профиль   Отправить e-mail  

Корректный запуск через subprocess и работа с потоками

corpse
Предположим, меня интересуют ошибки, происходящие исключительно в момент 0 - 0.5с относительно запуска. После 0.5с гори оно синим пламенем. :) Это можно сделать без тредов?
Можно выполнить чтение stderr, и оборвать через 0,5сек сигналом, но сигналы работают только в главном процессе.

Офлайн

#10 Дек. 3, 2011 20:28:20

corpse
От:
Зарегистрирован: 2010-03-27
Сообщения: 19
Репутация: +  0  -
Профиль   Отправить e-mail  

Корректный запуск через subprocess и работа с потоками

o7412369815963
Я б наверно сделал так:
2) на серверной части идет запуск python-оболчки, и назначение идентификатора задаче (хотя не обязательно), клиенту отсылается этот ид.
3) python-оболочка запускает процесс и через селекты, либо в разных потоках опрашивает вывод, складывая его в файлы/бд.
4) на клиенте таймер - раз в 5 сек опрашивает сервер на получение логов, сервер отдает логи по ид, клиент их отрисовывает.

если не нужно что-б python-оболочка висела, можно задать ей самоубийство через Х секунд, с помощью сигналов.

итого: в браузере кликаем кнопку старт, где динамически отрисовываются логи, время работы и пр. инфу.
2. Тут не клиент-сервер получается. Точнее не совсем так. Приложение является одновременно ещё и http сервером (использую webop), на котором реализована веб-мордочка.
3. Уже осознал, что минимумом заморочек и самым верным решением будет сделать вывод всего в логи и чтение из них (при том, если серверная часть упадёт, можно будет судить о работе дочерних процессов по логам, которые никуда не денутся и будут работать спокойно дальше). Тогда можно не плодить тредов, не обрабатывать, не складывать, а сразу читать нужное из файла, а при обновлении просто хранить в базе номер строки, на которой остановились и читать уже с этой строки.
4. Соответственно, подгруженный html на стороне клиента ява скриптом дёргает наш сервер, передавая ему имя лога и номер строки, на которой остановился, на что ему функция на серверной стороне выплёвывает необходимый кусок лога. Все счастливы.



Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version