Убедитесь, что один экземпляр приложения в 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, вы можете проверить свой идентификатор процесса и сказать, не произошел ли сбой, нет?

Я не делал этого лично, так что принимайте с соответствующим количеством соли.:п

Другие вопросы по тегам