Re: WAL segment file descriptor leak on read errors can PANIC the server

From: Sami Imseih <samimseih(dot)pg(at)gmail(dot)com>
To: Bharath Rupireddy <bharath(dot)rupireddyforpostgres(at)gmail(dot)com>
Cc: PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Subject: Re: WAL segment file descriptor leak on read errors can PANIC the server
Date: 2026-09-21 20:14:39
Message-ID: CAN12+YLw97nPz3kDph6C2_Y2UpFzjCJ8Hzh5f8gtoKVyZKNM+w@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

> A failed WAL read leaks the open WAL segment file descriptor until the
> backend exits. Once the fd limit is reached, all later queries in that
> backend fail with "Too many open files". With a connection pooler the
> leaks add up across clients. There is no warning or log message for
> this. Reproducers are at [1] and [2].

I searched the archives and did not find any earlier report of this. It
should be fixed regardless.

> I think the fix is to register a memory context reset callback on the
> context the reader is allocated in. [...] Doing this in
> XLogReaderAllocate() covers every caller, present and future, instead
> of adding an error handler to each one.

I agree with the proposed fix.

Using your patch, I put Assert(state->seg.ws_file == -1) before the
close in the new callback and ran check-world on a cassert build. It
fires in two distinct places.

One is the logical decoding case, that is, errors mid-decode which means
XLogReaderFree() is never reached.

The other is the physical walsender, which brings me to this:

> The walsender does not have this issue because it closes the file in
> its own error cleanup.

This is not true in the normal exit case. StartReplication() never
calls XLogReaderFree(), so on a clean CopyDone the reader is discarded
with the segment file still open.

This is easy to see by logging in the new callback:

```
if (state->seg.ws_file != -1)
{
elog(LOG, "closing leaked WAL fd %d", state->seg.ws_file);
state->routine.segment_close(state);
}
```

Starting a pg_receivewal and stopping it with Ctrl-C, which ends
streaming cleanly rather than with an error:

```
LOG: closing leaked WAL fd 9
STATEMENT: START_REPLICATION SLOT "s1" 0/04000000 TIMELINE 1
```

So a segment file is still open when the reader goes away, on a path
with no error involved at all. Without your patch nothing closes it.

In terms of the patch, I was thinking about this allocation:

```
> + state->reset_cb = palloc_extended(sizeof(MemoryContextCallback),
> + MCXT_ALLOC_NO_OOM | MCXT_ALLOC_ZERO);
```

At first, I thought palloc_object() would do and be simpler, but keeping
MCXT_ALLOC_NO_OOM gives the better message, since callers report
errdetail("Failed while allocating a WAL reading processor."), and
keeping this consistent with the other allocations is probably best.
So I agree with what you have.

I do not see anything else that stands out in the patch.

--
Sami Imseih
Amazon Web Services (AWS)

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Hannu Krosing 2026-09-21 20:21:03 Re: Direct TOAST v2, faster, smaller and no migration needed
Previous Message shihao zhong 2026-09-21 20:12:27 Re: Direct TOAST v2, faster, smaller and no migration needed