From 98fa70914199a44a0911172823b8cffad8e15871 Mon Sep 17 00:00:00 2001
From: Paul Kim <mok03127@gmail.com>
Date: Fri, 4 Sep 2026 08:29:54 +0900
Subject: [PATCH v2 2/2] Add a TAP test for the WAL insertion clamp in
 XLogBackgroundFlush

The new test_walwriter module stores a position where nothing has been
inserted in asyncXactLSN: it requests a segment switch and takes the
resulting insert position, which lies past the new segment's long page
header, beyond the end of generated WAL.  The walwriter's next cycle
then requests a flush past the end of generated WAL.

Without the previous commit, the walwriter fails on the "xlog write
request is past end of log" check in XLogWrite() and its crash takes
the server down, in both assert and production builds.  With it, the
request is clamped, the existing diagnostic LOG message is emitted,
and normal WAL activity proceeds afterwards.
---
 src/test/modules/Makefile                     |  1 +
 src/test/modules/meson.build                  |  1 +
 src/test/modules/test_walwriter/.gitignore    |  4 ++
 src/test/modules/test_walwriter/Makefile      | 23 +++++++
 src/test/modules/test_walwriter/meson.build   | 33 ++++++++++
 .../test_walwriter/t/001_bogus_async_lsn.pl   | 64 +++++++++++++++++++
 .../test_walwriter/test_walwriter--1.0.sql    |  8 +++
 .../modules/test_walwriter/test_walwriter.c   | 46 +++++++++++++
 .../test_walwriter/test_walwriter.control     |  4 ++
 9 files changed, 184 insertions(+)
 create mode 100644 src/test/modules/test_walwriter/.gitignore
 create mode 100644 src/test/modules/test_walwriter/Makefile
 create mode 100644 src/test/modules/test_walwriter/meson.build
 create mode 100644 src/test/modules/test_walwriter/t/001_bogus_async_lsn.pl
 create mode 100644 src/test/modules/test_walwriter/test_walwriter--1.0.sql
 create mode 100644 src/test/modules/test_walwriter/test_walwriter.c
 create mode 100644 src/test/modules/test_walwriter/test_walwriter.control

diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index bb88b3058ed..ac949be4695 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -54,6 +54,7 @@ SUBDIRS = \
 		  test_slru \
 		  test_tidstore \
 		  test_wait_lsn \
+		  test_walwriter \
 		  unsafe_tests \
 		  worker_spi \
 		  xid_wraparound
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index ce09e00531d..473d1a7bf87 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -55,6 +55,7 @@ subdir('test_shm_mq')
 subdir('test_slru')
 subdir('test_tidstore')
 subdir('test_wait_lsn')
+subdir('test_walwriter')
 subdir('typcache')
 subdir('unsafe_tests')
 subdir('worker_spi')
