@@ -451,8 +451,7 @@ class ListHandler(logging.Handler):
def lock_file(fn, timeout=30, logger=None):
start = time.time()
- last = start
- counter = 1
+ last = None
while True:
lock = open(fn, 'w')
try:
@@ -463,11 +462,12 @@ def lock_file(fn, timeout=30, logger=None):
current = time.time()
if current - start > timeout:
return None
- # Print a message in every 5 seconds
- if logger and (current - last > 5):
+ # Say why we are waiting, then once a minute
+ if logger and (last is None or current - last >= 60):
last = current
- logger.info('Trying to get lock on %s (tried %s seconds) ...' % (fn, (5 * counter)))
- counter += 1
+ logger.info('Waiting for another update to finish (lock %s held, waited %d of up to %d seconds) ...'
+ % (fn, current - start, timeout))
+ time.sleep(1)
def unlock_file(lock):
fcntl.flock(lock, fcntl.LOCK_UN)
@@ -147,7 +147,9 @@ def update_layer_view(request, template_name, branch='master', slug=None):
from celery import uuid
- cmd = 'layerindex/update.py --layer %s --branch %s --update %%update%%;' % (layeritem.name, branch)
+ # Wait for an update that is already running (e.g. the regular full
+ # update, which can take hours) rather than failing after 30 seconds
+ cmd = 'layerindex/update.py --layer %s --branch %s --update %%update%% --timeout 43200;' % (layeritem.name, branch)
task_id = uuid()
# Create this here first, because inside the task we don't have all of the required info
@@ -40,6 +40,8 @@ def test_update_layer_creates_one_update(staff_client, monkeypatch):
assert Update.objects.filter(task_id=task_id).exists()
# update.py records its work in the view's Update rather than a new one
assert '--update %update%' in cmd
+ # and waits for an update that is already running
+ assert '--timeout 43200' in cmd
@pytest.mark.django_db
@@ -43,6 +43,30 @@ def test_is_deps_satisfied(fake_bb, req_ver, collections, expected):
assert utils.is_deps_satisfied('core', req_ver, collections) is expected
+def test_lock_file_waits_without_spinning(tmp_path, caplog):
+ # [YOCTO #15395] the Update Layer button waits for a running update
+ import fcntl
+ import logging
+ import threading
+ import time
+ fn = str(tmp_path / 'layerindex.lock')
+ held = open(fn, 'w')
+ fcntl.flock(held, fcntl.LOCK_EX)
+ logger = logging.getLogger('test')
+ with caplog.at_level(logging.INFO):
+ start, cpu = time.time(), time.process_time()
+ assert utils.lock_file(fn, timeout=2, logger=logger) is None
+ assert time.time() - start >= 2
+ assert time.process_time() - cpu < 0.5
+ assert len([r for r in caplog.records if 'Waiting for another update' in r.message]) == 1
+
+ threading.Timer(1, lambda: fcntl.flock(held, fcntl.LOCK_UN)).start()
+ lock = utils.lock_file(fn, timeout=10)
+ assert lock is not None
+ utils.unlock_file(lock)
+ held.close()
+
+
def make_layer(path):
(path / 'conf').mkdir(parents=True)
(path / 'conf' / 'layer.conf').write_text('')
When another update held the layer index lock, e.g. the regular full update, the update started by the Update Layer button gave up after 30 seconds. Wait up to 12 hours instead. While waiting, lock_file() retried without pausing, using a whole CPU, and logged every 5 seconds; have it pause between tries, and log once and then once a minute, so that the task page shows that the update is waiting for another one. [YOCTO #15395] AI-Generated: Claude Opus 5.5 Signed-off-by: Tim Orling <tim.orling@konsulko.com> --- This applies on top of "[PATCH 0/3] Fix the Update Layer button" (bug 15395), and on master-next. It is the part of the bug about waiting for a running update. Tested on a test instance with a Celery worker: with the layer index lock held for 100 seconds, the Update Layer button's task log showed "Waiting for another update to finish" at 0 and 60 seconds, and the update then ran and succeeded. Before, it failed with "Layer index lock timeout expired" after 30 seconds. layerindex/utils.py | 12 ++++++------ layerindex/views.py | 4 +++- tests/test_update_button.py | 2 ++ tests/test_utils.py | 24 ++++++++++++++++++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) base-commit: 08e58cdece30019a6f9e5d14af16ebe20afa6177 prerequisite-patch-id: 23026c7697b245494ae240bea1d68b9fcaa8ba63 prerequisite-patch-id: 26ce753e3fb271485dbcf24e2c2e518acb4bcb82 prerequisite-patch-id: 74a73c75f97102b56617334eff8faae8c94e24df