From 7acb02405be5471d9827bd62b37c810038e262e3 Mon Sep 17 00:00:00 2001
From: Krishna Sarabu <34084857+kmsarabu@users.noreply.github.com>
Date: Sat, 14 Feb 2026 17:02:54 -0600
Subject: [PATCH] Fix sync worker hanging on Extension wait event after table
 resynchronization (#508)

This has been the cause of add_table test failures. (Fixes #126, fixes #497.)
---
 expected/add_table.out | 17 +++++++++--------
 pglogical_apply.c      | 40 +++++++++++++++++++++++++++++++++++++++-
 sql/add_table.sql      |  4 ++--
 3 files changed, 50 insertions(+), 11 deletions(-)

diff --git a/expected/add_table.out b/expected/add_table.out
index 3ac96133..84f051e5 100644
--- a/expected/add_table.out
+++ b/expected/add_table.out
@@ -263,13 +263,13 @@ SELECT pglogical.wait_for_table_sync_complete('test_subscription', 'test_publics
 (1 row)
 
 COMMIT;
-SELECT sync_kind, sync_subid, sync_nspname, sync_relname, sync_status IN ('y', 'r') FROM pglogical.local_sync_status ORDER BY 2,3,4;
- sync_kind | sync_subid |   sync_nspname    |    sync_relname    | ?column? 
------------+------------+-------------------+--------------------+----------
- d         | 3848008564 | public            | test_nosync        | t
- d         | 3848008564 | public            | test_publicschema  | t
- d         | 3848008564 | strange.schema-IS | test_strangeschema | t
- f         | 3848008564 |                   |                    | t
+SELECT sync_kind, sync_subid, sync_nspname, sync_relname, sync_status, sync_status IN ('y', 'r') FROM pglogical.local_sync_status ORDER BY 2,3,4;
+ sync_kind | sync_subid |   sync_nspname    |    sync_relname    | sync_status | ?column? 
+-----------+------------+-------------------+--------------------+-------------+----------
+ d         | 3848008564 | public            | test_nosync        | r           | t
+ d         | 3848008564 | public            | test_publicschema  | r           | t
+ d         | 3848008564 | strange.schema-IS | test_strangeschema | r           | t
+ f         | 3848008564 |                   |                    | r           | t
 (4 rows)
 
 SELECT * FROM public.test_publicschema;
@@ -282,10 +282,11 @@ SELECT * FROM public.test_publicschema;
 (4 rows)
 
 \x
-SELECT nspname, relname, status IN ('synchronized', 'replicating') FROM pglogical.show_subscription_table('test_subscription', 'test_publicschema');
+SELECT nspname, relname, status, status IN ('synchronized', 'replicating') FROM pglogical.show_subscription_table('test_subscription', 'test_publicschema');
 -[ RECORD 1 ]---------------
 nspname  | public
 relname  | test_publicschema
+status   | replicating
 ?column? | t
 
 \x
diff --git a/pglogical_apply.c b/pglogical_apply.c
index 8826bf0b..fac7222a 100644
--- a/pglogical_apply.c
+++ b/pglogical_apply.c
@@ -1360,7 +1360,7 @@ apply_work(PGconn *streamConn)
 	{
 		int			rc;
 		int			r;
-		
+
 		CHECK_FOR_INTERRUPTS();
 
 		/*
@@ -1382,6 +1382,44 @@ apply_work(PGconn *streamConn)
 		if (rc & WL_POSTMASTER_DEATH)
 			proc_exit(1);
 
+		/*
+		* Completion check for tablesync workers:
+		* If we've already applied up to replay_stop_lsn, mark SYNC_STATUS_SYNCDONE
+		* and exit, without waiting for a new commit record from the publisher.
+		*/
+		if (MyPGLogicalWorker->worker_type == PGLOGICAL_WORKER_SYNC &&
+			MyApplyWorker->replay_stop_lsn != InvalidXLogRecPtr)
+		{
+			static XLogRecPtr last_processed_lsn = InvalidXLogRecPtr;
+
+			if (last_received != InvalidXLogRecPtr && last_received > last_processed_lsn)
+				last_processed_lsn = last_received;
+
+			elog(DEBUG1, "sync worker check: target LSN %X/%X, last_processed %X/%X",
+					 (uint32)(MyApplyWorker->replay_stop_lsn >> 32),
+					 (uint32)MyApplyWorker->replay_stop_lsn,
+					 (uint32)(last_processed_lsn >> 32), (uint32)last_processed_lsn);
+
+			if (last_processed_lsn != InvalidXLogRecPtr &&
+				MyApplyWorker->replay_stop_lsn <= last_processed_lsn)
+			{
+				elog(DEBUG1, "sync worker completion: processed LSN %X/%X >= target LSN %X/%X",
+					 (uint32)(last_processed_lsn >> 32), (uint32)last_processed_lsn,
+					 (uint32)(MyApplyWorker->replay_stop_lsn >> 32),
+					 (uint32)MyApplyWorker->replay_stop_lsn);
+				StartTransactionCommand();
+				set_table_sync_status(MyApplyWorker->subid,
+						  NameStr(MyPGLogicalWorker->worker.sync.nspname),
+						  NameStr(MyPGLogicalWorker->worker.sync.relname),
+						  SYNC_STATUS_SYNCDONE, last_processed_lsn);
+				CommitTransactionCommand();
+				XLogFlush(GetXLogWriteRecPtr());
+				PQfinish(applyconn);
+				pglogical_sync_worker_finish();
+				proc_exit(0);
+			}
+		}
+
 		if (rc & WL_SOCKET_READABLE)
 			PQconsumeInput(applyconn);
 
diff --git a/sql/add_table.sql b/sql/add_table.sql
index cbc3f2b5..d3d6002f 100644
--- a/sql/add_table.sql
+++ b/sql/add_table.sql
@@ -115,12 +115,12 @@ SET statement_timeout = '180s';
 SELECT pglogical.wait_for_table_sync_complete('test_subscription', 'test_publicschema');
 COMMIT;
 
-SELECT sync_kind, sync_subid, sync_nspname, sync_relname, sync_status IN ('y', 'r') FROM pglogical.local_sync_status ORDER BY 2,3,4;
+SELECT sync_kind, sync_subid, sync_nspname, sync_relname, sync_status, sync_status IN ('y', 'r') FROM pglogical.local_sync_status ORDER BY 2,3,4;
 
 SELECT * FROM public.test_publicschema;
 
 \x
-SELECT nspname, relname, status IN ('synchronized', 'replicating') FROM pglogical.show_subscription_table('test_subscription', 'test_publicschema');
+SELECT nspname, relname, status, status IN ('synchronized', 'replicating') FROM pglogical.show_subscription_table('test_subscription', 'test_publicschema');
 \x
 
 BEGIN;
