Этому вопросу 6 лет, но у меня была та же проблема, и существующие ответы не были кроссплатформенными для моего варианта использования. Хотя службы Windows часто используются так же, как и демоны Unix, в конце концов они существенно различаются, и «в деталях дьявол». Короче говоря, я решил попытаться найти что-то, что позволило бы мне запускать один и тот же код приложения как в Unix, так и в Windows, в то же время выполняя ожидания для хорошо работающего демона Unix (который лучше объяснить в других местах ) как можно лучше на обеих платформах:
- Закрытие открытых дескрипторов файлов (как правило, все они, но некоторым приложениям может потребоваться защитить некоторые дескрипторы от закрытия)
- Измените рабочий каталог для процесса на подходящее место, чтобы предотвратить ошибки "Directory Busy"
- Изменить маску создания доступа к файлу (
os.umask
в мире Python)
- Переместить приложение в фоновый режим и отключить его от исходного процесса
- Полностью развестись с терминалом, включая перенаправление
STDIN
, STDOUT
и STDERR
на разные потоки (часто DEVNULL
), и предотвратить повторный захват управляющего терминала
- Обработка сигналов, в частности,
SIGTERM
.
Основная проблема кросс-платформенной демонизации заключается в том, что Windows как операционная система действительно не поддерживает понятие демона: приложения, которые запускаются из терминала (или в любом другом интерактивном контексте, включая запуск из Explorer, и т. д.) будет продолжать работать с видимым окном, если только управляющее приложение (в данном примере Python) не имеет GUI без окон. Кроме того, обработка сигналов Windows крайне неадекватна, и попытки отправить сигналы в независимый процесс Python (в отличие от подпроцесса, который не выдержит закрытия терминала) почти всегда приводят к немедленному выходу этого Python процесс без очистки (без finally:
, без atexit
, без __del__
и т. д.).
Службы Windows (хотя во многих случаях это жизнеспособная альтернатива) были в принципе для меня невозможны: они не кроссплатформенные, и для них потребуется модификация кода. pythonw.exe
( версия Python без окон , которая поставляется со всеми последними двоичными файлами Windows Python) ближе, но она все еще не совсем уходит: в частности, она не может улучшить ситуацию с обработкой сигналов и вы все еще не можете легко запустить приложение pythonw.exe
из терминала и взаимодействовать с ним во время запуска (например, для доставки аргументов динамического запуска в ваш скрипт, скажем, возможно, пароля, пути к файлу и т. д.), до «демонизация».
В итоге я остановился на использовании subprocess.Popen
с ключевым словом creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
для создания независимого процесса без окон:
import subprocess
independent_process = subprocess.Popen(
'/path/to/pythonw.exe /path/to/file.py',
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
)
Однако, это все еще оставило меня с дополнительным вызовом при запуске связи и обработке сигналов. Не вдаваясь в тонну деталей, для первого моя стратегия была:
pickle
важные части пространства имен процесса запуска
- Сохраните это в
tempfile
- Добавить путь к этому файлу в среде дочернего процесса перед запуском
- Извлечение и возврат пространства имен из функции «демонизация»
Для обработки сигналов мне нужно было немного более креативно. В рамках «демонизированного» процесса:
- Игнорировать сигналы в процессе демона, поскольку, как уже упоминалось, все они завершают процесс немедленно и без очистки
- Создать новый поток для управления обработкой сигналов
- Этот поток запускает дочерние процессы обработки сигналов и ожидает их завершения
- Внешние приложения отправляют сигналы в дочерний процесс обработки сигналов, вызывая его завершение и завершение
- Затем эти процессы используют номер сигнала в качестве кода возврата
- Поток обработки сигналов считывает код возврата и затем вызывает либо определяемый пользователем обработчик сигналов, либо использует API cytpes, чтобы вызвать соответствующее исключение в основном потоке Python
- Промыть и повторить для новых сигналов
Тем не менее, для всех, кто столкнется с этой проблемой в будущем, я свернул библиотеку под названием daemoniker , которая объединяет правильную демонизацию Unix и вышеуказанной стратегии Windows в единый фасад. кроссплатформенный API выглядит следующим образом:
from daemoniker import Daemonizer
with Daemonizer() as (is_setup, daemonizer):
if is_setup:
# This code is run before daemonization.
do_things_here()
# We need to explicitly pass resources to the daemon; other variables
# may not be correct
is_parent, my_arg1, my_arg2 = daemonizer(
path_to_pid_file,
my_arg1,
my_arg2
)
if is_parent:
# Run code in the parent after daemonization
parent_only_code()
# We are now daemonized, and the parent just exited.
code_continues_here()