Убедитесь, что один экземпляр приложения в Linux

StackOverflow https://stackoverflow.com/questions/220525

  •  03-07-2019
  •  | 
  •  

Вопрос

Я работаю над приложением с графическим интерфейсом в WxPython, и я не уверен, каким образом я могу гарантировать, что в любой момент времени на компьютере работает только одна копия моего приложения. Из-за характера приложения запуск более одного раза не имеет никакого смысла и быстро завершится неудачей. Под Win32 я могу просто сделать именованный мьютекс и проверить это при запуске. К сожалению, я не знаю каких-либо средств в Linux, которые могли бы сделать это.

Я ищу что-то, что будет автоматически выпущено в случае неожиданного сбоя приложения. Я не хочу обременять своих пользователей необходимостью вручную удалять файлы блокировки, потому что я разбился.

Это было полезно?

Решение

Существует несколько распространенных методов, в том числе использование семафоров. Наиболее часто используемым я вижу создание & Файла блокировки pid & Quot; при запуске, который содержит 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 больше не сможет успешно пытаться переписать этот файл из-за отсутствия разрешений на запись для пользователя /var/run/<appname>/. Кажется, очевидным решением является создание файла с правами на запись для всех. Это решение также основано на другом моем ответе , когда мне нужно было создать файл с такими пользовательскими разрешениями. Эта проблема важна в реальном мире, где ваша программа может запускаться любым пользователем, включая <=>.

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://stackoverflow.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)

Ограничением вышеприведенного кода является то, что если файл блокировки уже существовал с неожиданными разрешениями, эти разрешения не будут исправлены.

Мне бы хотелось использовать <=> в качестве каталога для файла блокировки, но для создания этого каталога требуются разрешения <=>. Вы можете сами решить, какой каталог использовать.

Обратите внимание, что нет необходимости открывать дескриптор файла для файла блокировки.

Вот решение на основе 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)

Найдите модуль 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

Набор функций, определенных в semaphore.h - sem_open(), sem_trywait() и т. д. - я полагаю, это эквивалент POSIX.

Если вы создадите файл блокировки и поместите в него pid, вы можете проверить его идентификатор процесса и сказать, не произошел ли сбой, нет?

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

Можете ли вы использовать утилиту 'pidof'? Если ваше приложение запущено, pidof запишет идентификатор процесса вашего приложения в стандартный вывод. Если нет, он напечатает символ новой строки (LF) и вернет код ошибки.

Пример (от bash, для простоты):

linux# pidof myapp
8947
linux# pidof nonexistent_app

linux#

Безусловно, самый распространенный метод - поместить файл в / var / run / с именем [application] .pid, который содержит только PID запущенного процесса или родительского процесса. В качестве альтернативы вы можете создать именованный канал в том же каталоге, чтобы иметь возможность отправлять сообщения активному процессу, например, открыть новый файл.

Я создал базовую платформу для запуска приложений такого типа, когда вы хотите иметь возможность передавать аргументы командной строки последующих попыток экземпляров в первый. Экземпляр начнет прослушивание на заранее определенном порту, если он не найдет экземпляр, уже прослушивающий его. Если экземпляр уже существует, он отправляет свои аргументы командной строки через сокет и завершает работу.

код с объяснением

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top