[PATCH 4/4] Add cfg cache-lock-fail behavior if failing to acquire cache-file, lock

Sven Göthel sgothel at jausoft.com
Sun Jul 19 17:07:33 UTC 2026


>From 23472c578356f8ac5a2858ebaa7287bc24c3a5b0 Mon Sep 17 00:00:00 2001
From: Sven Göthel <sgothel at jausoft.com>
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".


More information about the CGit mailing list