PHP 8.5.2
Preview: lock.py Size: 4.42 KB
//lib/python3/dist-packages/uaclient/lock.py

import logging
import os
import time
from typing import Tuple

from uaclient import exceptions, system, util
from uaclient.data_types import DataObject, Field, StringDataValue
from uaclient.files import notices
from uaclient.files.data_types import DataObjectFile, DataObjectFileFormat
from uaclient.files.files import UAFile
from uaclient.files.notices import Notice

LOG = logging.getLogger(util.replace_top_level_logger_name(__name__))


class LockData(DataObject):
    fields = [
        Field("lock_pid", StringDataValue),
        Field("lock_holder", StringDataValue),
    ]

    def __init__(self, lock_pid: str, lock_holder: str):
        self.lock_pid = lock_pid
        self.lock_holder = lock_holder


lock_data_file = DataObjectFile(
    LockData,
    UAFile("lock", private=False),
    DataObjectFileFormat.JSON,
)


def check_lock_info() -> Tuple[int, str]:
    """Return lock info if lock file is present the lock is active.

    If process claiming the lock is no longer present, remove the lock file
    and log a warning.

    :return: A tuple (pid, string describing lock holder)
        If no active lock, pid will be -1.
    """

    try:
        lock_data_obj = lock_data_file.read()
    except exceptions.InvalidFileFormatError:
        raise exceptions.InvalidLockFile(lock_file_path=lock_data_file.path)

    no_lock = (-1, "")
    if not lock_data_obj:
        return no_lock

    lock_pid = lock_data_obj.lock_pid
    lock_holder = lock_data_obj.lock_holder

    try:
        system.subp(["ps", lock_pid])
        return (int(lock_pid), lock_holder)
    except exceptions.ProcessExecutionError:
        if not util.we_are_currently_root():
            LOG.debug(
                "Found stale lock file previously held by %s:%s",
                lock_pid,
                lock_holder,
            )
            return (int(lock_pid), lock_holder)
        LOG.warning(
            "Removing stale lock file previously held by %s:%s",
            lock_pid,
            lock_holder,
        )
        system.ensure_file_absent(lock_data_file.path)
        return no_lock


def clear_lock_file_if_present():
    lock_data_file.delete()


class RetryLock:
    """
    Context manager for gaining exclusive access to the lock file.

    Create a lock file if absent. The lock file will contain a pid of the
    running process, and a customer-visible description of the lock holder.

    The RetryLock will try several times to acquire the lock before giving up.
    The number of times to try and how long to sleep in between tries is
    configurable.

    :param lock_holder: String with the service name or command which is
        holding the lock. This lock_holder string will be customer visible in
        status.json.
    :param sleep_time: Number of seconds to sleep before retrying if the lock
        is already held.
    :param max_retries: Maximum number of times to try to grab the lock before
        giving up and raising a LockHeldError.
    :raises: LockHeldError if lock is held after (sleep_time * max_retries)
    """

    def __init__(
        self,
        *_args,
        lock_holder: str,
        sleep_time: int = 10,
        max_retries: int = 12
    ):
        self.lock_holder = lock_holder
        self.sleep_time = sleep_time
        self.max_retries = max_retries

    def grab_lock(self):
        (lock_pid, cur_lock_holder) = check_lock_info()
        if lock_pid > 0:
            raise exceptions.LockHeldError(
                lock_request=self.lock_holder,
                lock_holder=cur_lock_holder,
                pid=lock_pid,
            )
        lock_data_file.write(
            LockData(lock_pid=str(os.getpid()), lock_holder=self.lock_holder)
        )
        notices.add(
            Notice.OPERATION_IN_PROGRESS,
            operation=self.lock_holder,
        )

    def __enter__(self):
        LOG.debug("spin lock starting for %s", self.lock_holder)
        tries = 0
        while True:
            try:
                self.grab_lock()
                break
            except exceptions.LockHeldError as e:
                LOG.debug(
                    "RetryLock Attempt %d. %s. Spinning...", tries + 1, e.msg
                )
                tries += 1
                if tries >= self.max_retries:
                    raise e
                else:
                    time.sleep(self.sleep_time)

    def __exit__(self, _exc_type, _exc_value, _traceback):
        lock_data_file.delete()
        notices.remove(Notice.OPERATION_IN_PROGRESS)

Directory Contents

Dirs: 10 × Files: 26

Name Size Perms Modified Actions
api DIR
- drwxr-xr-x 2026-01-08 12:55:17
Edit Download
cli DIR
- drwxr-xr-x 2026-01-08 12:55:17
Edit Download
clouds DIR
- drwxr-xr-x 2026-01-08 12:55:17
Edit Download
daemon DIR
- drwxr-xr-x 2026-01-08 12:55:17
Edit Download
- drwxr-xr-x 2026-01-08 12:55:17
Edit Download
files DIR
- drwxr-xr-x 2026-01-08 12:55:17
Edit Download
http DIR
- drwxr-xr-x 2026-01-08 12:55:17
Edit Download
messages DIR
- drwxr-xr-x 2026-01-08 12:55:17
Edit Download
timer DIR
- drwxr-xr-x 2026-01-08 12:55:17
Edit Download
- drwxr-xr-x 2026-01-08 12:55:18
Edit Download
14.63 KB lrw-r--r-- 2025-09-23 17:28:08
Edit Download
35.77 KB lrw-r--r-- 2025-09-23 17:27:20
Edit Download
8.39 KB lrw-r--r-- 2025-02-19 15:13:27
Edit Download
18.91 KB lrw-r--r-- 2025-02-19 15:13:27
Edit Download
35.88 KB lrw-r--r-- 2025-09-23 17:28:08
Edit Download
9.89 KB lrw-r--r-- 2025-02-19 13:15:01
Edit Download
13.21 KB lrw-r--r-- 2025-02-28 15:17:00
Edit Download
3.19 KB lrw-r--r-- 2025-04-10 13:49:28
Edit Download
8.06 KB lrw-r--r-- 2025-02-19 13:15:01
Edit Download
18.29 KB lrw-r--r-- 2025-09-23 17:28:08
Edit Download
836 B lrw-r--r-- 2025-02-19 13:15:01
Edit Download
12.85 KB lrw-r--r-- 2025-02-19 13:15:01
Edit Download
4.42 KB lrw-r--r-- 2025-02-19 13:15:01
Edit Download
4.90 KB lrw-r--r-- 2025-02-19 15:13:27
Edit Download
648 B lrw-r--r-- 2025-02-19 13:15:01
Edit Download
26.16 KB lrw-r--r-- 2025-02-19 15:13:27
Edit Download
6.26 KB lrw-r--r-- 2025-02-19 15:13:27
Edit Download
28.53 KB lrw-r--r-- 2025-02-19 15:13:27
Edit Download
28.12 KB lrw-r--r-- 2025-02-19 15:13:27
Edit Download
308 B lrw-r--r-- 2025-02-19 13:15:01
Edit Download
1.55 KB lrw-r--r-- 2025-09-23 17:27:20
Edit Download
3.22 KB lrw-r--r-- 2025-10-27 12:20:07
Edit Download
15.45 KB lrw-r--r-- 2025-02-19 13:15:01
Edit Download
2.63 KB lrw-r--r-- 2025-10-27 12:42:14
Edit Download
840 B lrw-r--r-- 2025-02-19 13:15:01
Edit Download
0 B lrw-r--r-- 2025-02-19 13:15:01
Edit Download

If ZipArchive is unavailable, a .tar will be created (no compression).