Re: pg_rewind: Skip vanished source files during traversal

From: Rui Zhao <zhaorui126(at)gmail(dot)com>
To: Ayush Tiwari <ayushtiwari(dot)slg01(at)gmail(dot)com>
Cc: Heikki Linnakangas <hlinnaka(at)iki(dot)fi>, PostgreSQL Hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: pg_rewind: Skip vanished source files during traversal
Date: 2026-08-09 16:45:40
Message-ID: CAHWVJhEDtKWpzJqbmnVS+15eWcJNnNX8iDWNwcXtOOkEGqsXOg@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi Ayush, Heikki,

I tested v2 on a real local-mode rewind -- primary, standby promoted, old
primary rewound from it -- forcing the branch with an LD_PRELOAD shim that
makes lstat() return ENOENT for one chosen name, since the readdir/lstat
race isn't reproducible by hand. Three things worth adding.

1. On master the consequence is worse than an uninitialized read: the entry
can be dropped silently.

The same injection in two places, on master:

a file in the source root
pg_rewind: error: could not open directory ".../vanishme_test":
Not a directory
exit 1

a file in source global/
pg_rewind: Done!
exit 0

The second one is the bad case. That file exists in the source, and a
control run without the injection copies it to the target; with the
injection it is simply absent from the rewound target and pg_rewind reports
success. Which of the two you get depends on whether the garbage st_mode
happens to satisfy S_ISREG/S_ISDIR/S_ISLNK -- in the first run it looked
like a directory, so recurse_dir() descended into a path that isn't there
and reported a problem unrelated to the real one. So this is not only
undefined in theory: one of its outcomes is an incomplete rewind that exits
0.

v2 gives the same message in both places, and exits 1:

pg_rewind: error: could not stat file ".../global/vanishme2": No
such file or directory

2. The premise behind the change is enforced, not just expected.

sanityChecks() already refuses to run unless the target -- and, for
--source-pgdata, the source -- has DB_SHUTDOWNED or DB_SHUTDOWNED_IN_RECOVERY
in its control file, and it runs well before either traversal. A running
source therefore cannot reach recurse_dir() at all, which is a bit stronger
than what the new comment claims, and might be worth pointing at from there.

The one gap is the one that check's own comment already admits -- "This
doesn't guard against someone starting the cluster concurrently" -- and that
is precisely the case where the new pg_fatal() fires. Failing loudly is the
right answer there.

3. The new error cannot leave a half-rewound target.

Both traversals run before anything is written to the target; the first
modification is remove_target() in the apply loop, with createBackupLabel()
after it. Confirmed rather than assumed: with the error injected I compared
the target against a copy taken before the run, and diff -r reports no
difference, so the rewind can just be retried.

make check in src/bin/pg_rewind passes with v2. For what it's worth,
nothing else in pg_rewind reads a struct stat after tolerating a failed
stat: the only other ENOENT is remove_target_file()'s missing_ok, which is
deliberate and doesn't inspect anything afterwards.

I found nothing to change in the code -- as far as I can tell v2 is ready to
commit. Two things for the committer rather than for the patch:

- The commit message explains the bug as "reading an uninitialized struct
stat", which undersells it. The observable outcome in my second run was a
rewind that exits 0 with a source file missing from the target. That seems
worth saying, not least because it bears on whether this is back-patch
material -- I don't have a strong view there, since the race needs
something removing files from a data directory pg_rewind has already
verified is shut down, which is not an everyday event.

- The new comment could point at sanityChecks() for its "never over a running
server" claim, since that is where it is actually enforced.

Regards,
Rui

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Christoph Berg 2026-08-09 17:25:26 Re: WAL compression setting after PostgreSQL LZ4 default change
Previous Message Ayush Tiwari 2026-08-09 16:30:08 Re: Error handling in after-startup shmem requests