From 308c315e26b2c800f276749d9193ba3f7a37a30d Mon Sep 17 00:00:00 2001 From: Ayush Tiwari Date: Fri, 25 Sep 2026 22:50:14 +0530 Subject: [PATCH v1 1/2] Recheck table sync state after refresh An apply worker can cache a table's SYNCDONE state and then wait for the subscription lock while ALTER SUBSCRIPTION ... REFRESH PUBLICATION removes that table. The worker subsequently tries to mark the absent catalog row READY, causing an error. With disable_on_error, that disables the entire subscription, including unrelated tables. Two refreshes can also remove and re-add the table before the apply worker gets the lock. In that case an existence-only check would mark the new INIT row READY using the old SYNCDONE state, silently skipping its initial copy. After taking the existing subscription lock, re-read the table's current sync state and LSN. Skip origin cleanup and the READY update unless that state is still SYNCDONE and apply has reached its current sync LSN. This table-side issue predates the sequence-side fix in dca73f7dd03. --- src/backend/replication/logical/tablesync.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c index 4015e861a64..20ddad229c7 100644 --- a/src/backend/replication/logical/tablesync.c +++ b/src/backend/replication/logical/tablesync.c @@ -433,9 +433,7 @@ ProcessSyncingTablesForApply(XLogRecPtr current_lsn) if (current_lsn >= rstate->lsn) { char originname[NAMEDATALEN]; - - rstate->state = SUBREL_STATE_READY; - rstate->lsn = current_lsn; + XLogRecPtr statelsn; /* * Remove the tablesync origin tracking if exists. @@ -451,10 +449,23 @@ ProcessSyncingTablesForApply(XLogRecPtr current_lsn) * Lock the subscription and origin in the same order as we * are doing during DDL commands to avoid deadlocks. See * AlterSubscription_refresh. + * + * Recheck the state after acquiring the subscription lock. A + * concurrent refresh may have removed the table, or removed + * and re-added it with a new synchronization state, while we + * waited. */ LockSharedObject(SubscriptionRelationId, MyLogicalRepWorker->subid, 0, AccessShareLock); + if (GetSubscriptionRelState(MyLogicalRepWorker->subid, + rstate->relid, &statelsn) != SUBREL_STATE_SYNCDONE || + current_lsn < statelsn) + continue; + + rstate->state = SUBREL_STATE_READY; + rstate->lsn = current_lsn; + if (!rel) rel = table_open(SubscriptionRelRelationId, RowExclusiveLock); -- 2.34.1