Merge pull request #994 from minrk/consolidate_lock

consolidate lock file handling
This commit is contained in:
Erik Sundell
2024-09-17 00:55:46 +02:00
committed by GitHub
5 changed files with 46 additions and 67 deletions

View File

@@ -1,4 +1,3 @@
filelock
packaging packaging
pytest pytest
pytest-cov pytest-cov

View File

@@ -1,4 +1,3 @@
filelock
pytest pytest
pytest-cov pytest-cov
pytest-asyncio pytest-asyncio

View File

@@ -15,6 +15,7 @@ setup(
"jinja2", "jinja2",
"pluggy==1.*", "pluggy==1.*",
"backoff", "backoff",
"filelock",
"requests", "requests",
"bcrypt", "bcrypt",
"jupyterhub-traefik-proxy==1.*", "jupyterhub-traefik-proxy==1.*",

View File

@@ -18,9 +18,11 @@ import re
import sys import sys
import time import time
from collections.abc import Mapping, Sequence from collections.abc import Mapping, Sequence
from contextlib import contextmanager
from copy import deepcopy from copy import deepcopy
import requests import requests
from filelock import FileLock, Timeout
from .yaml import yaml from .yaml import yaml
@@ -32,6 +34,22 @@ CONFIG_DIR = os.path.join(INSTALL_PREFIX, "config")
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.yaml") CONFIG_FILE = os.path.join(CONFIG_DIR, "config.yaml")
@contextmanager
def config_file_lock(config_path, timeout=1):
"""Context manager to acquire the config file lock"""
lock_file = f"{config_path}.lock"
try:
with FileLock(lock_file).acquire(timeout=timeout):
yield
except Timeout:
print(
f"Another instance of tljh-config holds the lock {lock_file}.",
file=sys.stderr,
)
sys.exit(1)
def set_item_in_config(config, property_path, value): def set_item_in_config(config, property_path, value):
""" """
Set key at property_path to value in config & return new config. Set key at property_path to value in config & return new config.
@@ -169,9 +187,10 @@ def validate_config(config, validate):
print( print(
f"Config validation error: {e.message}.\n" f"Config validation error: {e.message}.\n"
"You can still apply this change without validation by re-running your command with the --no-validate flag.\n" "You can still apply this change without validation by re-running your command with the --no-validate flag.\n"
"If you think this validation error is incorrect, please report it to https://github.com/jupyterhub/the-littlest-jupyterhub/issues." "If you think this validation error is incorrect, please report it to https://github.com/jupyterhub/the-littlest-jupyterhub/issues.",
file=sys.stderr,
) )
exit() sys.exit(1)
def show_config(config_path): def show_config(config_path):
@@ -186,12 +205,7 @@ def set_config_value(config_path, key_path, value, validate=True):
""" """
Set key at key_path in config_path to value Set key at key_path in config_path to value
""" """
from filelock import FileLock, Timeout with config_file_lock(config_path):
lock_file = f"{config_path}.lock"
lock = FileLock(lock_file)
try:
with lock.acquire(timeout=1):
config = get_current_config(config_path) config = get_current_config(config_path)
config = set_item_in_config(config, key_path, value) config = set_item_in_config(config, key_path, value)
validate_config(config, validate) validate_config(config, validate)
@@ -199,21 +213,12 @@ def set_config_value(config_path, key_path, value, validate=True):
with open(config_path, "w") as f: with open(config_path, "w") as f:
yaml.dump(config, f) yaml.dump(config, f)
except Timeout:
print(f"Another instance of tljh-config holds the lock {lock_file}")
exit(1)
def unset_config_value(config_path, key_path, validate=True): def unset_config_value(config_path, key_path, validate=True):
""" """
Unset key at key_path in config_path Unset key at key_path in config_path
""" """
from filelock import FileLock, Timeout with config_file_lock(config_path):
lock_file = f"{config_path}.lock"
lock = FileLock(lock_file)
try:
with lock.acquire(timeout=1):
config = get_current_config(config_path) config = get_current_config(config_path)
config = unset_item_from_config(config, key_path) config = unset_item_from_config(config, key_path)
validate_config(config, validate) validate_config(config, validate)
@@ -221,21 +226,12 @@ def unset_config_value(config_path, key_path, validate=True):
with open(config_path, "w") as f: with open(config_path, "w") as f:
yaml.dump(config, f) yaml.dump(config, f)
except Timeout:
print(f"Another instance of tljh-config holds the lock {lock_file}")
exit(1)
def add_config_value(config_path, key_path, value, validate=True): def add_config_value(config_path, key_path, value, validate=True):
""" """
Add value to list at key_path Add value to list at key_path
""" """
from filelock import FileLock, Timeout with config_file_lock(config_path):
lock_file = f"{config_path}.lock"
lock = FileLock(lock_file)
try:
with lock.acquire(timeout=1):
config = get_current_config(config_path) config = get_current_config(config_path)
config = add_item_to_config(config, key_path, value) config = add_item_to_config(config, key_path, value)
validate_config(config, validate) validate_config(config, validate)
@@ -243,21 +239,12 @@ def add_config_value(config_path, key_path, value, validate=True):
with open(config_path, "w") as f: with open(config_path, "w") as f:
yaml.dump(config, f) yaml.dump(config, f)
except Timeout:
print(f"Another instance of tljh-config holds the lock {lock_file}")
exit(1)
def remove_config_value(config_path, key_path, value, validate=True): def remove_config_value(config_path, key_path, value, validate=True):
""" """
Remove value from list at key_path Remove value from list at key_path
""" """
from filelock import FileLock, Timeout with config_file_lock(config_path):
lock_file = f"{config_path}.lock"
lock = FileLock(lock_file)
try:
with lock.acquire(timeout=1):
config = get_current_config(config_path) config = get_current_config(config_path)
config = remove_item_from_config(config, key_path, value) config = remove_item_from_config(config, key_path, value)
validate_config(config, validate) validate_config(config, validate)
@@ -265,10 +252,6 @@ def remove_config_value(config_path, key_path, value, validate=True):
with open(config_path, "w") as f: with open(config_path, "w") as f:
yaml.dump(config, f) yaml.dump(config, f)
except Timeout:
print(f"Another instance of tljh-config holds the lock {lock_file}")
exit(1)
def get_current_config(config_path): def get_current_config(config_path):
""" """

View File

@@ -26,6 +26,3 @@ jupyterhub-idle-culler>=1.2.1,<2
# ref: https://github.com/jupyterhub/the-littlest-jupyterhub/issues/289 # ref: https://github.com/jupyterhub/the-littlest-jupyterhub/issues/289
# #
pycurl>=7.45.2,<8 pycurl>=7.45.2,<8
# filelock is used to help us do atomic operations on config file(s)
filelock>=3.15.4,<4