Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add process locking #851

Merged
merged 1 commit into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions leapp/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import pkgutil
import socket
import sys
import textwrap

from leapp import VERSION
from leapp.cli import commands
from leapp.exceptions import UnknownCommandError
from leapp.exceptions import UnknownCommandError, ProcessLockError
from leapp.utils.clicmd import command
from leapp.utils.lock import leapp_lock


@command('')
Expand Down Expand Up @@ -42,7 +42,8 @@ def main():
os.environ['LEAPP_HOSTNAME'] = socket.getfqdn()
_load_commands(cli.command)
try:
cli.command.execute('leapp version {}'.format(VERSION))
with leapp_lock():
cli.command.execute('leapp version {}'.format(VERSION))
except UnknownCommandError as e:
bad_cmd = (
"Command \"{CMD}\" is unknown.\nMost likely there is a typo in the command or particular "
Expand All @@ -54,3 +55,6 @@ def main():
bad_cmd = "No such argument {CMD}"
print(bad_cmd.format(CMD=e.requested))
sys.exit(1)
except ProcessLockError as e:
sys.stderr.write('{}\nAborting.\n'.format(e.message))
sys.exit(1)
3 changes: 3 additions & 0 deletions leapp/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
'dir': '/var/log/leapp/',
'files': ','.join(_FILES_TO_ARCHIVE),
},
'lock': {
'path': '/var/run/leapp.pid'
},
abadger marked this conversation as resolved.
Show resolved Hide resolved
'logs': {
'dir': '/var/log/leapp/',
'files': ','.join(_LOGS),
Expand Down
4 changes: 4 additions & 0 deletions leapp/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,7 @@ class RequestStopAfterPhase(LeappError):

def __init__(self):
super(RequestStopAfterPhase, self).__init__('Stop after phase has been requested.')


class ProcessLockError(LeappError):
dkubek marked this conversation as resolved.
Show resolved Hide resolved
""" This exception is used to represent an error within the process locking mechanism. """
83 changes: 83 additions & 0 deletions leapp/utils/lock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import os
import fcntl
import logging

from leapp.config import get_config
from leapp.exceptions import ProcessLockError


def leapp_lock(lockfile=None):
return ProcessLock(lockfile=lockfile)


def _acquire_lock(fd):
try:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
return True
except OSError:
return False


def _clear_lock(fd):
os.lseek(fd, 0, os.SEEK_SET)
os.ftruncate(fd, 0)


def _read_pid(fd):
return os.read(fd, 20)


def _write_pid(fd, pid):
_clear_lock(fd)
os.write(fd, str(pid).encode('utf-8'))
dkubek marked this conversation as resolved.
Show resolved Hide resolved


class ProcessLock(object):

def __init__(self, lockfile=None):
self.log = logging.getLogger('leapp.utils.lock')
self.lockfile = lockfile if lockfile else get_config().get('lock', 'path')

self.fd = None

def _get_pid_from_lockfile(self):
running_pid = _read_pid(self.fd)
self.log.debug("_get_pid_from_lockfile: running_pid=%s", running_pid)
running_pid = int(running_pid)

return running_pid

def _try_lock(self, pid):
if not _acquire_lock(self.fd):
try:
running_pid = self._get_pid_from_lockfile()
except ValueError:
process_msg = ''
else:
process_msg = ' by process with PID {}'.format(running_pid)

msg = (
'Leapp is currently locked{} and cannot be started.\n'
'Please ensure no other instance of leapp is running and then delete the lockfile at {} and try again.'
).format(process_msg, self.lockfile)
raise ProcessLockError(msg)

try:
_write_pid(self.fd, pid)
except OSError:
raise ProcessLockError('Could not write PID to lockfile.')
pirat89 marked this conversation as resolved.
Show resolved Hide resolved

def __enter__(self):
my_pid = os.getpid()

self.fd = os.open(self.lockfile, os.O_CREAT | os.O_RDWR, 0o600)
try:
self._try_lock(my_pid)
except ProcessLockError:
os.close(self.fd)
raise

def __exit__(self, *exc_args):
_clear_lock(self.fd)
os.close(self.fd)
os.unlink(self.lockfile)
Loading