Skip to content

Commit 5c8cb5b

Browse files
authored
Merge pull request #976 from jrdnbradford/add-conf-lockfile
Add TLJH config lockfile
2 parents 5278351 + 8490ef2 commit 5c8cb5b

File tree

4 files changed

+72
-42
lines changed

4 files changed

+72
-42
lines changed

dev-requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
filelock
12
packaging
23
pytest
34
pytest-cov

integration-tests/requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
filelock
12
pytest
23
pytest-cov
34
pytest-asyncio

tljh/config.py

+67-42
Original file line numberDiff line numberDiff line change
@@ -178,88 +178,113 @@ def show_config(config_path):
178178
"""
179179
Pretty print config from given config_path
180180
"""
181-
try:
182-
with open(config_path) as f:
183-
config = yaml.load(f)
184-
except FileNotFoundError:
185-
config = {}
186-
181+
config = get_current_config(config_path)
187182
yaml.dump(config, sys.stdout)
188183

189184

190185
def set_config_value(config_path, key_path, value, validate=True):
191186
"""
192187
Set key at key_path in config_path to value
193188
"""
194-
# FIXME: Have a file lock here
189+
from filelock import FileLock, Timeout
190+
191+
lock_file = f"{config_path}.lock"
192+
lock = FileLock(lock_file)
195193
try:
196-
with open(config_path) as f:
197-
config = yaml.load(f)
198-
except FileNotFoundError:
199-
config = {}
200-
config = set_item_in_config(config, key_path, value)
194+
with lock.acquire(timeout=1):
195+
config = get_current_config(config_path)
196+
config = set_item_in_config(config, key_path, value)
197+
validate_config(config, validate)
201198

202-
validate_config(config, validate)
199+
with open(config_path, "w") as f:
200+
yaml.dump(config, f)
203201

204-
with open(config_path, "w") as f:
205-
yaml.dump(config, f)
202+
except Timeout:
203+
print(f"Another instance of tljh-config holds the lock {lock_file}")
204+
exit(1)
206205

207206

208207
def unset_config_value(config_path, key_path, validate=True):
209208
"""
210209
Unset key at key_path in config_path
211210
"""
212-
# FIXME: Have a file lock here
211+
from filelock import FileLock, Timeout
212+
213+
lock_file = f"{config_path}.lock"
214+
lock = FileLock(lock_file)
213215
try:
214-
with open(config_path) as f:
215-
config = yaml.load(f)
216-
except FileNotFoundError:
217-
config = {}
216+
with lock.acquire(timeout=1):
217+
config = get_current_config(config_path)
218+
config = unset_item_from_config(config, key_path)
219+
validate_config(config, validate)
218220

219-
config = unset_item_from_config(config, key_path)
220-
validate_config(config, validate)
221+
with open(config_path, "w") as f:
222+
yaml.dump(config, f)
221223

222-
with open(config_path, "w") as f:
223-
yaml.dump(config, f)
224+
except Timeout:
225+
print(f"Another instance of tljh-config holds the lock {lock_file}")
226+
exit(1)
224227

225228

226229
def add_config_value(config_path, key_path, value, validate=True):
227230
"""
228231
Add value to list at key_path
229232
"""
230-
# FIXME: Have a file lock here
233+
from filelock import FileLock, Timeout
234+
235+
lock_file = f"{config_path}.lock"
236+
lock = FileLock(lock_file)
231237
try:
232-
with open(config_path) as f:
233-
config = yaml.load(f)
234-
except FileNotFoundError:
235-
config = {}
238+
with lock.acquire(timeout=1):
239+
config = get_current_config(config_path)
240+
config = add_item_to_config(config, key_path, value)
241+
validate_config(config, validate)
236242

237-
config = add_item_to_config(config, key_path, value)
238-
validate_config(config, validate)
243+
with open(config_path, "w") as f:
244+
yaml.dump(config, f)
239245

240-
with open(config_path, "w") as f:
241-
yaml.dump(config, f)
246+
except Timeout:
247+
print(f"Another instance of tljh-config holds the lock {lock_file}")
248+
exit(1)
242249

243250

244251
def remove_config_value(config_path, key_path, value, validate=True):
245252
"""
246253
Remove value from list at key_path
247254
"""
248-
# FIXME: Have a file lock here
255+
from filelock import FileLock, Timeout
256+
257+
lock_file = f"{config_path}.lock"
258+
lock = FileLock(lock_file)
249259
try:
250-
with open(config_path) as f:
251-
config = yaml.load(f)
252-
except FileNotFoundError:
253-
config = {}
260+
with lock.acquire(timeout=1):
261+
config = get_current_config(config_path)
262+
config = remove_item_from_config(config, key_path, value)
263+
validate_config(config, validate)
254264

255-
config = remove_item_from_config(config, key_path, value)
256-
validate_config(config, validate)
265+
with open(config_path, "w") as f:
266+
yaml.dump(config, f)
257267

258-
with open(config_path, "w") as f:
259-
yaml.dump(config, f)
268+
except Timeout:
269+
print(f"Another instance of tljh-config holds the lock {lock_file}")
270+
exit(1)
271+
272+
273+
def get_current_config(config_path):
274+
"""
275+
Retrieve the current config at config_path
276+
"""
277+
try:
278+
with open(config_path) as f:
279+
return yaml.load(f)
280+
except FileNotFoundError:
281+
return {}
260282

261283

262284
def check_hub_ready():
285+
"""
286+
Checks that hub is running.
287+
"""
263288
from .configurer import load_config
264289

265290
base_url = load_config()["base_url"]

tljh/requirements-hub-env.txt

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

0 commit comments

Comments
 (0)