Python - как перезапустить приложение "на лету", когда приложение имеет порт TCP в режиме прослушивания?
Каков наилучший способ перезапуска приложения, в котором он запускал TCP-порт прослушивания?
Проблема заключается в следующем: если я быстро запустил приложение в качестве перезапуска, он терпит неудачу, потому что прослушиваемый сокет уже используется.
Как безопасно перезапустить в таком случае?
socket.error: [Errno 98] Address already in use
код:
#!/usr/bin/python
import sys,os
import pygtk, gtk, gobject
import socket, datetime, threading
import ConfigParser
import urllib2
import subprocess
def server(host, port):
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((host, port))
sock.listen(1)
print "Listening... "
gobject.io_add_watch(sock, gobject.IO_IN, listener)
def listener(sock, *args):
conn, addr = sock.accept()
print "Connected"
gobject.io_add_watch(conn, gobject.IO_IN, handler)
return True
def handler(conn, *args):
line = conn.recv(4096)
if not len(line):
print "Connection closed."
return False
else:
print line
if line.startswith("unittest"):
subprocess.call("/var/tmp/runme.sh", shell=True)
else:
print "not ok"
return True
server('localhost', 8080)
gobject.MainLoop().run()
runme.sh
#!/bin/bash
ps aux | grep py.py | awk '{print $2}' | xargs kill -9;
export DISPLAY=:0.0 && lsof -i tcp:58888 | grep LISTEN | awk '{print $2}' | xargs kill -9;
export DISPLAY=:0.0 && java -cp Something.jar System.V &
export DISPLAY=:0.0 && /var/tmp/py.py &
EDIT:
Обратите внимание, что я использую Java и Python вместе как одно приложение с двумя слоями. Итак, runme.sh - это мой запуск script для запуска обоих приложений одновременно. Из Java я нажимаю кнопку перезапуска Python. Но Python не перезапускает, потому что kill выполняется через BASH.
Ответы
Ответ 1
Вам нужно будет найти эквивалент Python для установки SO_REUSEADDR
в сокете, прежде чем связывать его. Обеспечение закрытия розетки при выходе, как рекомендовано в других ответах, не является ни необходимым, ни достаточным, так как (а) сокеты закрываются ОС при выходе из процесса и (б) вам все равно придется преодолевать принятые соединения в состоянии TIME_WAIT
, который может выполнять только SO_REUSEADDR
.
Ответ 2
1.
У вас есть проблема с убийством вашего python
air:~ dima$ ps aux | grep i-dont-exist.py | awk '{print $2}'
34198
Это означает, что ваш процесс grep
попадает в вашу логику перезапуска и убивается.
В linux вы можете использовать pidof вместо этого.
Альтернативно используйте start-stop-daemon и pid файл.
2.
Вы уже используете адрес повторно, поэтому я думаю, ваш питон не умирает достаточно быстро.
Для быстрого теста добавьте спать, прежде чем запускать python снова.
Если это помогает, добавьте цикл ожидания ожидания после команды kill и только запустите новый python, если вы уверены, что старый python больше не работает.
Ответ 3
Есть ли вероятность, что ваша программа Python запускает другие процессы? например через fork, subprocess или os.system?
Возможно, что ваш дескриптор прослушивающего файла наследуется порожденным процессом:
os.system( "sleep 1000" ) # без сокетов:
ls -l /proc/`pidof sleep`/fd
total 0
lrwx------ 1 user user 64 2012-12-19 19:52 0 -> /dev/pts/0
lrwx------ 1 user user 64 2012-12-19 19:52 1 -> /dev/pts/0
l-wx------ 1 user user 64 2012-12-19 19:52 13 -> /dev/null
lrwx------ 1 user user 64 2012-12-19 19:52 2 -> /dev/pts/0
разъем(); setsockopt(); связывания(); Слушать(); os.system( "sleep 1000" ) # с сокетами:
ls -l /proc/`pidof sleep`/fd
total 0
lrwx------ 1 user user 64 2012-12-19 19:49 0 -> /dev/pts/0
lrwx------ 1 user user 64 2012-12-19 19:49 1 -> /dev/pts/0
l-wx------ 1 user user 64 2012-12-19 19:49 13 -> /dev/null
lrwx------ 1 user user 64 2012-12-19 19:49 2 -> /dev/pts/0
lrwx------ 1 user user 64 2012-12-19 19:49 5 -> socket:[238967]
lrwx------ 1 user user 64 2012-12-19 19:49 6 -> socket:[238969]
Возможно, ваш Python script умер, но его дети этого не сделали, последние сохраняют ссылку на прослушивающий сокет, и поэтому новый процесс Python не может связываться с одним и тем же адресом.
Ответ 4
Вот мое предположение: kill асинхронно. Он просто сообщает ядру отправить сигнал процессу, он также не дожидается, когда сигнал будет доставлен и обработан. Перед перезапуском процесса вы должны использовать команду wait.
$ wait $PID
Ответ 5
Вы можете добавить больше логики к вашему запуску script, чтобы выполнить предварительное тестирование и очистку.
#!/bin/bash
export DISPLAY=:0.0
# If py.py is found running
if pgrep py.py; then
for n in $(seq 1 9); do
# kill py.py starting at kill -1 and increase to kill -9
if ! pgrep py.py; then
# if no running py.py is found break out of this loop
break
fi
pkill -${n} py.py
sleep .5
done
fi
# Verify nothing has tcp/58888 open in a listening state
if lsof -t -i tcp:58888 -stcp:listen; then
echo process with pid $(lsof -t -i tcp:58888 -stcp:listen) still listening on port 58888, exiting
exit
fi
java -cp Something.jar System.V &
/var/tmp/py.py &
В конце концов вы, вероятно, захотите использовать полномасштабный init script и демонзировать эти процессы. См. http://www.thegeekstuff.com/2012/03/lsbinit-script/ для примера, хотя, если ваши процессы запущены как непривилегированный пользователь, который немного изменит реализацию, но общие концепции то же самое.
Ответ 6
Возможное решение # 1: Fork и выполнить новую копию вашего python script из старой. Он унаследует прослушивающий сокет. Затем, при желании, отсоедините его от родителя и убейте (или выйдите) из родителя. Обратите внимание, что родительский (старая версия) может завершить обслуживание любых существующих запросов, даже если ребенок (новая версия) обрабатывает любые новые входящие запросы.
Возможное решение №2: передать старый запуск script передать сокет новому script с sendmsg()
и SCM_RIGHTS
, а затем убить старый script. Этот пример кода говорит о "дескрипторах файлов", но отлично работает с сокетами. Смотрите: Как передать прослушивающий сокет TCP с минимальным временем простоя?
Возможное решение # 3: Если bind()
возвращает EADDRINUSE, подождите некоторое время и повторите попытку, пока он не удастся. Если вам нужно перезапустить script быстро и без простоя между ними, это не сработает, конечно:)
Возможное решение №4: Не убивайте свой процесс с помощью kill -9. Убейте его другим сигналом, например SIGTERM
. Поймайте SIGTERM
и вызовите gobject.MainLoop.quit()
, когда получите это.
Возможное решение №5: Убедитесь, что на нем установлен родительский процесс вашего python script (например, оболочка) wait
. Если родительский процесс script не запущен, или если script демонанизирован, то, если его убили с помощью SIGKILL
, init станет его родителем. init вызывает wait
периодически, но это может занять немного времени, возможно, это то, с чем вы столкнулись. Если вы должны использовать SIGKILL
, но вы хотите, чтобы более быстрая очистка просто вызывала wait
самостоятельно.
Решения 4 и 5 имеют очень короткое, но отличное от нуля время между остановкой старого script и запуском нового. Решение 3 имеет потенциально значительное время между ними, но очень просто. Решения 1 и 2 - это способы сделать это буквально без простоя: любой вызов соединения будет успешным и получит либо старый, либо новый запуск script.
P.S. Более подробно о поведении SO_REUSEADDR
на разных платформах: SO_REUSEADDR не имеет той же семантики в Windows, что и в Unix
В Windows, однако, эта опция фактически означает что-то совершенно другой. Это означает, что адрес должен быть украден из любого процесс, который в настоящее время использует его.
Я не уверен, что это то, над чем вы работаете, но обратите внимание, что, как описано здесь, поведение в разных версиях Unix также несколько отличается.
Ответ 7
Что бы я ни пытался, не работал. Поэтому, чтобы уменьшить риск, я начал использовать файловую систему в качестве примера сокета:
# Echo server program
import socket,os
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
os.remove("/tmp/socketname")
except OSError:
pass
s.bind("/tmp/socketname")
s.listen(1)
conn, addr = s.accept()
while 1:
data = conn.recv(1024)
if not data: break
conn.send(data)
conn.close()
# Echo client program
import socket
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect("/tmp/socketname")
s.send('Hello, world')
data = s.recv(1024)
s.close()
print 'Received', repr(data)