From 477a834f801b6b7571ad0d7343cfb249c6a368a2 Mon Sep 17 00:00:00 2001 From: Andrey Borodin Date: Sun, 23 Aug 2026 12:15:15 +0300 Subject: [PATCH v2] Make test WAL archiving publish files atomically An interrupted archive command can leave a partial WAL segment visible under its final name. A restore command running against the same archive can then copy the segment before a later archive attempt replaces it, making recovery tests fail intermittently. Have PostgreSQL::Test::Cluster copy WAL files to per-node temporary paths and rename them into place only after copying completes. Document that archive commands with concurrent readers should likewise publish complete files atomically without unconditionally overwriting a pre-existing file. Discussion: https://postgr.es/m/8fd856ff-b37c-4731-a10b-202ef53a679d@gmail.com --- doc/src/sgml/backup.sgml | 16 ++++++++++++--- src/test/perl/PostgreSQL/Test/Cluster.pm | 25 ++++++++++++++++++++---- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml index 168444eccc5..e34ae12fa5d 100644 --- a/doc/src/sgml/backup.sgml +++ b/doc/src/sgml/backup.sgml @@ -632,9 +632,10 @@ archive_command = 'copy "%p" "C:\\server\\archivedir\\%f"' # Windows which will copy archivable WAL segments to the directory /mnt/server/archivedir. (This is an example, not a - recommendation, and might not work on all platforms.) After the - %p and %f parameters have been replaced, - the actual command executed might look like this: + recommendation, and might not work on all platforms. In particular, + concurrent readers can see a partially copied file.) After the + %p and %f parameters have been + replaced, the actual command executed might look like this: test ! -f /mnt/server/archivedir/00000001000000A900000065 && cp pg_wal/00000001000000A900000065 /mnt/server/archivedir/00000001000000A900000065 @@ -679,6 +680,15 @@ test ! -f /mnt/server/archivedir/00000001000000A900000065 && cp pg_wal/0 not reported in . + + If archive files can be read while archiving is in progress, an archive + command or library should make each file visible under its final name only + after it has been completely written. For example, it can write a + temporary file in the same file system and then publish the file + atomically. This must not unconditionally overwrite an existing file; + pre-existing files should be handled as described below. + + Archive commands and libraries should generally be designed to refuse to overwrite any pre-existing archive file. This is an important safety feature to diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm index 920d831be9e..87289f743f9 100644 --- a/src/test/perl/PostgreSQL/Test/Cluster.pm +++ b/src/test/perl/PostgreSQL/Test/Cluster.pm @@ -1541,10 +1541,27 @@ sub enable_archiving # first. Paths also need to be double-quoted to prevent failures where # the path contains spaces. $path =~ s{\\}{\\\\}g if ($PostgreSQL::Test::Utils::windows_os); - my $copy_command = - $PostgreSQL::Test::Utils::windows_os - ? qq{copy "%p" "$path\\\\%f"} - : qq{cp "%p" "$path/%f"}; + # Copy to a temporary file in the archive directory, then publish it under + # the name expected by restore_command. In particular, don't leave a + # partially-copied file visible if the archive command is interrupted. + # A test process's PID distinguishes concurrent tests, while the port + # distinguishes nodes belonging to the same test. Since each node runs + # archive commands serially, retries can safely reuse the temporary path. + my $temp_suffix = "$$." . $self->port; + my $copy_command; + + if ($PostgreSQL::Test::Utils::windows_os) + { + my $temp_path = "$path\\\\.%f.tmp.$temp_suffix"; + $copy_command = qq{copy /Y "%p" "$temp_path"} + . qq{ && move /Y "$temp_path" "$path\\\\%f"}; + } + else + { + my $temp_path = "$path/.%f.tmp.$temp_suffix"; + $copy_command = qq{cp "%p" "$temp_path"} + . qq{ && mv -f "$temp_path" "$path/%f"}; + } # Enable archive_mode and archive_command on node $self->append_conf( -- That's all, folks. May the source be with you.