[PATCH 2/2] tests: exercise the lock_slot() rename race directly

Konstantin Ryabitsev konstantin at linuxfoundation.org
Tue Aug 25 03:34:43 UTC 2026


The preceding commit is covered only indirectly: the test plants a stale
slot rather than reproducing the interleaving that produces one. The
race needs a rename() to land between cgit's own open() and its
F_SETLK, which a test cannot arrange from the outside.

Add a CGIT_TEST_LOCK_DELAY hook that sleeps in that window, in the
spirit of git's own GIT_TEST_* variables, and a test that uses it to
rename the lock file over the cache slot at exactly the wrong moment.
The test asserts that the slot still holds what the previous occupant
put there, which fails without the inode check and passes with it.

The test costs two seconds of wall clock and is the only timing
dependent test in the suite. The margin is large relative to the work
cgit does in that window, but it is a wall clock assumption rather than
a guarantee.

Assisted-by: LLM [analysis, codegen, tests]
Signed-off-by: Konstantin Ryabitsev <konstantin at linuxfoundation.org>
---
 cache.c                         |  6 ++++++
 tests/t0021-cache-slot-reuse.sh | 20 ++++++++++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/cache.c b/cache.c
index ebc7de2..ea28023 100644
--- a/cache.c
+++ b/cache.c
@@ -182,12 +182,18 @@ static int lock_slot(struct cache_slot *slot)
 		.l_len = 0,
 	};
 	struct stat st_fd, st_name;
+	const char *delay;
 	int err;
 
 	slot->lock_fd = open(slot->lock_name, O_RDWR | O_CREAT,
 			     S_IRUSR | S_IWUSR);
 	if (slot->lock_fd == -1)
 		return errno;
+	/* Test hook: widen the window between open() and F_SETLK so that the
+	 * test suite can rename the lock file away underneath us on purpose.
+	 */
+	if ((delay = getenv("CGIT_TEST_LOCK_DELAY")))
+		sleep(atoi(delay));
 	if (fcntl(slot->lock_fd, F_SETLK, &lock) < 0) {
 		int saved_errno = errno;
 		close(slot->lock_fd);
diff --git a/tests/t0021-cache-slot-reuse.sh b/tests/t0021-cache-slot-reuse.sh
index e6dd374..ed25197 100755
--- a/tests/t0021-cache-slot-reuse.sh
+++ b/tests/t0021-cache-slot-reuse.sh
@@ -43,4 +43,24 @@ test_expect_success 'no stale content is left behind in the slot' '
 	test "$(tail -n 1 output.cached)" = "</html>"
 '
 
+# The lock file can be renamed over the cache slot by the process that
+# holds it in the window between our open() and our F_SETLK. The lock we
+# then acquire is on the live cache file, and filling it clobbers a slot
+# other processes are streaming. CGIT_TEST_LOCK_DELAY widens that window
+# so we can perform the rename at exactly the wrong moment.
+rename_lock_during_fill() {
+	CGIT_TEST_LOCK_DELAY=2 cgit_url "foo/refs" >output.race &
+	cgit_pid=$!
+	sleep 1
+	mv "$lock" "$slot"
+	wait $cgit_pid
+}
+
+test_expect_success 'a slot renamed away mid-lock is left alone' '
+	rm -f cache/* &&
+	plant_junk "$lock" &&
+	rename_lock_during_fill &&
+	grep -q "$junk" "$slot"
+'
+
 test_done

-- 
2.55.0



More information about the CGit mailing list