Re: BUG #18754: Logical replication cannot restart if client sent a copyDone

From: Kacper Kuras <kacperkuras(at)hotmail(dot)com>
To: vignesh C <vignesh21(at)gmail(dot)com>
Cc: "ua(dot)san(dot)alex(at)gmail(dot)com" <ua(dot)san(dot)alex(at)gmail(dot)com>, "pgsql-bugs(at)lists(dot)postgresql(dot)org" <pgsql-bugs(at)lists(dot)postgresql(dot)org>
Subject: Re: BUG #18754: Logical replication cannot restart if client sent a copyDone
Date: 2026-11-12 12:55:06
Message-ID: VI0P193MB31112F69B2FC45FDB8946F7EBF702@VI0P193MB3111.EURP193.PROD.OUTLOOK.COM
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

Hi,

Following up on this one with steps to reproduce it and a patch with a test. It still happens on current master.

> In logical replication, however, the process exits after
> streamingDoneReceiving is set to true (this happens when sending the
> PqMsg_CopyDone message from libpqrcv_endstreaming), so the issue will
> not happen in logical replication.

That holds for the apply worker, which exits after ending the stream.
The walsender itself goes back to command mode once both sides have
sent CopyDone, as the protocol docs describe, and tablesync relies on
that: it ends streaming and then drops its slot over the same
connection. What no in-core client does is start a logical stream on
a connection that has already streamed, and that is what the
reporter's client does.

On a connection with replication=database:

1. START_REPLICATION SLOT s LOGICAL 0/0. The server sends
CopyBothResponse and the changes. The client sends CopyDone, and
the server replies with CopyDone, CommandComplete and
ReadyForQuery.
2. START_REPLICATION SLOT s LOGICAL 0/0 again. The server sends
CopyBothResponse, then CommandComplete and ReadyForQuery right
away, with no data and no CopyDone.

A physical stream on the same connection before the logical one has
the same effect. Physical after logical works, since
StartReplication() resets both flags.

> ... or at least validate if setting
> 'streamingDoneReceiving' to false is sufficient.

It isn't. With only streamingDoneReceiving reset, the second stream
delivers data, but when the client sends CopyDone the server doesn't
send its own, because ProcessRepliesIfAny() only does that while
streamingDoneSending is false. The attached patch resets both flags
in StartLogicalReplication(), like StartReplication() does.

The patch adds a TAP test. psql can't do COPY BOTH, so the test talks
to the walsender over a raw socket through raw_connect(), as
src/test/postmaster/t/004_negotiate.pl does. Without the fix, the two
logical streams that follow an earlier stream fail; with it, all four
subtests pass. I ran it on Windows, over TCP and with
PG_TEST_USE_UNIX_SOCKETS=1.

The fix applies cleanly to all supported branches.

To be transparent: I wrote the patch and the test with the help of an
AI assistant. I reproduced the problem and ran the test with and
without the fix on a local build.

Regards,
Kacper Kuras

Attachment Content-Type Size
v1-0001-Reset-streaming-state-at-the-start-of-logical-rep.patch application/octet-stream 7.4 KB

In response to

Browse pgsql-bugs by date

  From Date Subject
Previous Message Konstantin Knizhnik 2026-11-12 12:49:12 Re: BUG #19519: REPACK can fail due to missing chunk for toast value