diff --git a/layerindex/utils.py b/layerindex/utils.py
index fd29de0..f10c501 100644
--- a/layerindex/utils.py
+++ b/layerindex/utils.py
@@ -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)
diff --git a/layerindex/views.py b/layerindex/views.py
index e7728ca..8584620 100644
--- a/layerindex/views.py
+++ b/layerindex/views.py
@@ -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
diff --git a/tests/test_update_button.py b/tests/test_update_button.py
index bb3b84a..cde153f 100644
--- a/tests/test_update_button.py
+++ b/tests/test_update_button.py
@@ -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
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 95b5e04..396d5ef 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -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('')
