Убедитесь, что один экземпляр приложения в Linux
Я работаю над приложением с графическим интерфейсом в WxPython, и я не уверен, как я могу гарантировать, что в любой момент времени на компьютере работает только одна копия моего приложения. Из-за характера приложения запуск более одного раза не имеет никакого смысла и быстро завершится неудачей. Под Win32 я могу просто сделать именованный мьютекс и проверить это при запуске. К сожалению, я не знаю каких-либо средств в Linux, которые могли бы сделать это.
Я ищу что-то, что будет автоматически выпущено в случае неожиданного сбоя приложения. Я не хочу обременять своих пользователей необходимостью вручную удалять файлы блокировки, потому что я потерпел крах.
12 ответов
Есть несколько распространенных методов, включая использование семафоров. Чаще всего я вижу, что при запуске создается "файл блокировки pid", который содержит pid запущенного процесса. Если файл уже существует, когда программа запускается, откройте его и возьмите pid внутри, проверьте, запущен ли процесс с этим pid, если он проверяет значение cmdline в / proc /pid, чтобы увидеть, является ли он Экземпляр вашей программы, если он затем выйдет, в противном случае перезапишите файл с вашим pid. Обычное имя для файла pid: application_name.pid
,
Правильная вещь - это консультативная блокировка с использованием flock(LOCK_EX)
; в Python это находится в fcntl
модуль.
В отличие от pid-файлов, эти блокировки всегда автоматически снимаются, когда ваш процесс по какой-либо причине умирает, нет условий гонки, связанных с удалением файла (так как файл не нужно удалять, чтобы снять блокировку), и нет никаких шансов на другое процесс, наследующий PID и, таким образом, появляющийся для проверки устаревшей блокировки.
Если вы хотите нечистое обнаружение выключения, вы можете записать маркер (например, ваш PID для традиционалистов) в файл после снятия блокировки, а затем усечь файл до 0-байтового состояния перед чистым выключением (пока удерживается блокировка).); таким образом, если блокировка не удерживается и файл не пуст, указывается нечистое завершение работы.
Полное решение для блокировки с использованием fcntl
модуль:
import fcntl
pid_file = 'program.pid'
fp = open(pid_file, 'w')
try:
fcntl.lockf(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
# another instance is running
sys.exit(1)
Для этой цели wxWidgets предлагает класс wxSingleInstanceChecker: wxPython doc или wxWidgets doc. В документе wxWidgets есть пример кода на C++, но эквивалент Python должен быть примерно таким (непроверенным):
name = "MyApp-%s" % wx.GetUserId()
checker = wx.SingleInstanceChecker(name)
if checker.IsAnotherRunning():
return False
Это основано на ответе пользователя zgoda. В основном это касается сложной проблемы, связанной с доступом на запись в файл блокировки. В частности, если файл блокировки был впервые создан root
другой пользователь foo
больше не может успешно пытаться переписать этот файл из-за отсутствия прав на запись для пользователя foo
, Кажется, очевидным решением является создание файла с правами на запись для всех. Это решение также основано на другом ответе от меня, создавая файл с такими пользовательскими разрешениями. Эта проблема важна в реальном мире, где ваша программа может запускаться любым пользователем, включая root
,
import fcntl, os, stat, tempfile
app_name = 'myapp' # <-- Customize this value
# Establish lock file settings
lf_name = '.{}.lock'.format(app_name)
lf_path = os.path.join(tempfile.gettempdir(), lf_name)
lf_flags = os.O_WRONLY | os.O_CREAT
lf_mode = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH # This is 0o222, i.e. 146
# Create lock file
# Regarding umask, see https://stackru.com/a/15015748/832230
umask_original = os.umask(0)
try:
lf_fd = os.open(lf_path, lf_flags, lf_mode)
finally:
os.umask(umask_original)
# Try locking the file
try:
fcntl.lockf(lf_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
msg = ('Error: {} may already be running. Only one instance of it '
'can run at a time.'
).format('appname')
exit(msg)
Ограничением приведенного выше кода является то, что если файл блокировки уже существовал с неожиданными разрешениями, эти разрешения не будут исправлены.
Я бы хотел использовать /var/run/<appname>/
в качестве каталога для файла блокировки, но для создания этого каталога требуется root
разрешения. Вы можете сами решить, какой каталог использовать.
Обратите внимание, что нет необходимости открывать дескриптор файла для файла блокировки.
Вот решение на основе TCP-порта:
# Use a listening socket as a mutex against multiple invocations
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 5080))
s.listen(1)
Набор функций, определенных вsemaphore.h
- sem_open()
, sem_trywait()
и т. д. - это POSIX-эквивалент, я полагаю.
Ищите модуль python, который взаимодействует с семафорами SYSV в Unix. Семафоры имеют флаг SEM_UNDO, который приведет к освобождению ресурсов, удерживаемых процессом, в случае сбоя процесса.
В противном случае, как предложил Бернард, вы можете использовать
import os
os.getpid()
И запишите это в / var / run /application_name.pid. Когда процесс запускается, он должен проверить, указан ли pid в / var / run /application_name.pid в таблице ps, и выйти, если он есть, в противном случае записать свой собственный pid в / var / run /application_name.pid. В следующем примере var_run_pid - это pid, который вы прочитали из / var / run /application_name.pid
cmd = "ps -p %s -o comm=" % var_run_pid
app_name = os.popen(cmd).read().strip()
if len(app_name) > 0:
Already running
Можете ли вы использовать утилиту 'pidof'? Если ваше приложение запущено, pidof запишет идентификатор процесса вашего приложения в стандартный вывод. Если нет, он напечатает новую строку (LF) и вернет код ошибки.
Пример (от bash, для простоты):
linux# pidof myapp
8947
linux# pidof nonexistent_app
linux#
Безусловно, самый распространенный метод - поместить файл в /var/run/ с именем [application].pid, который содержит только PID запущенного процесса или родительского процесса. В качестве альтернативы вы можете создать именованный канал в том же каталоге, чтобы иметь возможность отправлять сообщения активному процессу, например, для открытия нового файла.
Я создал базовую среду для запуска приложений такого рода, когда вы хотите иметь возможность передавать аргументы командной строки последующих попыток экземпляров в первый. Экземпляр начнет прослушивание на заранее определенном порте, если он не найдет экземпляр, уже прослушивающий его. Если экземпляр уже существует, он отправляет свои аргументы командной строки через сокет и завершает работу.
Если вы создадите файл блокировки и поместите в него pid, вы можете проверить свой идентификатор процесса и сказать, не произошел ли сбой, нет?
Я не делал этого лично, так что принимайте с соответствующим количеством соли.:п