>From 23472c578356f8ac5a2858ebaa7287bc24c3a5b0 Mon Sep 17 00:00:00 2001
From: Sven Göthel <[email protected]>
Date: Sun, 19 Jul 2026 16:47:26 +0200
Subject: Add cfg cache-lock-fail behavior if failing to acquire cache-file
 lock

New default is http-response 503 and simply serve an error page
to avoid server lockup.

cache-lock-fail can be configured to 200 for old behavior,
i.e. producing an off-cache content page.

diff --git a/cache.c b/cache.c
index ca22194..10e5db5 100644
--- a/cache.c
+++ b/cache.c
@@ -16,6 +16,7 @@
 #include "cgit.h"
 #include "cache.h"
 #include "html.h"
+#include "ui-shared.h"
 #include <stdio.h>
 #include <fcntl.h>
 #include <time.h>
@@ -290,10 +291,20 @@ static int lock_slot(struct cache_slot *slot, time_t 
tStart)
        };
        size_t wait_count = 0;
 
-       slot->lock_fd = open(slot->lock_name, O_RDWR | O_CREAT,
-                            S_IRUSR | S_IWUSR);
-       if (slot->lock_fd == -1)
-               return errno;
+       if (0 == strncmp("cgit_test_key_no_lock", slot->key, 21)) {
+               cache_log("[cgit] Lock (%ds): Test-Key: %s -> forced fail\n",
+                         (int)(time(NULL) - tStart), slot->key);
+               return ENOENT;
+       }
+       slot->lock_fd =
+           open(slot->lock_name, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+       if (slot->lock_fd == -1) {
+               int saved_errno = errno;
+               cache_log("[cgit] Lock (%ds): Unable to open/create lock slot 
%s (%s): %s (%d)\n",
+                         (int)(time(NULL) - tStart), slot->lock_name,
+                         slot->key, strerror(saved_errno), saved_errno);
+               return saved_errno;
+       }
        while (fcntl(slot->lock_fd, F_SETLK, &lock) < 0) {
                int saved_errno = errno;
                time_t tNow = time(NULL);
@@ -489,20 +500,34 @@ static int process_slot(struct cache_slot *slot)
        close_slot(slot);
        if ((err = lock_slot(slot, tStart)) != 0) {
                time_t tNow1 = time(NULL);
-               slot->fn();
-               cache_log("[cgit] Unable to lock new slot (%ds, %ds) %s (%s): 
%s (%d)\n",
-                   (int)(tNow1 - tStart), (int)(time(NULL) - tStart),
-                   slot->lock_name, slot->key, strerror(err), err);
+               if (ctx.cfg.cache_lock_fail != 200) {
+                       ctx.page.title = "cgit error";
+                       cgit_print_error_page(503, "Service Unavailable",
+                           "Service Unavailable. Cache: Could not lock 
new-slot within %ds.",
+                           (int)(tNow1 - tStart));
+               } else {
+                       slot->fn();
+               }
+               cache_log("[cgit] Unable to lock new slot (%ds, %ds) %s (%s): 
%d: %s (%d)\n",
+                         (int)(tNow1 - tStart), (int)(time(NULL) - tStart),
+                         slot->lock_name, slot->key, ctx.cfg.cache_lock_fail, 
strerror(err), err);
                return 0;
        }
        if (slot->cache_fd <= 0) {
                // first concurrent process lock
                if ((err = fill_slot(slot)) != 0) {
-                       cache_log("[cgit] Unable to fill slot %s (%s): %s 
(%d)\n",
-                           slot->lock_name, slot->key, strerror(err), err);
+                       cache_log("[cgit] Unable to fill slot %s (%s): %d: %s 
(%d)\n",
+                           slot->lock_name, slot->key, 
ctx.cfg.cache_lock_fail, strerror(err), err);
                        unlock_slot(slot, 0);
                        close_lock(slot);
-                       slot->fn();
+                       if (ctx.cfg.cache_lock_fail != 200) {
+                               ctx.page.title = "cgit error";
+                               cgit_print_error_page(503, "Service 
Unavailable",
+                                   "Service Unavailable. Cache: Could not fill 
slot within %ds.",
+                                   (int)(time(NULL) - tStart));
+                       } else {
+                               slot->fn();
+                       }
                        return 0;
                }
                // We've got a valid cache slot in the lock file, which
diff --git a/cgit.c b/cgit.c
index 25e29f9..83665ca 100644
--- a/cgit.c
+++ b/cgit.c
@@ -217,6 +217,8 @@ static void config_cb(const char *name, const char *value)
                ctx.cfg.cache_scanrc_ttl = atoi(value);
        else if (!strcmp(name, "cache-static-ttl"))
                ctx.cfg.cache_static_ttl = atoi(value);
+       else if (!strcmp(name, "cache-lock-fail"))
+               ctx.cfg.cache_lock_fail = atoi(value);
        else if (!strcmp(name, "cache-lock-timeout"))
                ctx.cfg.cache_lock_timeout = atoi(value);
        else if (!strcmp(name, "client-io-idle-timeout"))
@@ -390,6 +392,7 @@ static void prepare_context(void)
        ctx.cfg.cache_scanrc_ttl = 15;
        ctx.cfg.cache_dynamic_ttl = 5;
        ctx.cfg.cache_static_ttl = -1;
+       ctx.cfg.cache_lock_fail = 503;
        ctx.cfg.cache_lock_timeout = 10;
        ctx.cfg.client_io_idle_timeout = 20;
        ctx.cfg.client_io_min_rate = 500;
@@ -890,6 +893,7 @@ static void print_config(FILE *f, const char *prefix)
        fprintf(f, "%slog-level=%d\n", prefix, ctx.cfg.log_level);
        fprintf(f, "%sproject-list=%s\n", prefix, ctx.cfg.project_list);
        fprintf(f, "%sscan-path=%s\n", prefix, ctx.cfg.scan_path);
+       fprintf(f, "%scache-lock-fail=%d\n", prefix, ctx.cfg.cache_lock_fail);
        fprintf(f, "%scache-lock-timeout=%d\n", prefix, 
ctx.cfg.cache_lock_timeout);
        fprintf(f, "%sclient-io-idle-timeout=%d\n", prefix, 
ctx.cfg.client_io_idle_timeout);
        fprintf(f, "%sclient-io-min-rate=%ld\n", prefix, 
ctx.cfg.client_io_min_rate);
diff --git a/cgit.h b/cgit.h
index 4aed96c..df6a6c1 100644
--- a/cgit.h
+++ b/cgit.h
@@ -229,6 +229,8 @@ struct cgit_config {
        int cache_static_ttl;
        int cache_about_ttl;
        int cache_snapshot_ttl;
+       /* cache lock fail action as http-response code. 200 returns un-cached 
processed content (old behavior), otherwise an error page is served (new 
default). Defaults to 503. */
+       int cache_lock_fail;
        /* cache lock timeout in seconds to acquire the cache lock-file against 
concurrent processes. Defaults to 10s. */
        int cache_lock_timeout;
        /* idle timeout in seconds between sending/receiving chunks of the 
cached body to/from the client. Defaults to 20s. */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index d3a8ae8..de25244 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -100,6 +100,11 @@ cache-static-ttl::
        version of repository pages accessed with a fixed SHA1. See also:
        "CACHE". Default value: -1".
 
+cache-lock-fail::
+       Cache lock fail action as http-response code.
+       200 returns un-cached processed content (old behavior),
+       otherwise an error page is served (new default). Default value: "503".
+
 cache-lock-timeout::
        Timeout in seconds to acquire the cache lock-file
        against concurrent processes. Default value: "10".

Reply via email to