diff --git a/src/test/modules/test_walwriter/.gitignore b/src/test/modules/test_walwriter/.gitignore
new file mode 100644
index 00000000000..5dcb3ff9723
--- /dev/null
+++ b/src/test/modules/test_walwriter/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_walwriter/Makefile b/src/test/modules/test_walwriter/Makefile
new file mode 100644
index 00000000000..aa55d2cd8b5
--- /dev/null
+++ b/src/test/modules/test_walwriter/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_walwriter/Makefile
+
+MODULE_big = test_walwriter
+OBJS = \
+	$(WIN32RES) \
+	test_walwriter.o
+PGFILEDESC = "test_walwriter - test facilities for the WAL writer"
+
+EXTENSION = test_walwriter
+DATA = test_walwriter--1.0.sql
+
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_walwriter
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_walwriter/meson.build b/src/test/modules/test_walwriter/meson.build
new file mode 100644
index 00000000000..9c2afc4cb6c
--- /dev/null
+++ b/src/test/modules/test_walwriter/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+test_walwriter_sources = files(
+  'test_walwriter.c',
+)
+
+if host_system == 'windows'
+  test_walwriter_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_walwriter',
+    '--FILEDESC', 'test_walwriter - test facilities for the WAL writer',])
+endif
+
+test_walwriter = shared_module('test_walwriter',
+  test_walwriter_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += test_walwriter
+
+test_install_data += files(
+  'test_walwriter.control',
+  'test_walwriter--1.0.sql',
+)
+
+tests += {
+  'name': 'test_walwriter',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'tap': {
+    'tests': [
+      't/001_bogus_async_lsn.pl',
+    ],
+  },
+}
diff --git a/src/test/modules/test_walwriter/t/001_bogus_async_lsn.pl b/src/test/modules/test_walwriter/t/001_bogus_async_lsn.pl
new file mode 100644
index 00000000000..ca0100dea37
--- /dev/null
+++ b/src/test/modules/test_walwriter/t/001_bogus_async_lsn.pl
@@ -0,0 +1,64 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test that XLogBackgroundFlush() honors the clamp applied by
+# WaitXLogInsertionsToFinish() when the flush request is past the end
+# of generated WAL.  Without the fix, the walwriter fails on one of
+# XLogWrite()'s sanity checks (which one fires first depends on the
+# WAL buffer state) and takes the server down, in both assert and
+# production builds.
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('primary');
+$node->init;
+$node->append_conf(
+	'postgresql.conf', qq(
+autovacuum = off
+wal_writer_delay = 10ms
+wal_writer_flush_after = 0
+));
+$node->start;
+
+$node->safe_psql('postgres', 'CREATE EXTENSION test_walwriter');
+
+# Create a table for the later INSERT; this also guarantees that the
+# segment switch performed by the injection is not a no-op.
+$node->safe_psql('postgres', 'CREATE TABLE t AS SELECT 1 AS i');
+
+my $log_offset = -s $node->logfile;
+
+# Store a position where nothing has been inserted in asyncXactLSN.
+my $injected = $node->safe_psql('postgres',
+	'SELECT test_walwriter_bogus_async_lsn()');
+
+# The walwriter's next cycle picks up the bogus request and logs the
+# clamp.
+$node->wait_for_log(qr/request to flush past end of generated WAL/,
+	$log_offset);
+
+# The advertised flush position must not include the bogus request.
+my $result = $node->safe_psql('postgres',
+	qq{SELECT pg_current_wal_flush_lsn() < '$injected'::pg_lsn});
+is($result, 't', 'flush position stays below the bogus request');
+
+# The walwriter must not have failed one of XLogWrite()'s sanity
+# checks: no child process may have been terminated.
+my $log = slurp_file($node->logfile, $log_offset);
+unlike(
+	$log,
+	qr/terminating any other active server processes/,
+	'no crash after the bogus flush request');
+
+# Normal WAL activity gets past the bogus position.
+$node->safe_psql('postgres', 'INSERT INTO t VALUES (2)');
+$node->safe_psql('postgres', 'SELECT pg_switch_wal()');
+$result = $node->safe_psql('postgres',
+	qq{SELECT pg_current_wal_flush_lsn() > '$injected'::pg_lsn});
+is($result, 't', 'flush position advances past the bogus request');
+
+$node->stop;
+done_testing();
diff --git a/src/test/modules/test_walwriter/test_walwriter--1.0.sql b/src/test/modules/test_walwriter/test_walwriter--1.0.sql
new file mode 100644
index 00000000000..dd9c3e142ce
--- /dev/null
+++ b/src/test/modules/test_walwriter/test_walwriter--1.0.sql
@@ -0,0 +1,8 @@
+/* src/test/modules/test_walwriter/test_walwriter--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_walwriter" to load this file. \quit
+
+CREATE FUNCTION test_walwriter_bogus_async_lsn()
+RETURNS pg_lsn
+AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_walwriter/test_walwriter.c b/src/test/modules/test_walwriter/test_walwriter.c
new file mode 100644
index 00000000000..4360083fff0
--- /dev/null
+++ b/src/test/modules/test_walwriter/test_walwriter.c
@@ -0,0 +1,46 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_walwriter.c
+ *		Test facilities for the WAL writer.
+ *
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_walwriter/test_walwriter.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/xlog.h"
+#include "access/xlog_internal.h"
+#include "fmgr.h"
+#include "utils/pg_lsn.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(test_walwriter_bogus_async_lsn);
+
+/*
+ * Store a position where nothing has been inserted in asyncXactLSN, and
+ * return it.
+ *
+ * Request a segment switch, then store the current insert position in
+ * asyncXactLSN.  After the switch that position is just past the new
+ * segment's long page header, beyond the end of generated WAL, so the
+ * walwriter's next cycle requests a flush past the end of generated WAL.
+ * (If the insert position was already at a segment boundary the switch is
+ * a no-op, but the position returned still lies past the end of reserved
+ * WAL, so the outcome is the same.)
+ */
+Datum
+test_walwriter_bogus_async_lsn(PG_FUNCTION_ARGS)
+{
+	XLogRecPtr	ptr;
+
+	(void) RequestXLogSwitch(false);
+	ptr = GetXLogInsertRecPtr();
+	XLogSetAsyncXactLSN(ptr);
+
+	PG_RETURN_LSN(ptr);
+}
diff --git a/src/test/modules/test_walwriter/test_walwriter.control b/src/test/modules/test_walwriter/test_walwriter.control
new file mode 100644
index 00000000000..a196863447c
--- /dev/null
+++ b/src/test/modules/test_walwriter/test_walwriter.control
@@ -0,0 +1,4 @@
+comment = 'Test code for the WAL writer'
+default_version = '1.0'
+module_pathname = '$libdir/test_walwriter'
+relocatable = true
-- 
2.50.1 (Apple Git-155)

