LCOV - code coverage report
Current view: top level - src/backend/replication/logical - launcher.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 90.5 % 578 523
Test Date: 2026-09-12 04:15:38 Functions: 100.0 % 37 37
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 70.7 % 338 239

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  * launcher.c
       3                 :             :  *     PostgreSQL logical replication worker launcher process
       4                 :             :  *
       5                 :             :  * Copyright (c) 2016-2026, PostgreSQL Global Development Group
       6                 :             :  *
       7                 :             :  * IDENTIFICATION
       8                 :             :  *    src/backend/replication/logical/launcher.c
       9                 :             :  *
      10                 :             :  * NOTES
      11                 :             :  *    This module contains the logical replication worker launcher which
      12                 :             :  *    uses the background worker infrastructure to start the logical
      13                 :             :  *    replication workers for every enabled subscription.
      14                 :             :  *
      15                 :             :  *-------------------------------------------------------------------------
      16                 :             :  */
      17                 :             : 
      18                 :             : #include "postgres.h"
      19                 :             : 
      20                 :             : #include "access/heapam.h"
      21                 :             : #include "access/htup.h"
      22                 :             : #include "access/htup_details.h"
      23                 :             : #include "access/tableam.h"
      24                 :             : #include "access/xact.h"
      25                 :             : #include "catalog/pg_subscription.h"
      26                 :             : #include "catalog/pg_subscription_rel.h"
      27                 :             : #include "funcapi.h"
      28                 :             : #include "lib/dshash.h"
      29                 :             : #include "miscadmin.h"
      30                 :             : #include "pgstat.h"
      31                 :             : #include "postmaster/bgworker.h"
      32                 :             : #include "postmaster/interrupt.h"
      33                 :             : #include "replication/logicallauncher.h"
      34                 :             : #include "replication/origin.h"
      35                 :             : #include "replication/slot.h"
      36                 :             : #include "replication/walreceiver.h"
      37                 :             : #include "replication/worker_internal.h"
      38                 :             : #include "storage/ipc.h"
      39                 :             : #include "storage/proc.h"
      40                 :             : #include "storage/procarray.h"
      41                 :             : #include "storage/subsystems.h"
      42                 :             : #include "tcop/tcopprot.h"
      43                 :             : #include "utils/builtins.h"
      44                 :             : #include "utils/memutils.h"
      45                 :             : #include "utils/pg_lsn.h"
      46                 :             : #include "utils/snapmgr.h"
      47                 :             : #include "utils/syscache.h"
      48                 :             : #include "utils/wait_event.h"
      49                 :             : 
      50                 :             : /* max sleep time between cycles (3min) */
      51                 :             : #define DEFAULT_NAPTIME_PER_CYCLE 180000L
      52                 :             : 
      53                 :             : /* GUC variables */
      54                 :             : int         max_logical_replication_workers = 4;
      55                 :             : int         max_sync_workers_per_subscription = 2;
      56                 :             : int         max_parallel_apply_workers_per_subscription = 2;
      57                 :             : 
      58                 :             : LogicalRepWorker *MyLogicalRepWorker = NULL;
      59                 :             : 
      60                 :             : typedef struct LogicalRepCtxStruct
      61                 :             : {
      62                 :             :     /* Supervisor process. */
      63                 :             :     pid_t       launcher_pid;
      64                 :             : 
      65                 :             :     /* Hash table holding last start times of subscriptions' apply workers. */
      66                 :             :     dsa_handle  last_start_dsa;
      67                 :             :     dshash_table_handle last_start_dsh;
      68                 :             : 
      69                 :             :     /* Background workers. */
      70                 :             :     LogicalRepWorker workers[FLEXIBLE_ARRAY_MEMBER];
      71                 :             : } LogicalRepCtxStruct;
      72                 :             : 
      73                 :             : static LogicalRepCtxStruct *LogicalRepCtx;
      74                 :             : 
      75                 :             : static void ApplyLauncherShmemRequest(void *arg);
      76                 :             : static void ApplyLauncherShmemInit(void *arg);
      77                 :             : 
      78                 :             : const ShmemCallbacks ApplyLauncherShmemCallbacks = {
      79                 :             :     .request_fn = ApplyLauncherShmemRequest,
      80                 :             :     .init_fn = ApplyLauncherShmemInit,
      81                 :             : };
      82                 :             : 
      83                 :             : /* an entry in the last-start-times shared hash table */
      84                 :             : typedef struct LauncherLastStartTimesEntry
      85                 :             : {
      86                 :             :     Oid         subid;          /* OID of logrep subscription (hash key) */
      87                 :             :     TimestampTz last_start_time;    /* last time its apply worker was started */
      88                 :             : } LauncherLastStartTimesEntry;
      89                 :             : 
      90                 :             : /* parameters for the last-start-times shared hash table */
      91                 :             : static const dshash_parameters dsh_params = {
      92                 :             :     sizeof(Oid),
      93                 :             :     sizeof(LauncherLastStartTimesEntry),
      94                 :             :     dshash_memcmp,
      95                 :             :     dshash_memhash,
      96                 :             :     dshash_memcpy,
      97                 :             :     LWTRANCHE_LAUNCHER_HASH
      98                 :             : };
      99                 :             : 
     100                 :             : static dsa_area *last_start_times_dsa = NULL;
     101                 :             : static dshash_table *last_start_times = NULL;
     102                 :             : 
     103                 :             : static bool on_commit_launcher_wakeup = false;
     104                 :             : 
     105                 :             : 
     106                 :             : static void logicalrep_launcher_onexit(int code, Datum arg);
     107                 :             : static void logicalrep_worker_onexit(int code, Datum arg);
     108                 :             : static void logicalrep_worker_detach(void);
     109                 :             : static void logicalrep_worker_cleanup(LogicalRepWorker *worker);
     110                 :             : static int  logicalrep_pa_worker_count(Oid subid);
     111                 :             : static void logicalrep_launcher_attach_dshmem(void);
     112                 :             : static void ApplyLauncherSetWorkerStartTime(Oid subid, TimestampTz start_time);
     113                 :             : static TimestampTz ApplyLauncherGetWorkerStartTime(Oid subid);
     114                 :             : static bool collect_min_nonremovable_xid(Subscription *sub,
     115                 :             :                                          LogicalRepWorker *worker,
     116                 :             :                                          TransactionId *xmin);
     117                 :             : static bool acquire_conflict_slot_if_exists(void);
     118                 :             : static void update_conflict_slot_xmin(TransactionId new_xmin);
     119                 :             : static void reset_conflict_slot_xmin_to_safe_horizon(void);
     120                 :             : 
     121                 :             : 
     122                 :             : /*
     123                 :             :  * Load the list of subscriptions.
     124                 :             :  *
     125                 :             :  * Only the fields interesting for worker start/stop functions are filled for
     126                 :             :  * each subscription.
     127                 :             :  */
     128                 :             : static List *
     129                 :        3366 : get_subscription_list(void)
     130                 :             : {
     131                 :        3366 :     List       *res = NIL;
     132                 :             :     Relation    rel;
     133                 :             :     TableScanDesc scan;
     134                 :             :     HeapTuple   tup;
     135                 :             :     MemoryContext resultcxt;
     136                 :             : 
     137                 :             :     /* This is the context that we will allocate our output data in */
     138                 :        3366 :     resultcxt = CurrentMemoryContext;
     139                 :             : 
     140                 :             :     /*
     141                 :             :      * Start a transaction so we can access pg_subscription.
     142                 :             :      */
     143                 :        3366 :     StartTransactionCommand();
     144                 :             : 
     145                 :        3366 :     rel = table_open(SubscriptionRelationId, AccessShareLock);
     146                 :        3366 :     scan = table_beginscan_catalog(rel, 0, NULL);
     147                 :             : 
     148         [ +  + ]:        4490 :     while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
     149                 :             :     {
     150                 :        1124 :         Form_pg_subscription subform = (Form_pg_subscription) GETSTRUCT(tup);
     151                 :             :         Subscription *sub;
     152                 :             :         MemoryContext oldcxt;
     153                 :             : 
     154                 :             :         /*
     155                 :             :          * Allocate our results in the caller's context, not the
     156                 :             :          * transaction's. We do this inside the loop, and restore the original
     157                 :             :          * context at the end, so that leaky things like heap_getnext() are
     158                 :             :          * not called in a potentially long-lived context.
     159                 :             :          */
     160                 :        1124 :         oldcxt = MemoryContextSwitchTo(resultcxt);
     161                 :             : 
     162                 :        1124 :         sub = palloc0_object(Subscription);
     163                 :        1124 :         sub->oid = subform->oid;
     164                 :        1124 :         sub->dbid = subform->subdbid;
     165                 :        1124 :         sub->owner = subform->subowner;
     166                 :        1124 :         sub->enabled = subform->subenabled;
     167                 :        1124 :         sub->name = pstrdup(NameStr(subform->subname));
     168                 :        1124 :         sub->retaindeadtuples = subform->subretaindeadtuples;
     169                 :        1124 :         sub->retentionactive = subform->subretentionactive;
     170                 :             :         /* We don't fill fields we are not interested in. */
     171                 :             : 
     172                 :        1124 :         res = lappend(res, sub);
     173                 :        1124 :         MemoryContextSwitchTo(oldcxt);
     174                 :             :     }
     175                 :             : 
     176                 :        3366 :     table_endscan(scan);
     177                 :        3366 :     table_close(rel, AccessShareLock);
     178                 :             : 
     179                 :        3366 :     CommitTransactionCommand();
     180                 :             : 
     181                 :        3366 :     return res;
     182                 :             : }
     183                 :             : 
     184                 :             : /*
     185                 :             :  * Wait for a background worker to start up and attach to the shmem context.
     186                 :             :  *
     187                 :             :  * This is only needed for cleaning up the shared memory in case the worker
     188                 :             :  * fails to attach.
     189                 :             :  *
     190                 :             :  * Returns whether the attach was successful.
     191                 :             :  */
     192                 :             : static bool
     193                 :         490 : WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
     194                 :             :                                uint16 generation,
     195                 :             :                                BackgroundWorkerHandle *handle)
     196                 :             : {
     197                 :         490 :     bool        result = false;
     198                 :         490 :     bool        dropped_latch = false;
     199                 :             : 
     200                 :             :     for (;;)
     201                 :        1176 :     {
     202                 :             :         BgwHandleStatus status;
     203                 :             :         pid_t       pid;
     204                 :             :         int         rc;
     205                 :             : 
     206         [ -  + ]:        1666 :         CHECK_FOR_INTERRUPTS();
     207                 :             : 
     208                 :        1666 :         LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
     209                 :             : 
     210                 :             :         /* Worker either died or has started. Return false if died. */
     211   [ +  +  +  + ]:        1666 :         if (!worker->in_use || worker->proc)
     212                 :             :         {
     213                 :         490 :             result = worker->in_use;
     214                 :         490 :             LWLockRelease(LogicalRepWorkerLock);
     215                 :         490 :             break;
     216                 :             :         }
     217                 :             : 
     218                 :        1176 :         LWLockRelease(LogicalRepWorkerLock);
     219                 :             : 
     220                 :             :         /* Check if worker has died before attaching, and clean up after it. */
     221                 :        1176 :         status = GetBackgroundWorkerPid(handle, &pid);
     222                 :             : 
     223         [ -  + ]:        1176 :         if (status == BGWH_STOPPED)
     224                 :             :         {
     225                 :           0 :             LWLockAcquire(LogicalRepWorkerLock, LW_EXCLUSIVE);
     226                 :             :             /* Ensure that this was indeed the worker we waited for. */
     227         [ #  # ]:           0 :             if (generation == worker->generation)
     228                 :           0 :                 logicalrep_worker_cleanup(worker);
     229                 :           0 :             LWLockRelease(LogicalRepWorkerLock);
     230                 :           0 :             break;              /* result is already false */
     231                 :             :         }
     232                 :             : 
     233                 :             :         /*
     234                 :             :          * We need timeout because we generally don't get notified via latch
     235                 :             :          * about the worker attach.  But we don't expect to have to wait long.
     236                 :             :          */
     237                 :        1176 :         rc = WaitLatch(MyLatch,
     238                 :             :                        WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
     239                 :             :                        10L, WAIT_EVENT_BGWORKER_STARTUP);
     240                 :             : 
     241         [ +  + ]:        1176 :         if (rc & WL_LATCH_SET)
     242                 :             :         {
     243                 :         563 :             ResetLatch(MyLatch);
     244         [ -  + ]:         563 :             CHECK_FOR_INTERRUPTS();
     245                 :         563 :             dropped_latch = true;
     246                 :             :         }
     247                 :             :     }
     248                 :             : 
     249                 :             :     /*
     250                 :             :      * If we had to clear a latch event in order to wait, be sure to restore
     251                 :             :      * it before exiting.  Otherwise caller may miss events.
     252                 :             :      */
     253         [ +  + ]:         490 :     if (dropped_latch)
     254                 :         487 :         SetLatch(MyLatch);
     255                 :             : 
     256                 :         490 :     return result;
     257                 :             : }
     258                 :             : 
     259                 :             : /*
     260                 :             :  * Walks the workers array and searches for one that matches given worker type,
     261                 :             :  * subscription id, and relation id.
     262                 :             :  *
     263                 :             :  * For both apply workers and sequencesync workers, the relid should be set to
     264                 :             :  * InvalidOid, as these workers handle changes across all tables and sequences
     265                 :             :  * respectively, rather than targeting a specific relation. For tablesync
     266                 :             :  * workers, the relid should be set to the OID of the relation being
     267                 :             :  * synchronized.
     268                 :             :  */
     269                 :             : LogicalRepWorker *
     270                 :        3586 : logicalrep_worker_find(LogicalRepWorkerType wtype, Oid subid, Oid relid,
     271                 :             :                        bool only_running)
     272                 :             : {
     273                 :             :     int         i;
     274                 :        3586 :     LogicalRepWorker *res = NULL;
     275                 :             : 
     276                 :             :     /* relid must be valid only for table sync workers */
     277                 :             :     Assert((wtype == WORKERTYPE_TABLESYNC) == OidIsValid(relid));
     278                 :             :     Assert(LWLockHeldByMe(LogicalRepWorkerLock));
     279                 :             : 
     280                 :             :     /* Search for an attached worker that matches the specified criteria. */
     281         [ +  + ]:       11048 :     for (i = 0; i < max_logical_replication_workers; i++)
     282                 :             :     {
     283                 :        9613 :         LogicalRepWorker *w = &LogicalRepCtx->workers[i];
     284                 :             : 
     285                 :             :         /* Skip parallel apply workers. */
     286   [ +  +  -  + ]:        9613 :         if (isParallelApplyWorker(w))
     287                 :           0 :             continue;
     288                 :             : 
     289   [ +  +  +  +  :        9613 :         if (w->in_use && w->subid == subid && w->relid == relid &&
                   +  + ]
     290   [ +  +  +  +  :        2209 :             w->type == wtype && (!only_running || w->proc))
                   +  - ]
     291                 :             :         {
     292                 :        2151 :             res = w;
     293                 :        2151 :             break;
     294                 :             :         }
     295                 :             :     }
     296                 :             : 
     297                 :        3586 :     return res;
     298                 :             : }
     299                 :             : 
     300                 :             : /*
     301                 :             :  * Similar to logicalrep_worker_find(), but returns a list of all workers for
     302                 :             :  * the subscription, instead of just one.
     303                 :             :  */
     304                 :             : List *
     305                 :         968 : logicalrep_workers_find(Oid subid, bool only_running, bool acquire_lock)
     306                 :             : {
     307                 :             :     int         i;
     308                 :         968 :     List       *res = NIL;
     309                 :             : 
     310         [ +  + ]:         968 :     if (acquire_lock)
     311                 :         175 :         LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
     312                 :             : 
     313                 :             :     Assert(LWLockHeldByMe(LogicalRepWorkerLock));
     314                 :             : 
     315                 :             :     /* Search for attached worker for a given subscription id. */
     316         [ +  + ]:        4970 :     for (i = 0; i < max_logical_replication_workers; i++)
     317                 :             :     {
     318                 :        4002 :         LogicalRepWorker *w = &LogicalRepCtx->workers[i];
     319                 :             : 
     320   [ +  +  +  +  :        4002 :         if (w->in_use && w->subid == subid && (!only_running || w->proc))
             +  +  +  + ]
     321                 :         583 :             res = lappend(res, w);
     322                 :             :     }
     323                 :             : 
     324         [ +  + ]:         968 :     if (acquire_lock)
     325                 :         175 :         LWLockRelease(LogicalRepWorkerLock);
     326                 :             : 
     327                 :         968 :     return res;
     328                 :             : }
     329                 :             : 
     330                 :             : /*
     331                 :             :  * Start new logical replication background worker, if possible.
     332                 :             :  *
     333                 :             :  * Returns true on success, false on failure.
     334                 :             :  */
     335                 :             : bool
     336                 :         501 : logicalrep_worker_launch(LogicalRepWorkerType wtype,
     337                 :             :                          Oid dbid, Oid subid, const char *subname, Oid userid,
     338                 :             :                          Oid relid, dsm_handle subworker_dsm,
     339                 :             :                          bool retain_dead_tuples)
     340                 :             : {
     341                 :             :     BackgroundWorker bgw;
     342                 :             :     BackgroundWorkerHandle *bgw_handle;
     343                 :             :     uint16      generation;
     344                 :             :     int         i;
     345                 :         501 :     int         slot = 0;
     346                 :         501 :     LogicalRepWorker *worker = NULL;
     347                 :             :     int         nsyncworkers;
     348                 :             :     int         nparallelapplyworkers;
     349                 :             :     TimestampTz now;
     350                 :         501 :     bool        is_tablesync_worker = (wtype == WORKERTYPE_TABLESYNC);
     351                 :         501 :     bool        is_sequencesync_worker = (wtype == WORKERTYPE_SEQUENCESYNC);
     352                 :         501 :     bool        is_parallel_apply_worker = (wtype == WORKERTYPE_PARALLEL_APPLY);
     353                 :             : 
     354                 :             :     /*----------
     355                 :             :      * Sanity checks:
     356                 :             :      * - must be valid worker type
     357                 :             :      * - tablesync workers are only ones to have relid
     358                 :             :      * - parallel apply worker is the only kind of subworker
     359                 :             :      * - The replication slot used in conflict detection is created when
     360                 :             :      *   retain_dead_tuples is enabled
     361                 :             :      */
     362                 :             :     Assert(wtype != WORKERTYPE_UNKNOWN);
     363                 :             :     Assert(is_tablesync_worker == OidIsValid(relid));
     364                 :             :     Assert(is_parallel_apply_worker == (subworker_dsm != DSM_HANDLE_INVALID));
     365                 :             :     Assert(!retain_dead_tuples || MyReplicationSlot);
     366                 :             : 
     367         [ +  + ]:         501 :     ereport(DEBUG1,
     368                 :             :             (errmsg_internal("starting logical replication worker for subscription \"%s\"",
     369                 :             :                              subname)));
     370                 :             : 
     371                 :             :     /* Report this after the initial starting message for consistency. */
     372         [ -  + ]:         501 :     if (max_active_replication_origins == 0)
     373         [ #  # ]:           0 :         ereport(ERROR,
     374                 :             :                 (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
     375                 :             :                  errmsg("cannot start logical replication workers when \"max_active_replication_origins\" is 0")));
     376                 :             : 
     377                 :             :     /*
     378                 :             :      * We need to do the modification of the shared memory under lock so that
     379                 :             :      * we have consistent view.
     380                 :             :      */
     381                 :         501 :     LWLockAcquire(LogicalRepWorkerLock, LW_EXCLUSIVE);
     382                 :             : 
     383                 :         501 : retry:
     384                 :             :     /* Find unused worker slot. */
     385         [ +  - ]:         900 :     for (i = 0; i < max_logical_replication_workers; i++)
     386                 :             :     {
     387                 :         900 :         LogicalRepWorker *w = &LogicalRepCtx->workers[i];
     388                 :             : 
     389         [ +  + ]:         900 :         if (!w->in_use)
     390                 :             :         {
     391                 :         501 :             worker = w;
     392                 :         501 :             slot = i;
     393                 :         501 :             break;
     394                 :             :         }
     395                 :             :     }
     396                 :             : 
     397                 :         501 :     nsyncworkers = logicalrep_sync_worker_count(subid);
     398                 :             : 
     399                 :         501 :     now = GetCurrentTimestamp();
     400                 :             : 
     401                 :             :     /*
     402                 :             :      * If we didn't find a free slot, try to do garbage collection.  The
     403                 :             :      * reason we do this is because if some worker failed to start up and its
     404                 :             :      * parent has crashed while waiting, the in_use state was never cleared.
     405                 :             :      */
     406   [ +  -  -  + ]:         501 :     if (worker == NULL || nsyncworkers >= max_sync_workers_per_subscription)
     407                 :             :     {
     408                 :           0 :         bool        did_cleanup = false;
     409                 :             : 
     410         [ #  # ]:           0 :         for (i = 0; i < max_logical_replication_workers; i++)
     411                 :             :         {
     412                 :           0 :             LogicalRepWorker *w = &LogicalRepCtx->workers[i];
     413                 :             : 
     414                 :             :             /*
     415                 :             :              * If the worker was marked in use but didn't manage to attach in
     416                 :             :              * time, clean it up.
     417                 :             :              */
     418   [ #  #  #  #  :           0 :             if (w->in_use && !w->proc &&
                   #  # ]
     419                 :           0 :                 TimestampDifferenceExceeds(w->launch_time, now,
     420                 :             :                                            wal_receiver_timeout))
     421                 :             :             {
     422         [ #  # ]:           0 :                 elog(WARNING,
     423                 :             :                      "logical replication worker for subscription %u took too long to start; canceled",
     424                 :             :                      w->subid);
     425                 :             : 
     426                 :           0 :                 logicalrep_worker_cleanup(w);
     427                 :           0 :                 did_cleanup = true;
     428                 :             :             }
     429                 :             :         }
     430                 :             : 
     431         [ #  # ]:           0 :         if (did_cleanup)
     432                 :           0 :             goto retry;
     433                 :             :     }
     434                 :             : 
     435                 :             :     /*
     436                 :             :      * We don't allow to invoke more sync workers once we have reached the
     437                 :             :      * sync worker limit per subscription. So, just return silently as we
     438                 :             :      * might get here because of an otherwise harmless race condition.
     439                 :             :      */
     440   [ +  +  +  + ]:         501 :     if ((is_tablesync_worker || is_sequencesync_worker) &&
     441         [ -  + ]:         244 :         nsyncworkers >= max_sync_workers_per_subscription)
     442                 :             :     {
     443                 :           0 :         LWLockRelease(LogicalRepWorkerLock);
     444                 :           0 :         return false;
     445                 :             :     }
     446                 :             : 
     447                 :         501 :     nparallelapplyworkers = logicalrep_pa_worker_count(subid);
     448                 :             : 
     449                 :             :     /*
     450                 :             :      * Return false if the number of parallel apply workers reached the limit
     451                 :             :      * per subscription.
     452                 :             :      */
     453         [ +  + ]:         501 :     if (is_parallel_apply_worker &&
     454         [ -  + ]:          13 :         nparallelapplyworkers >= max_parallel_apply_workers_per_subscription)
     455                 :             :     {
     456                 :           0 :         LWLockRelease(LogicalRepWorkerLock);
     457                 :           0 :         return false;
     458                 :             :     }
     459                 :             : 
     460                 :             :     /*
     461                 :             :      * However if there are no more free worker slots, inform user about it
     462                 :             :      * before exiting.
     463                 :             :      */
     464         [ -  + ]:         501 :     if (worker == NULL)
     465                 :             :     {
     466                 :           0 :         LWLockRelease(LogicalRepWorkerLock);
     467         [ #  # ]:           0 :         ereport(WARNING,
     468                 :             :                 (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
     469                 :             :                  errmsg("out of logical replication worker slots"),
     470                 :             :                  errhint("You might need to increase \"%s\".", "max_logical_replication_workers")));
     471                 :           0 :         return false;
     472                 :             :     }
     473                 :             : 
     474                 :             :     /* Prepare the worker slot. */
     475                 :         501 :     worker->type = wtype;
     476                 :         501 :     worker->launch_time = now;
     477                 :         501 :     worker->in_use = true;
     478                 :         501 :     worker->generation++;
     479                 :         501 :     worker->proc = NULL;
     480                 :         501 :     worker->dbid = dbid;
     481                 :         501 :     worker->userid = userid;
     482                 :         501 :     worker->subid = subid;
     483                 :         501 :     worker->relid = relid;
     484                 :         501 :     worker->relstate = SUBREL_STATE_UNKNOWN;
     485                 :         501 :     worker->relstate_lsn = InvalidXLogRecPtr;
     486                 :         501 :     worker->stream_fileset = NULL;
     487         [ +  + ]:         501 :     worker->leader_pid = is_parallel_apply_worker ? MyProcPid : InvalidPid;
     488                 :         501 :     worker->parallel_apply = is_parallel_apply_worker;
     489                 :         501 :     worker->oldest_nonremovable_xid = retain_dead_tuples
     490                 :           2 :         ? MyReplicationSlot->data.xmin
     491         [ +  + ]:         501 :         : InvalidTransactionId;
     492                 :         501 :     worker->last_lsn = InvalidXLogRecPtr;
     493                 :         501 :     TIMESTAMP_NOBEGIN(worker->last_send_time);
     494                 :         501 :     TIMESTAMP_NOBEGIN(worker->last_recv_time);
     495                 :         501 :     worker->reply_lsn = InvalidXLogRecPtr;
     496                 :         501 :     TIMESTAMP_NOBEGIN(worker->reply_time);
     497                 :         501 :     worker->last_seqsync_start_time = 0;
     498                 :             : 
     499                 :             :     /* Before releasing lock, remember generation for future identification. */
     500                 :         501 :     generation = worker->generation;
     501                 :             : 
     502                 :         501 :     LWLockRelease(LogicalRepWorkerLock);
     503                 :             : 
     504                 :             :     /* Register the new dynamic worker. */
     505                 :         501 :     memset(&bgw, 0, sizeof(bgw));
     506                 :         501 :     bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
     507                 :             :         BGWORKER_BACKEND_DATABASE_CONNECTION;
     508                 :         501 :     bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
     509                 :         501 :     snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
     510                 :             : 
     511   [ +  +  +  +  :         501 :     switch (worker->type)
                   -  - ]
     512                 :             :     {
     513                 :         244 :         case WORKERTYPE_APPLY:
     514                 :         244 :             snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ApplyWorkerMain");
     515                 :         244 :             snprintf(bgw.bgw_name, BGW_MAXLEN,
     516                 :             :                      "logical replication apply worker for subscription %u",
     517                 :             :                      subid);
     518                 :         244 :             snprintf(bgw.bgw_type, BGW_MAXLEN, "logical replication apply worker");
     519                 :         244 :             break;
     520                 :             : 
     521                 :          13 :         case WORKERTYPE_PARALLEL_APPLY:
     522                 :          13 :             snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ParallelApplyWorkerMain");
     523                 :          13 :             snprintf(bgw.bgw_name, BGW_MAXLEN,
     524                 :             :                      "logical replication parallel apply worker for subscription %u",
     525                 :             :                      subid);
     526                 :          13 :             snprintf(bgw.bgw_type, BGW_MAXLEN, "logical replication parallel worker");
     527                 :             : 
     528                 :          13 :             memcpy(bgw.bgw_extra, &subworker_dsm, sizeof(dsm_handle));
     529                 :          13 :             break;
     530                 :             : 
     531                 :          15 :         case WORKERTYPE_SEQUENCESYNC:
     532                 :          15 :             snprintf(bgw.bgw_function_name, BGW_MAXLEN, "SequenceSyncWorkerMain");
     533                 :          15 :             snprintf(bgw.bgw_name, BGW_MAXLEN,
     534                 :             :                      "logical replication sequencesync worker for subscription %u",
     535                 :             :                      subid);
     536                 :          15 :             snprintf(bgw.bgw_type, BGW_MAXLEN, "logical replication sequencesync worker");
     537                 :          15 :             break;
     538                 :             : 
     539                 :         229 :         case WORKERTYPE_TABLESYNC:
     540                 :         229 :             snprintf(bgw.bgw_function_name, BGW_MAXLEN, "TableSyncWorkerMain");
     541                 :         229 :             snprintf(bgw.bgw_name, BGW_MAXLEN,
     542                 :             :                      "logical replication tablesync worker for subscription %u sync %u",
     543                 :             :                      subid,
     544                 :             :                      relid);
     545                 :         229 :             snprintf(bgw.bgw_type, BGW_MAXLEN, "logical replication tablesync worker");
     546                 :         229 :             break;
     547                 :             : 
     548                 :           0 :         case WORKERTYPE_UNKNOWN:
     549                 :             :             /* Should never happen. */
     550         [ #  # ]:           0 :             elog(ERROR, "unknown worker type");
     551                 :             :     }
     552                 :             : 
     553                 :         501 :     bgw.bgw_restart_time = BGW_NEVER_RESTART;
     554                 :         501 :     bgw.bgw_notify_pid = MyProcPid;
     555                 :         501 :     bgw.bgw_main_arg = Int32GetDatum(slot);
     556                 :             : 
     557         [ +  + ]:         501 :     if (!RegisterDynamicBackgroundWorker(&bgw, &bgw_handle))
     558                 :             :     {
     559                 :             :         /* Failed to start worker, so clean up the worker slot. */
     560                 :          11 :         LWLockAcquire(LogicalRepWorkerLock, LW_EXCLUSIVE);
     561                 :             :         Assert(generation == worker->generation);
     562                 :          11 :         logicalrep_worker_cleanup(worker);
     563                 :          11 :         LWLockRelease(LogicalRepWorkerLock);
     564                 :             : 
     565         [ +  - ]:          11 :         ereport(WARNING,
     566                 :             :                 (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
     567                 :             :                  errmsg("out of background worker slots"),
     568                 :             :                  errhint("You might need to increase \"%s\".", "max_worker_processes")));
     569                 :          11 :         return false;
     570                 :             :     }
     571                 :             : 
     572                 :             :     /* Now wait until it attaches. */
     573                 :         490 :     return WaitForReplicationWorkerAttach(worker, generation, bgw_handle);
     574                 :             : }
     575                 :             : 
     576                 :             : /*
     577                 :             :  * Internal function to stop the worker and wait until it detaches from the
     578                 :             :  * slot.
     579                 :             :  */
     580                 :             : static void
     581                 :          97 : logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
     582                 :             : {
     583                 :             :     uint16      generation;
     584                 :             : 
     585                 :             :     Assert(LWLockHeldByMeInMode(LogicalRepWorkerLock, LW_SHARED));
     586                 :             : 
     587                 :             :     /*
     588                 :             :      * Remember which generation was our worker so we can check if what we see
     589                 :             :      * is still the same one.
     590                 :             :      */
     591                 :          97 :     generation = worker->generation;
     592                 :             : 
     593                 :             :     /*
     594                 :             :      * If we found a worker but it does not have proc set then it is still
     595                 :             :      * starting up; wait for it to finish starting and then kill it.
     596                 :             :      */
     597   [ +  -  +  + ]:          97 :     while (worker->in_use && !worker->proc)
     598                 :             :     {
     599                 :             :         int         rc;
     600                 :             : 
     601                 :           7 :         LWLockRelease(LogicalRepWorkerLock);
     602                 :             : 
     603                 :             :         /* Wait a bit --- we don't expect to have to wait long. */
     604                 :           7 :         rc = WaitLatch(MyLatch,
     605                 :             :                        WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
     606                 :             :                        10L, WAIT_EVENT_BGWORKER_STARTUP);
     607                 :             : 
     608         [ -  + ]:           7 :         if (rc & WL_LATCH_SET)
     609                 :             :         {
     610                 :           0 :             ResetLatch(MyLatch);
     611         [ #  # ]:           0 :             CHECK_FOR_INTERRUPTS();
     612                 :             :         }
     613                 :             : 
     614                 :             :         /* Recheck worker status. */
     615                 :           7 :         LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
     616                 :             : 
     617                 :             :         /*
     618                 :             :          * Check whether the worker slot is no longer used, which would mean
     619                 :             :          * that the worker has exited, or whether the worker generation is
     620                 :             :          * different, meaning that a different worker has taken the slot.
     621                 :             :          */
     622   [ +  +  -  + ]:           7 :         if (!worker->in_use || worker->generation != generation)
     623                 :           2 :             return;
     624                 :             : 
     625                 :             :         /* Worker has assigned proc, so it has started. */
     626         [ +  - ]:           5 :         if (worker->proc)
     627                 :           5 :             break;
     628                 :             :     }
     629                 :             : 
     630                 :             :     /* Now terminate the worker ... */
     631                 :          95 :     kill(worker->proc->pid, signo);
     632                 :             : 
     633                 :             :     /* ... and wait for it to die. */
     634                 :             :     for (;;)
     635                 :         126 :     {
     636                 :             :         int         rc;
     637                 :             : 
     638                 :             :         /* is it gone? */
     639   [ +  +  +  + ]:         221 :         if (!worker->proc || worker->generation != generation)
     640                 :             :             break;
     641                 :             : 
     642                 :         126 :         LWLockRelease(LogicalRepWorkerLock);
     643                 :             : 
     644                 :             :         /* Wait a bit --- we don't expect to have to wait long. */
     645                 :         126 :         rc = WaitLatch(MyLatch,
     646                 :             :                        WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
     647                 :             :                        10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
     648                 :             : 
     649         [ +  + ]:         126 :         if (rc & WL_LATCH_SET)
     650                 :             :         {
     651                 :          36 :             ResetLatch(MyLatch);
     652         [ +  + ]:          36 :             CHECK_FOR_INTERRUPTS();
     653                 :             :         }
     654                 :             : 
     655                 :         126 :         LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
     656                 :             :     }
     657                 :             : }
     658                 :             : 
     659                 :             : /*
     660                 :             :  * Stop the logical replication worker that matches the specified worker type,
     661                 :             :  * subscription id, and relation id.
     662                 :             :  */
     663                 :             : void
     664                 :         114 : logicalrep_worker_stop(LogicalRepWorkerType wtype, Oid subid, Oid relid)
     665                 :             : {
     666                 :             :     LogicalRepWorker *worker;
     667                 :             : 
     668                 :             :     /* relid must be valid only for table sync workers */
     669                 :             :     Assert((wtype == WORKERTYPE_TABLESYNC) == OidIsValid(relid));
     670                 :             : 
     671                 :         114 :     LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
     672                 :             : 
     673                 :         114 :     worker = logicalrep_worker_find(wtype, subid, relid, false);
     674                 :             : 
     675         [ +  + ]:         114 :     if (worker)
     676                 :             :     {
     677                 :             :         Assert(!isParallelApplyWorker(worker));
     678                 :          86 :         logicalrep_worker_stop_internal(worker, SIGTERM);
     679                 :             :     }
     680                 :             : 
     681                 :         114 :     LWLockRelease(LogicalRepWorkerLock);
     682                 :         114 : }
     683                 :             : 
     684                 :             : /*
     685                 :             :  * Stop the given logical replication parallel apply worker.
     686                 :             :  *
     687                 :             :  * Node that the function sends SIGUSR2 instead of SIGTERM to the parallel apply
     688                 :             :  * worker so that the worker exits cleanly.
     689                 :             :  */
     690                 :             : void
     691                 :           5 : logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
     692                 :             : {
     693                 :             :     int         slot_no;
     694                 :             :     uint16      generation;
     695                 :             :     LogicalRepWorker *worker;
     696                 :             : 
     697                 :           5 :     SpinLockAcquire(&winfo->shared->mutex);
     698                 :           5 :     generation = winfo->shared->logicalrep_worker_generation;
     699                 :           5 :     slot_no = winfo->shared->logicalrep_worker_slot_no;
     700                 :           5 :     SpinLockRelease(&winfo->shared->mutex);
     701                 :             : 
     702                 :             :     Assert(slot_no >= 0 && slot_no < max_logical_replication_workers);
     703                 :             : 
     704                 :             :     /*
     705                 :             :      * Detach from the error_mq_handle for the parallel apply worker before
     706                 :             :      * stopping it. This prevents the leader apply worker from trying to
     707                 :             :      * receive the message from the error queue that might already be detached
     708                 :             :      * by the parallel apply worker.
     709                 :             :      */
     710         [ +  - ]:           5 :     if (winfo->error_mq_handle)
     711                 :             :     {
     712                 :           5 :         shm_mq_detach(winfo->error_mq_handle);
     713                 :           5 :         winfo->error_mq_handle = NULL;
     714                 :             :     }
     715                 :             : 
     716                 :           5 :     LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
     717                 :             : 
     718                 :           5 :     worker = &LogicalRepCtx->workers[slot_no];
     719                 :             :     Assert(isParallelApplyWorker(worker));
     720                 :             : 
     721                 :             :     /*
     722                 :             :      * Only stop the worker if the generation matches and the worker is alive.
     723                 :             :      */
     724   [ +  -  +  - ]:           5 :     if (worker->generation == generation && worker->proc)
     725                 :           5 :         logicalrep_worker_stop_internal(worker, SIGUSR2);
     726                 :             : 
     727                 :           5 :     LWLockRelease(LogicalRepWorkerLock);
     728                 :           5 : }
     729                 :             : 
     730                 :             : /*
     731                 :             :  * Wake up (using latch) any logical replication worker that matches the
     732                 :             :  * specified worker type, subscription id, and relation id.
     733                 :             :  */
     734                 :             : void
     735                 :         234 : logicalrep_worker_wakeup(LogicalRepWorkerType wtype, Oid subid, Oid relid)
     736                 :             : {
     737                 :             :     LogicalRepWorker *worker;
     738                 :             : 
     739                 :             :     /* relid must be valid only for table sync workers */
     740                 :             :     Assert((wtype == WORKERTYPE_TABLESYNC) == OidIsValid(relid));
     741                 :             : 
     742                 :         234 :     LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
     743                 :             : 
     744                 :         234 :     worker = logicalrep_worker_find(wtype, subid, relid, true);
     745                 :             : 
     746         [ +  - ]:         234 :     if (worker)
     747                 :         234 :         logicalrep_worker_wakeup_ptr(worker);
     748                 :             : 
     749                 :         234 :     LWLockRelease(LogicalRepWorkerLock);
     750                 :         234 : }
     751                 :             : 
     752                 :             : /*
     753                 :             :  * Wake up (using latch) the specified logical replication worker.
     754                 :             :  *
     755                 :             :  * Caller must hold lock, else worker->proc could change under us.
     756                 :             :  */
     757                 :             : void
     758                 :         717 : logicalrep_worker_wakeup_ptr(LogicalRepWorker *worker)
     759                 :             : {
     760                 :             :     Assert(LWLockHeldByMe(LogicalRepWorkerLock));
     761                 :             : 
     762                 :         717 :     SetLatch(&worker->proc->procLatch);
     763                 :         717 : }
     764                 :             : 
     765                 :             : /*
     766                 :             :  * Attach to a slot.
     767                 :             :  */
     768                 :             : void
     769                 :         660 : logicalrep_worker_attach(int slot)
     770                 :             : {
     771                 :             :     /* Block concurrent access. */
     772                 :         660 :     LWLockAcquire(LogicalRepWorkerLock, LW_EXCLUSIVE);
     773                 :             : 
     774                 :             :     Assert(slot >= 0 && slot < max_logical_replication_workers);
     775                 :         660 :     MyLogicalRepWorker = &LogicalRepCtx->workers[slot];
     776                 :             : 
     777         [ -  + ]:         660 :     if (!MyLogicalRepWorker->in_use)
     778                 :             :     {
     779                 :           0 :         LWLockRelease(LogicalRepWorkerLock);
     780         [ #  # ]:           0 :         ereport(ERROR,
     781                 :             :                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     782                 :             :                  errmsg("logical replication worker slot %d is empty, cannot attach",
     783                 :             :                         slot)));
     784                 :             :     }
     785                 :             : 
     786         [ -  + ]:         660 :     if (MyLogicalRepWorker->proc)
     787                 :             :     {
     788                 :           0 :         LWLockRelease(LogicalRepWorkerLock);
     789         [ #  # ]:           0 :         ereport(ERROR,
     790                 :             :                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     791                 :             :                  errmsg("logical replication worker slot %d is already used by "
     792                 :             :                         "another worker, cannot attach", slot)));
     793                 :             :     }
     794                 :             : 
     795                 :         660 :     MyLogicalRepWorker->proc = MyProc;
     796                 :         660 :     before_shmem_exit(logicalrep_worker_onexit, (Datum) 0);
     797                 :             : 
     798                 :         660 :     LWLockRelease(LogicalRepWorkerLock);
     799                 :         660 : }
     800                 :             : 
     801                 :             : /*
     802                 :             :  * Stop the parallel apply workers if any, and detach the leader apply worker
     803                 :             :  * (cleans up the worker info).
     804                 :             :  */
     805                 :             : static void
     806                 :         660 : logicalrep_worker_detach(void)
     807                 :             : {
     808                 :             :     /* Stop the parallel apply workers. */
     809         [ +  + ]:         660 :     if (am_leader_apply_worker())
     810                 :             :     {
     811                 :             :         List       *workers;
     812                 :             :         ListCell   *lc;
     813                 :             : 
     814                 :             :         /*
     815                 :             :          * Detach from the error_mq_handle for all parallel apply workers
     816                 :             :          * before terminating them. This prevents the leader apply worker from
     817                 :             :          * receiving the worker termination message and sending it to logs
     818                 :             :          * when the same is already done by the parallel worker.
     819                 :             :          */
     820                 :         412 :         pa_detach_all_error_mq();
     821                 :             : 
     822                 :         412 :         LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
     823                 :             : 
     824                 :         412 :         workers = logicalrep_workers_find(MyLogicalRepWorker->subid, true, false);
     825   [ +  -  +  +  :         831 :         foreach(lc, workers)
                   +  + ]
     826                 :             :         {
     827                 :         419 :             LogicalRepWorker *w = (LogicalRepWorker *) lfirst(lc);
     828                 :             : 
     829   [ +  -  +  + ]:         419 :             if (isParallelApplyWorker(w))
     830                 :           6 :                 logicalrep_worker_stop_internal(w, SIGTERM);
     831                 :             :         }
     832                 :             : 
     833                 :         412 :         LWLockRelease(LogicalRepWorkerLock);
     834                 :             : 
     835                 :         412 :         list_free(workers);
     836                 :             :     }
     837                 :             : 
     838                 :             :     /* Block concurrent access. */
     839                 :         660 :     LWLockAcquire(LogicalRepWorkerLock, LW_EXCLUSIVE);
     840                 :             : 
     841                 :         660 :     logicalrep_worker_cleanup(MyLogicalRepWorker);
     842                 :             : 
     843                 :         660 :     LWLockRelease(LogicalRepWorkerLock);
     844                 :         660 : }
     845                 :             : 
     846                 :             : /*
     847                 :             :  * Clean up worker info.
     848                 :             :  */
     849                 :             : static void
     850                 :         671 : logicalrep_worker_cleanup(LogicalRepWorker *worker)
     851                 :             : {
     852                 :             :     Assert(LWLockHeldByMeInMode(LogicalRepWorkerLock, LW_EXCLUSIVE));
     853                 :             : 
     854                 :         671 :     worker->type = WORKERTYPE_UNKNOWN;
     855                 :         671 :     worker->in_use = false;
     856                 :         671 :     worker->proc = NULL;
     857                 :         671 :     worker->dbid = InvalidOid;
     858                 :         671 :     worker->userid = InvalidOid;
     859                 :         671 :     worker->subid = InvalidOid;
     860                 :         671 :     worker->relid = InvalidOid;
     861                 :         671 :     worker->leader_pid = InvalidPid;
     862                 :         671 :     worker->parallel_apply = false;
     863                 :         671 : }
     864                 :             : 
     865                 :             : /*
     866                 :             :  * Cleanup function for logical replication launcher.
     867                 :             :  *
     868                 :             :  * Called on logical replication launcher exit.
     869                 :             :  */
     870                 :             : static void
     871                 :         530 : logicalrep_launcher_onexit(int code, Datum arg)
     872                 :             : {
     873                 :         530 :     LogicalRepCtx->launcher_pid = 0;
     874                 :         530 : }
     875                 :             : 
     876                 :             : /*
     877                 :             :  * Reset the last_seqsync_start_time of the sequencesync worker in the
     878                 :             :  * subscription's apply worker.
     879                 :             :  *
     880                 :             :  * Note that this value is not stored in the sequencesync worker, because that
     881                 :             :  * has finished already and is about to exit.
     882                 :             :  */
     883                 :             : void
     884                 :           8 : logicalrep_reset_seqsync_start_time(void)
     885                 :             : {
     886                 :             :     LogicalRepWorker *worker;
     887                 :             : 
     888                 :             :     /*
     889                 :             :      * The apply worker can't access last_seqsync_start_time concurrently, so
     890                 :             :      * it is okay to use SHARED lock here. See ProcessSequencesForSync().
     891                 :             :      */
     892                 :           8 :     LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
     893                 :             : 
     894                 :           8 :     worker = logicalrep_worker_find(WORKERTYPE_APPLY,
     895                 :           8 :                                     MyLogicalRepWorker->subid, InvalidOid,
     896                 :             :                                     true);
     897         [ +  - ]:           8 :     if (worker)
     898                 :           8 :         worker->last_seqsync_start_time = 0;
     899                 :             : 
     900                 :           8 :     LWLockRelease(LogicalRepWorkerLock);
     901                 :           8 : }
     902                 :             : 
     903                 :             : /*
     904                 :             :  * Cleanup function.
     905                 :             :  *
     906                 :             :  * Called on logical replication worker exit.
     907                 :             :  */
     908                 :             : static void
     909                 :         660 : logicalrep_worker_onexit(int code, Datum arg)
     910                 :             : {
     911                 :             :     /* Disconnect gracefully from the remote side. */
     912         [ +  + ]:         660 :     if (LogRepWorkerWalRcvConn)
     913                 :         514 :         walrcv_disconnect(LogRepWorkerWalRcvConn);
     914                 :             : 
     915                 :         660 :     logicalrep_worker_detach();
     916                 :             : 
     917                 :             :     /* Cleanup fileset used for streaming transactions. */
     918         [ +  + ]:         660 :     if (MyLogicalRepWorker->stream_fileset != NULL)
     919                 :          14 :         FileSetDeleteAll(MyLogicalRepWorker->stream_fileset);
     920                 :             : 
     921                 :             :     /*
     922                 :             :      * Session level locks may be acquired outside of a transaction in
     923                 :             :      * parallel apply mode and will not be released when the worker
     924                 :             :      * terminates, so manually release all locks before the worker exits.
     925                 :             :      *
     926                 :             :      * The locks will be acquired once the worker is initialized.
     927                 :             :      */
     928         [ +  + ]:         660 :     if (!InitializingApplyWorker)
     929                 :         582 :         LockReleaseAll(DEFAULT_LOCKMETHOD, true);
     930                 :             : 
     931                 :         660 :     ApplyLauncherWakeup();
     932                 :         660 : }
     933                 :             : 
     934                 :             : /*
     935                 :             :  * Count the number of registered (not necessarily running) sync workers
     936                 :             :  * for a subscription.
     937                 :             :  */
     938                 :             : int
     939                 :        1492 : logicalrep_sync_worker_count(Oid subid)
     940                 :             : {
     941                 :             :     int         i;
     942                 :        1492 :     int         res = 0;
     943                 :             : 
     944                 :             :     Assert(LWLockHeldByMe(LogicalRepWorkerLock));
     945                 :             : 
     946                 :             :     /* Search for attached worker for a given subscription id. */
     947         [ +  + ]:        7664 :     for (i = 0; i < max_logical_replication_workers; i++)
     948                 :             :     {
     949                 :        6172 :         LogicalRepWorker *w = &LogicalRepCtx->workers[i];
     950                 :             : 
     951   [ +  +  +  -  :        6172 :         if (w->subid == subid && (isTableSyncWorker(w) || isSequenceSyncWorker(w)))
          +  +  +  -  -  
                      + ]
     952                 :        1461 :             res++;
     953                 :             :     }
     954                 :             : 
     955                 :        1492 :     return res;
     956                 :             : }
     957                 :             : 
     958                 :             : /*
     959                 :             :  * Count the number of registered (but not necessarily running) parallel apply
     960                 :             :  * workers for a subscription.
     961                 :             :  */
     962                 :             : static int
     963                 :         501 : logicalrep_pa_worker_count(Oid subid)
     964                 :             : {
     965                 :             :     int         i;
     966                 :         501 :     int         res = 0;
     967                 :             : 
     968                 :             :     Assert(LWLockHeldByMe(LogicalRepWorkerLock));
     969                 :             : 
     970                 :             :     /*
     971                 :             :      * Scan all attached parallel apply workers, only counting those which
     972                 :             :      * have the given subscription id.
     973                 :             :      */
     974         [ +  + ]:        2631 :     for (i = 0; i < max_logical_replication_workers; i++)
     975                 :             :     {
     976                 :        2130 :         LogicalRepWorker *w = &LogicalRepCtx->workers[i];
     977                 :             : 
     978   [ +  +  +  +  :        2130 :         if (isParallelApplyWorker(w) && w->subid == subid)
                   +  - ]
     979                 :           2 :             res++;
     980                 :             :     }
     981                 :             : 
     982                 :         501 :     return res;
     983                 :             : }
     984                 :             : 
     985                 :             : /*
     986                 :             :  * ApplyLauncherShmemRequest
     987                 :             :  *      Register shared memory space needed for replication launcher
     988                 :             :  */
     989                 :             : static void
     990                 :        1284 : ApplyLauncherShmemRequest(void *arg)
     991                 :             : {
     992                 :             :     Size        size;
     993                 :             : 
     994                 :             :     /*
     995                 :             :      * Need the fixed struct and the array of LogicalRepWorker.
     996                 :             :      */
     997                 :        1284 :     size = sizeof(LogicalRepCtxStruct);
     998                 :        1284 :     size = MAXALIGN(size);
     999                 :        1284 :     size = add_size(size, mul_size(max_logical_replication_workers,
    1000                 :             :                                    sizeof(LogicalRepWorker)));
    1001                 :        1284 :     ShmemRequestStruct(.name = "Logical Replication Launcher Data",
    1002                 :             :                        .size = size,
    1003                 :             :                        .ptr = (void **) &LogicalRepCtx,
    1004                 :             :         );
    1005                 :        1284 : }
    1006                 :             : 
    1007                 :             : /*
    1008                 :             :  * ApplyLauncherRegister
    1009                 :             :  *      Register a background worker running the logical replication launcher.
    1010                 :             :  */
    1011                 :             : void
    1012                 :        1047 : ApplyLauncherRegister(void)
    1013                 :             : {
    1014                 :             :     BackgroundWorker bgw;
    1015                 :             : 
    1016                 :             :     /*
    1017                 :             :      * The logical replication launcher is disabled during binary upgrades, to
    1018                 :             :      * prevent logical replication workers from running on the source cluster.
    1019                 :             :      * That could cause replication origins to move forward after having been
    1020                 :             :      * copied to the target cluster, potentially creating conflicts with the
    1021                 :             :      * copied data files.
    1022                 :             :      */
    1023   [ +  +  +  + ]:        1047 :     if (max_logical_replication_workers == 0 || IsBinaryUpgrade)
    1024                 :          64 :         return;
    1025                 :             : 
    1026                 :         983 :     memset(&bgw, 0, sizeof(bgw));
    1027                 :         983 :     bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
    1028                 :             :         BGWORKER_BACKEND_DATABASE_CONNECTION;
    1029                 :         983 :     bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
    1030                 :         983 :     snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
    1031                 :         983 :     snprintf(bgw.bgw_function_name, BGW_MAXLEN, "ApplyLauncherMain");
    1032                 :         983 :     snprintf(bgw.bgw_name, BGW_MAXLEN,
    1033                 :             :              "logical replication launcher");
    1034                 :         983 :     snprintf(bgw.bgw_type, BGW_MAXLEN,
    1035                 :             :              "logical replication launcher");
    1036                 :         983 :     bgw.bgw_restart_time = 5;
    1037                 :         983 :     bgw.bgw_notify_pid = 0;
    1038                 :         983 :     bgw.bgw_main_arg = (Datum) 0;
    1039                 :             : 
    1040                 :         983 :     RegisterBackgroundWorker(&bgw);
    1041                 :             : }
    1042                 :             : 
    1043                 :             : /*
    1044                 :             :  * ApplyLauncherShmemInit
    1045                 :             :  *      Initialize replication launcher shared memory
    1046                 :             :  */
    1047                 :             : static void
    1048                 :        1281 : ApplyLauncherShmemInit(void *arg)
    1049                 :             : {
    1050                 :             :     int         slot;
    1051                 :             : 
    1052                 :        1281 :     LogicalRepCtx->last_start_dsa = DSA_HANDLE_INVALID;
    1053                 :        1281 :     LogicalRepCtx->last_start_dsh = DSHASH_HANDLE_INVALID;
    1054                 :             : 
    1055                 :             :     /* Initialize memory and spin locks for each worker slot. */
    1056         [ +  + ]:        6368 :     for (slot = 0; slot < max_logical_replication_workers; slot++)
    1057                 :             :     {
    1058                 :        5087 :         LogicalRepWorker *worker = &LogicalRepCtx->workers[slot];
    1059                 :             : 
    1060                 :        5087 :         memset(worker, 0, sizeof(LogicalRepWorker));
    1061                 :        5087 :         SpinLockInit(&worker->relmutex);
    1062                 :             :     }
    1063                 :        1281 : }
    1064                 :             : 
    1065                 :             : /*
    1066                 :             :  * Initialize or attach to the dynamic shared hash table that stores the
    1067                 :             :  * last-start times, if not already done.
    1068                 :             :  * This must be called before accessing the table.
    1069                 :             :  */
    1070                 :             : static void
    1071                 :         957 : logicalrep_launcher_attach_dshmem(void)
    1072                 :             : {
    1073                 :             :     MemoryContext oldcontext;
    1074                 :             : 
    1075                 :             :     /* Quick exit if we already did this. */
    1076         [ +  + ]:         957 :     if (LogicalRepCtx->last_start_dsh != DSHASH_HANDLE_INVALID &&
    1077         [ +  + ]:         896 :         last_start_times != NULL)
    1078                 :         682 :         return;
    1079                 :             : 
    1080                 :             :     /* Otherwise, use a lock to ensure only one process creates the table. */
    1081                 :         275 :     LWLockAcquire(LogicalRepWorkerLock, LW_EXCLUSIVE);
    1082                 :             : 
    1083                 :             :     /* Be sure any local memory allocated by DSA routines is persistent. */
    1084                 :         275 :     oldcontext = MemoryContextSwitchTo(TopMemoryContext);
    1085                 :             : 
    1086         [ +  + ]:         275 :     if (LogicalRepCtx->last_start_dsh == DSHASH_HANDLE_INVALID)
    1087                 :             :     {
    1088                 :             :         /* Initialize dynamic shared hash table for last-start times. */
    1089                 :          61 :         last_start_times_dsa = dsa_create(LWTRANCHE_LAUNCHER_DSA);
    1090                 :          61 :         dsa_pin(last_start_times_dsa);
    1091                 :          61 :         dsa_pin_mapping(last_start_times_dsa);
    1092                 :          61 :         last_start_times = dshash_create(last_start_times_dsa, &dsh_params, NULL);
    1093                 :             : 
    1094                 :             :         /* Store handles in shared memory for other backends to use. */
    1095                 :          61 :         LogicalRepCtx->last_start_dsa = dsa_get_handle(last_start_times_dsa);
    1096                 :          61 :         LogicalRepCtx->last_start_dsh = dshash_get_hash_table_handle(last_start_times);
    1097                 :             :     }
    1098         [ +  - ]:         214 :     else if (!last_start_times)
    1099                 :             :     {
    1100                 :             :         /* Attach to existing dynamic shared hash table. */
    1101                 :         214 :         last_start_times_dsa = dsa_attach(LogicalRepCtx->last_start_dsa);
    1102                 :         214 :         dsa_pin_mapping(last_start_times_dsa);
    1103                 :         214 :         last_start_times = dshash_attach(last_start_times_dsa, &dsh_params,
    1104                 :         214 :                                          LogicalRepCtx->last_start_dsh, NULL);
    1105                 :             :     }
    1106                 :             : 
    1107                 :         275 :     MemoryContextSwitchTo(oldcontext);
    1108                 :         275 :     LWLockRelease(LogicalRepWorkerLock);
    1109                 :             : }
    1110                 :             : 
    1111                 :             : /*
    1112                 :             :  * Set the last-start time for the subscription.
    1113                 :             :  */
    1114                 :             : static void
    1115                 :         244 : ApplyLauncherSetWorkerStartTime(Oid subid, TimestampTz start_time)
    1116                 :             : {
    1117                 :             :     LauncherLastStartTimesEntry *entry;
    1118                 :             :     bool        found;
    1119                 :             : 
    1120                 :         244 :     logicalrep_launcher_attach_dshmem();
    1121                 :             : 
    1122                 :         244 :     entry = dshash_find_or_insert(last_start_times, &subid, &found);
    1123                 :         244 :     entry->last_start_time = start_time;
    1124                 :         244 :     dshash_release_lock(last_start_times, entry);
    1125                 :         244 : }
    1126                 :             : 
    1127                 :             : /*
    1128                 :             :  * Return the last-start time for the subscription, or 0 if there isn't one.
    1129                 :             :  */
    1130                 :             : static TimestampTz
    1131                 :         415 : ApplyLauncherGetWorkerStartTime(Oid subid)
    1132                 :             : {
    1133                 :             :     LauncherLastStartTimesEntry *entry;
    1134                 :             :     TimestampTz ret;
    1135                 :             : 
    1136                 :         415 :     logicalrep_launcher_attach_dshmem();
    1137                 :             : 
    1138                 :         415 :     entry = dshash_find(last_start_times, &subid, false);
    1139         [ +  + ]:         415 :     if (entry == NULL)
    1140                 :         142 :         return 0;
    1141                 :             : 
    1142                 :         273 :     ret = entry->last_start_time;
    1143                 :         273 :     dshash_release_lock(last_start_times, entry);
    1144                 :             : 
    1145                 :         273 :     return ret;
    1146                 :             : }
    1147                 :             : 
    1148                 :             : /*
    1149                 :             :  * Remove the last-start-time entry for the subscription, if one exists.
    1150                 :             :  *
    1151                 :             :  * This has two use-cases: to remove the entry related to a subscription
    1152                 :             :  * that's been deleted or disabled (just to avoid leaking shared memory),
    1153                 :             :  * and to allow immediate restart of an apply worker that has exited
    1154                 :             :  * due to subscription parameter changes.
    1155                 :             :  */
    1156                 :             : void
    1157                 :         298 : ApplyLauncherForgetWorkerStartTime(Oid subid)
    1158                 :             : {
    1159                 :         298 :     logicalrep_launcher_attach_dshmem();
    1160                 :             : 
    1161                 :         298 :     (void) dshash_delete_key(last_start_times, &subid);
    1162                 :         298 : }
    1163                 :             : 
    1164                 :             : /*
    1165                 :             :  * Wakeup the launcher on commit if requested.
    1166                 :             :  */
    1167                 :             : void
    1168                 :      646745 : AtEOXact_ApplyLauncher(bool isCommit)
    1169                 :             : {
    1170         [ +  + ]:      646745 :     if (isCommit)
    1171                 :             :     {
    1172         [ +  + ]:      610583 :         if (on_commit_launcher_wakeup)
    1173                 :         179 :             ApplyLauncherWakeup();
    1174                 :             :     }
    1175                 :             : 
    1176                 :      646745 :     on_commit_launcher_wakeup = false;
    1177                 :      646745 : }
    1178                 :             : 
    1179                 :             : /*
    1180                 :             :  * Request wakeup of the launcher on commit of the transaction.
    1181                 :             :  *
    1182                 :             :  * This is used to send launcher signal to stop sleeping and process the
    1183                 :             :  * subscriptions when current transaction commits. Should be used when new
    1184                 :             :  * tuple was added to the pg_subscription catalog.
    1185                 :             :  */
    1186                 :             : void
    1187                 :         180 : ApplyLauncherWakeupAtCommit(void)
    1188                 :             : {
    1189         [ +  + ]:         180 :     if (!on_commit_launcher_wakeup)
    1190                 :         179 :         on_commit_launcher_wakeup = true;
    1191                 :         180 : }
    1192                 :             : 
    1193                 :             : /*
    1194                 :             :  * Wakeup the launcher immediately.
    1195                 :             :  */
    1196                 :             : void
    1197                 :         894 : ApplyLauncherWakeup(void)
    1198                 :             : {
    1199         [ +  + ]:         894 :     if (LogicalRepCtx->launcher_pid != 0)
    1200                 :         882 :         kill(LogicalRepCtx->launcher_pid, SIGUSR1);
    1201                 :         894 : }
    1202                 :             : 
    1203                 :             : /*
    1204                 :             :  * Main loop for the apply launcher process.
    1205                 :             :  */
    1206                 :             : void
    1207                 :         530 : ApplyLauncherMain(Datum main_arg)
    1208                 :             : {
    1209                 :             :     List       *retained_dbids;
    1210                 :             : 
    1211         [ +  + ]:         530 :     ereport(DEBUG1,
    1212                 :             :             (errmsg_internal("logical replication launcher started")));
    1213                 :             : 
    1214                 :         530 :     before_shmem_exit(logicalrep_launcher_onexit, (Datum) 0);
    1215                 :             : 
    1216                 :             :     Assert(LogicalRepCtx->launcher_pid == 0);
    1217                 :         530 :     LogicalRepCtx->launcher_pid = MyProcPid;
    1218                 :             : 
    1219                 :             :     /* Establish signal handlers. */
    1220                 :         530 :     pqsignal(SIGHUP, SignalHandlerForConfigReload);
    1221                 :         530 :     BackgroundWorkerUnblockSignals();
    1222                 :             : 
    1223                 :             :     /*
    1224                 :             :      * Establish connection to nailed catalogs (we only ever access
    1225                 :             :      * pg_subscription).
    1226                 :             :      */
    1227                 :         530 :     BackgroundWorkerInitializeConnection(NULL, NULL, 0);
    1228                 :             : 
    1229                 :             :     /*
    1230                 :             :      * Databases with actively-retaining subscriptions as of the previous
    1231                 :             :      * cycle. Local memory only, so every such database counts as new after a
    1232                 :             :      * launcher restart, resetting the slot's xmin once at startup.
    1233                 :             :      */
    1234                 :         530 :     retained_dbids = NIL;
    1235                 :             : 
    1236                 :             :     /*
    1237                 :             :      * Acquire the conflict detection slot at startup to ensure it can be
    1238                 :             :      * dropped if no longer needed after a restart.
    1239                 :             :      */
    1240                 :         530 :     acquire_conflict_slot_if_exists();
    1241                 :             : 
    1242                 :             :     /* Enter main loop */
    1243                 :             :     for (;;)
    1244                 :        2837 :     {
    1245                 :             :         int         rc;
    1246                 :             :         List       *sublist;
    1247                 :             :         ListCell   *lc;
    1248                 :             :         MemoryContext subctx;
    1249                 :             :         MemoryContext oldctx;
    1250                 :        3367 :         long        wait_time = DEFAULT_NAPTIME_PER_CYCLE;
    1251                 :        3367 :         bool        can_update_xmin = true;
    1252                 :        3367 :         bool        retain_dead_tuples = false;
    1253                 :        3367 :         bool        reset_done = false;
    1254                 :        3367 :         TransactionId xmin = InvalidTransactionId;
    1255                 :        3367 :         List       *current_dbids = NIL;
    1256                 :             : 
    1257         [ +  + ]:        3367 :         CHECK_FOR_INTERRUPTS();
    1258                 :             : 
    1259                 :             :         /* Use temporary context to avoid leaking memory across cycles. */
    1260                 :        3366 :         subctx = AllocSetContextCreate(TopMemoryContext,
    1261                 :             :                                        "Logical Replication Launcher sublist",
    1262                 :             :                                        ALLOCSET_DEFAULT_SIZES);
    1263                 :        3366 :         oldctx = MemoryContextSwitchTo(subctx);
    1264                 :             : 
    1265                 :             :         /*
    1266                 :             :          * Start any missing workers for enabled subscriptions.
    1267                 :             :          *
    1268                 :             :          * Also, during the iteration through all subscriptions, we compute
    1269                 :             :          * the minimum XID required to protect deleted tuples for conflict
    1270                 :             :          * detection if one of the subscription enables retain_dead_tuples
    1271                 :             :          * option.
    1272                 :             :          */
    1273                 :        3366 :         sublist = get_subscription_list();
    1274   [ +  +  +  +  :        4490 :         foreach(lc, sublist)
                   +  + ]
    1275                 :             :         {
    1276                 :        1124 :             Subscription *sub = (Subscription *) lfirst(lc);
    1277                 :             :             LogicalRepWorker *w;
    1278                 :             :             TimestampTz last_start;
    1279                 :             :             TimestampTz now;
    1280                 :             :             long        elapsed;
    1281                 :             : 
    1282         [ +  + ]:        1124 :             if (sub->retaindeadtuples)
    1283                 :             :             {
    1284                 :          86 :                 retain_dead_tuples = true;
    1285                 :             : 
    1286                 :             :                 /*
    1287                 :             :                  * Create a (physical) replication slot to retain information
    1288                 :             :                  * necessary for conflict detection such as dead tuples,
    1289                 :             :                  * commit timestamps, and origins.
    1290                 :             :                  *
    1291                 :             :                  * The slot is created before starting the apply worker to
    1292                 :             :                  * prevent the worker from unnecessarily maintaining its
    1293                 :             :                  * oldest_nonremovable_xid.
    1294                 :             :                  *
    1295                 :             :                  * The slot is created even for a disabled subscription to
    1296                 :             :                  * ensure that conflict-related information is available when
    1297                 :             :                  * applying remote changes that occurred before the
    1298                 :             :                  * subscription was enabled.
    1299                 :             :                  */
    1300                 :          86 :                 CreateConflictDetectionSlot();
    1301                 :             : 
    1302         [ +  - ]:          86 :                 if (sub->retentionactive)
    1303                 :             :                 {
    1304                 :             :                     /* Remember the retained databases for the next cycle. */
    1305                 :          86 :                     current_dbids = list_append_unique_oid(current_dbids,
    1306                 :             :                                                            sub->dbid);
    1307                 :             : 
    1308                 :             :                     /*
    1309                 :             :                      * Can't advance xmin of the slot unless all the
    1310                 :             :                      * subscriptions actively retaining dead tuples are
    1311                 :             :                      * enabled. This is required to ensure that we don't
    1312                 :             :                      * advance the xmin of CONFLICT_DETECTION_SLOT if one of
    1313                 :             :                      * the subscriptions is not enabled. Otherwise, we won't
    1314                 :             :                      * be able to detect conflicts reliably for such a
    1315                 :             :                      * subscription even though it has set the
    1316                 :             :                      * retain_dead_tuples option.
    1317                 :             :                      */
    1318                 :          86 :                     can_update_xmin &= sub->enabled;
    1319                 :             : 
    1320                 :             :                     /*
    1321                 :             :                      * A retaining apply worker starts with the slot's xmin as
    1322                 :             :                      * its oldest_nonremovable_xid (see
    1323                 :             :                      * logicalrep_worker_launch()), so a database whose oldest
    1324                 :             :                      * active transaction ID precedes that xmin would trip the
    1325                 :             :                      * assertion in get_candidate_xid(). Reset the xmin before
    1326                 :             :                      * starting the worker, which also seeds it on the first
    1327                 :             :                      * cycle. The set of retained databases is keyed on
    1328                 :             :                      * subretentionactive, so a subscription resuming
    1329                 :             :                      * retention is also treated as a new arrival. One call
    1330                 :             :                      * per cycle is enough, as a single reset covers all such
    1331                 :             :                      * databases.
    1332                 :             :                      */
    1333         [ +  - ]:          86 :                     if (!reset_done &&
    1334         [ +  - ]:          86 :                         (!TransactionIdIsValid(MyReplicationSlot->data.xmin) ||
    1335         [ +  + ]:          86 :                          !list_member_oid(retained_dbids, sub->dbid)))
    1336                 :             :                     {
    1337                 :           4 :                         reset_conflict_slot_xmin_to_safe_horizon();
    1338                 :           4 :                         reset_done = true;
    1339                 :             :                     }
    1340                 :             :                 }
    1341                 :             :             }
    1342                 :             : 
    1343         [ +  + ]:        1124 :             if (!sub->enabled)
    1344                 :          90 :                 continue;
    1345                 :             : 
    1346                 :        1034 :             LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
    1347                 :        1034 :             w = logicalrep_worker_find(WORKERTYPE_APPLY, sub->oid, InvalidOid,
    1348                 :             :                                        false);
    1349                 :             : 
    1350                 :             :             /*
    1351                 :             :              * Compute the minimum xmin required to protect dead tuples
    1352                 :             :              * required for conflict detection among all running apply
    1353                 :             :              * workers.
    1354                 :             :              */
    1355   [ +  -  +  + ]:        2068 :             can_update_xmin = can_update_xmin &&
    1356                 :        1034 :                 collect_min_nonremovable_xid(sub, w, &xmin);
    1357                 :             : 
    1358                 :        1034 :             LWLockRelease(LogicalRepWorkerLock);
    1359                 :             : 
    1360         [ +  + ]:        1034 :             if (w != NULL)
    1361                 :         619 :                 continue;       /* worker is running already */
    1362                 :             : 
    1363                 :             :             /*
    1364                 :             :              * If the worker is eligible to start now, launch it.  Otherwise,
    1365                 :             :              * adjust wait_time so that we'll wake up as soon as it can be
    1366                 :             :              * started.
    1367                 :             :              *
    1368                 :             :              * Each subscription's apply worker can only be restarted once per
    1369                 :             :              * wal_retrieve_retry_interval, so that errors do not cause us to
    1370                 :             :              * repeatedly restart the worker as fast as possible.  In cases
    1371                 :             :              * where a restart is expected (e.g., subscription parameter
    1372                 :             :              * changes), another process should remove the last-start entry
    1373                 :             :              * for the subscription so that the worker can be restarted
    1374                 :             :              * without waiting for wal_retrieve_retry_interval to elapse.
    1375                 :             :              */
    1376                 :         415 :             last_start = ApplyLauncherGetWorkerStartTime(sub->oid);
    1377                 :         415 :             now = GetCurrentTimestamp();
    1378         [ +  + ]:         415 :             if (last_start == 0 ||
    1379         [ +  + ]:         273 :                 (elapsed = TimestampDifferenceMilliseconds(last_start, now)) >= wal_retrieve_retry_interval)
    1380                 :             :             {
    1381                 :         244 :                 ApplyLauncherSetWorkerStartTime(sub->oid, now);
    1382         [ +  + ]:         255 :                 if (!logicalrep_worker_launch(WORKERTYPE_APPLY,
    1383                 :         244 :                                               sub->dbid, sub->oid, sub->name,
    1384                 :             :                                               sub->owner, InvalidOid,
    1385                 :             :                                               DSM_HANDLE_INVALID,
    1386         [ +  + ]:         246 :                                               sub->retaindeadtuples &&
    1387         [ +  - ]:         246 :                                               sub->retentionactive))
    1388                 :             :                 {
    1389                 :             :                     /*
    1390                 :             :                      * We get here either if we failed to launch a worker
    1391                 :             :                      * (perhaps for resource-exhaustion reasons) or if we
    1392                 :             :                      * launched one but it immediately quit.  Either way, it
    1393                 :             :                      * seems appropriate to try again after
    1394                 :             :                      * wal_retrieve_retry_interval.
    1395                 :             :                      */
    1396                 :          11 :                     wait_time = Min(wait_time,
    1397                 :             :                                     wal_retrieve_retry_interval);
    1398                 :             :                 }
    1399                 :             :             }
    1400                 :             :             else
    1401                 :             :             {
    1402                 :         171 :                 wait_time = Min(wait_time,
    1403                 :             :                                 wal_retrieve_retry_interval - elapsed);
    1404                 :             :             }
    1405                 :             :         }
    1406                 :             : 
    1407                 :             :         /*
    1408                 :             :          * Drop the CONFLICT_DETECTION_SLOT slot if there is no subscription
    1409                 :             :          * that requires us to retain dead tuples. Otherwise, if required,
    1410                 :             :          * advance the slot's xmin to protect dead tuples required for the
    1411                 :             :          * conflict detection.
    1412                 :             :          *
    1413                 :             :          * Additionally, if all apply workers for subscriptions with
    1414                 :             :          * retain_dead_tuples enabled have requested to stop retention, the
    1415                 :             :          * slot's xmin will be set to InvalidTransactionId allowing the
    1416                 :             :          * removal of dead tuples.
    1417                 :             :          */
    1418         [ +  + ]:        3366 :         if (MyReplicationSlot)
    1419                 :             :         {
    1420         [ +  + ]:          87 :             if (!retain_dead_tuples)
    1421                 :           1 :                 ReplicationSlotDropAcquired(false);
    1422         [ +  + ]:          86 :             else if (can_update_xmin)
    1423                 :          82 :                 update_conflict_slot_xmin(xmin);
    1424                 :             :         }
    1425                 :             : 
    1426                 :             :         /* Switch back to original memory context. */
    1427                 :        3366 :         MemoryContextSwitchTo(oldctx);
    1428                 :             : 
    1429                 :             :         /*
    1430                 :             :          * Remember the current set of retained databases for the next cycle,
    1431                 :             :          * in long-lived memory.
    1432                 :             :          */
    1433                 :        3366 :         list_free(retained_dbids);
    1434                 :        3366 :         retained_dbids = list_copy(current_dbids);
    1435                 :             : 
    1436                 :             :         /* Clean the temporary memory. */
    1437                 :        3366 :         MemoryContextDelete(subctx);
    1438                 :             : 
    1439                 :             :         /* Wait for more work. */
    1440                 :        3366 :         rc = WaitLatch(MyLatch,
    1441                 :             :                        WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
    1442                 :             :                        wait_time,
    1443                 :             :                        WAIT_EVENT_LOGICAL_LAUNCHER_MAIN);
    1444                 :             : 
    1445         [ +  + ]:        3363 :         if (rc & WL_LATCH_SET)
    1446                 :             :         {
    1447                 :        3329 :             ResetLatch(MyLatch);
    1448         [ +  + ]:        3329 :             CHECK_FOR_INTERRUPTS();
    1449                 :             :         }
    1450                 :             : 
    1451         [ +  + ]:        2837 :         if (ConfigReloadPending)
    1452                 :             :         {
    1453                 :          54 :             ConfigReloadPending = false;
    1454                 :          54 :             ProcessConfigFile(PGC_SIGHUP);
    1455                 :             :         }
    1456                 :             :     }
    1457                 :             : 
    1458                 :             :     /* Not reachable */
    1459                 :             : }
    1460                 :             : 
    1461                 :             : /*
    1462                 :             :  * Determine the minimum non-removable transaction ID required for conflict
    1463                 :             :  * detection, accumulating the contribution of the given subscription in
    1464                 :             :  * *xmin. Subscriptions that are not actively retaining conflict information
    1465                 :             :  * are ignored.
    1466                 :             :  *
    1467                 :             :  * Returns false if the slot's xmin cannot be advanced in this cycle, which is
    1468                 :             :  * the case when the apply worker is not running (worker is NULL) or does not
    1469                 :             :  * yet hold a valid oldest_nonremovable_xid.
    1470                 :             :  *
    1471                 :             :  * The caller must hold LogicalRepWorkerLock, to prevent accessing invalid
    1472                 :             :  * worker data in scenarios where a worker might exit and reset its state
    1473                 :             :  * concurrently.
    1474                 :             :  */
    1475                 :             : static bool
    1476                 :        1034 : collect_min_nonremovable_xid(Subscription *sub, LogicalRepWorker *worker,
    1477                 :             :                              TransactionId *xmin)
    1478                 :             : {
    1479                 :        1034 :     TransactionId nonremovable_xid = InvalidTransactionId;
    1480                 :             : 
    1481                 :             :     /* Skip subscriptions that are not actively retaining dead tuples */
    1482   [ +  +  -  + ]:        1034 :     if (!sub->retaindeadtuples || !sub->retentionactive)
    1483                 :         950 :         return true;
    1484                 :             : 
    1485                 :             :     /*
    1486                 :             :      * The replication slot for conflict detection must be created before the
    1487                 :             :      * worker starts.
    1488                 :             :      */
    1489                 :             :     Assert(MyReplicationSlot);
    1490                 :             : 
    1491         [ +  + ]:          84 :     if (worker)
    1492                 :             :     {
    1493                 :          82 :         SpinLockAcquire(&worker->relmutex);
    1494                 :          82 :         nonremovable_xid = worker->oldest_nonremovable_xid;
    1495                 :          82 :         SpinLockRelease(&worker->relmutex);
    1496                 :             :     }
    1497                 :             : 
    1498                 :             :     /*
    1499                 :             :      * Skip the xmin update if the apply worker's oldest_nonremovable_xid is
    1500                 :             :      * invalid, or if the worker is not running (worker is NULL).
    1501                 :             :      *
    1502                 :             :      * Although this function proceeds only when retentionactive is true,
    1503                 :             :      * worker's oldest_nonremovable_xid being invalid can still happen when
    1504                 :             :      * the worker has stopped retention after the launcher fetched
    1505                 :             :      * retentionactive, or when the worker recently enabled retention but
    1506                 :             :      * hasn't restarted yet and the XID is still invalid. In either case, it's
    1507                 :             :      * unnecessary to update the slot's xmin in this cycle.
    1508                 :             :      */
    1509         [ +  + ]:          84 :     if (!TransactionIdIsValid(nonremovable_xid))
    1510                 :           2 :         return false;
    1511                 :             : 
    1512   [ -  +  -  - ]:          82 :     if (!TransactionIdIsValid(*xmin) ||
    1513                 :           0 :         TransactionIdPrecedes(nonremovable_xid, *xmin))
    1514                 :          82 :         *xmin = nonremovable_xid;
    1515                 :             : 
    1516                 :          82 :     return true;
    1517                 :             : }
    1518                 :             : 
    1519                 :             : /*
    1520                 :             :  * Acquire the replication slot used to retain information for conflict
    1521                 :             :  * detection, if it exists.
    1522                 :             :  *
    1523                 :             :  * Return true if successfully acquired, otherwise return false.
    1524                 :             :  */
    1525                 :             : static bool
    1526                 :         530 : acquire_conflict_slot_if_exists(void)
    1527                 :             : {
    1528         [ +  + ]:         530 :     if (!SearchNamedReplicationSlot(CONFLICT_DETECTION_SLOT, true))
    1529                 :         529 :         return false;
    1530                 :             : 
    1531                 :           1 :     ReplicationSlotAcquire(CONFLICT_DETECTION_SLOT, true, false);
    1532                 :           1 :     return true;
    1533                 :             : }
    1534                 :             : 
    1535                 :             : /*
    1536                 :             :  * Update the xmin the replication slot used to retain information required
    1537                 :             :  * for conflict detection.
    1538                 :             :  */
    1539                 :             : static void
    1540                 :          82 : update_conflict_slot_xmin(TransactionId new_xmin)
    1541                 :             : {
    1542                 :             :     Assert(MyReplicationSlot);
    1543                 :             :     Assert(!TransactionIdIsValid(new_xmin) ||
    1544                 :             :            TransactionIdPrecedesOrEquals(MyReplicationSlot->data.xmin, new_xmin));
    1545                 :             : 
    1546                 :             :     /* Return if the xmin value of the slot cannot be updated */
    1547         [ +  + ]:          82 :     if (TransactionIdEquals(MyReplicationSlot->data.xmin, new_xmin))
    1548                 :          67 :         return;
    1549                 :             : 
    1550                 :          15 :     SpinLockAcquire(&MyReplicationSlot->mutex);
    1551                 :          15 :     MyReplicationSlot->effective_xmin = new_xmin;
    1552                 :          15 :     MyReplicationSlot->data.xmin = new_xmin;
    1553                 :          15 :     SpinLockRelease(&MyReplicationSlot->mutex);
    1554                 :             : 
    1555         [ -  + ]:          15 :     elog(DEBUG1, "updated xmin: %u", MyReplicationSlot->data.xmin);
    1556                 :             : 
    1557                 :          15 :     ReplicationSlotMarkDirty();
    1558                 :          15 :     ReplicationSlotsComputeRequiredXmin(false);
    1559                 :             : 
    1560                 :             :     /*
    1561                 :             :      * Like PhysicalConfirmReceivedLocation(), do not save slot information
    1562                 :             :      * each time. This is acceptable because all concurrent transactions on
    1563                 :             :      * the publisher that require the data preceding the slot's xmin should
    1564                 :             :      * have already been applied and flushed on the subscriber before the xmin
    1565                 :             :      * is advanced. So, even if the slot's xmin regresses after a restart, it
    1566                 :             :      * will be advanced again in the next cycle. Therefore, no data required
    1567                 :             :      * for conflict detection will be prematurely removed.
    1568                 :             :      */
    1569                 :          15 :     return;
    1570                 :             : }
    1571                 :             : 
    1572                 :             : /*
    1573                 :             :  * Reset the xmin of the conflict detection slot to the cluster-wide safe
    1574                 :             :  * decoding horizon, which accounts for all running transactions and is
    1575                 :             :  * therefore safe for an apply worker in any database. Called when the slot is
    1576                 :             :  * created and when a database newly needs retention.
    1577                 :             :  *
    1578                 :             :  * The xmin is left unchanged if it is already at or before the horizon.
    1579                 :             :  */
    1580                 :             : static void
    1581                 :           8 : reset_conflict_slot_xmin_to_safe_horizon(void)
    1582                 :             : {
    1583                 :             :     TransactionId xmin_horizon;
    1584                 :             :     TransactionId old_xmin;
    1585                 :             : 
    1586                 :             :     Assert(MyReplicationSlot);
    1587                 :             : 
    1588                 :           8 :     LWLockAcquire(ReplicationSlotControlLock, LW_EXCLUSIVE);
    1589                 :           8 :     LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
    1590                 :             : 
    1591                 :           8 :     xmin_horizon = GetOldestSafeDecodingTransactionId(false);
    1592                 :           8 :     old_xmin = MyReplicationSlot->data.xmin;
    1593                 :             : 
    1594                 :             :     /*
    1595                 :             :      * Nothing to do if the current xmin is valid and not newer than the
    1596                 :             :      * horizon. The xmin must not be advanced here, as advancing is only safe
    1597                 :             :      * once the apply workers confirm that all concurrent transactions have
    1598                 :             :      * been applied. Regressing it only retains more than necessary, and the
    1599                 :             :      * workers will advance it again in later cycles.
    1600                 :             :      */
    1601   [ +  +  +  - ]:          12 :     if (TransactionIdIsValid(old_xmin) &&
    1602                 :           4 :         TransactionIdPrecedesOrEquals(old_xmin, xmin_horizon))
    1603                 :             :     {
    1604                 :           4 :         LWLockRelease(ProcArrayLock);
    1605                 :           4 :         LWLockRelease(ReplicationSlotControlLock);
    1606                 :           4 :         return;
    1607                 :             :     }
    1608                 :             : 
    1609                 :           4 :     SpinLockAcquire(&MyReplicationSlot->mutex);
    1610                 :           4 :     MyReplicationSlot->effective_xmin = xmin_horizon;
    1611                 :           4 :     MyReplicationSlot->data.xmin = xmin_horizon;
    1612                 :           4 :     SpinLockRelease(&MyReplicationSlot->mutex);
    1613                 :             : 
    1614         [ -  + ]:           4 :     elog(DEBUG1, "reset conflict detection slot's xmin to %u", xmin_horizon);
    1615                 :             : 
    1616                 :           4 :     ReplicationSlotsComputeRequiredXmin(true);
    1617                 :             : 
    1618                 :           4 :     LWLockRelease(ProcArrayLock);
    1619                 :           4 :     LWLockRelease(ReplicationSlotControlLock);
    1620                 :             : 
    1621                 :             :     /* Write this slot to disk */
    1622                 :           4 :     ReplicationSlotMarkDirty();
    1623                 :           4 :     ReplicationSlotSave();
    1624                 :             : }
    1625                 :             : 
    1626                 :             : /*
    1627                 :             :  * Create and acquire the replication slot used to retain information for
    1628                 :             :  * conflict detection, if not yet.
    1629                 :             :  *
    1630                 :             :  * A physical slot is enough, as no logical decoding is going to be performed
    1631                 :             :  * through it.  In fact, the slot will only be used through its xmin horizon
    1632                 :             :  * to prevent the removal of dead tuples and commit timestamp data required by
    1633                 :             :  * subscriptions with retain_dead_tuples enabled in any database.
    1634                 :             :  */
    1635                 :             : void
    1636                 :          87 : CreateConflictDetectionSlot(void)
    1637                 :             : {
    1638                 :             :     /* Exit early, if the replication slot is already created and acquired */
    1639         [ +  + ]:          87 :     if (MyReplicationSlot)
    1640                 :          83 :         return;
    1641                 :             : 
    1642         [ +  - ]:           4 :     ereport(LOG,
    1643                 :             :             errmsg("creating replication conflict detection slot"));
    1644                 :             : 
    1645                 :           4 :     ReplicationSlotCreate(CONFLICT_DETECTION_SLOT, false, RS_PERSISTENT, false,
    1646                 :             :                           false, false, false);
    1647                 :             : 
    1648                 :           4 :     reset_conflict_slot_xmin_to_safe_horizon();
    1649                 :             : }
    1650                 :             : 
    1651                 :             : /*
    1652                 :             :  * Is current process the logical replication launcher?
    1653                 :             :  */
    1654                 :             : bool
    1655                 :        2901 : IsLogicalLauncher(void)
    1656                 :             : {
    1657                 :        2901 :     return LogicalRepCtx->launcher_pid == MyProcPid;
    1658                 :             : }
    1659                 :             : 
    1660                 :             : /*
    1661                 :             :  * Return the pid of the leader apply worker if the given pid is the pid of a
    1662                 :             :  * parallel apply worker, otherwise, return InvalidPid.
    1663                 :             :  */
    1664                 :             : pid_t
    1665                 :         842 : GetLeaderApplyWorkerPid(pid_t pid)
    1666                 :             : {
    1667                 :         842 :     int         leader_pid = InvalidPid;
    1668                 :             :     int         i;
    1669                 :             : 
    1670                 :         842 :     LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
    1671                 :             : 
    1672         [ +  + ]:        4210 :     for (i = 0; i < max_logical_replication_workers; i++)
    1673                 :             :     {
    1674                 :        3368 :         LogicalRepWorker *w = &LogicalRepCtx->workers[i];
    1675                 :             : 
    1676   [ +  +  -  +  :        3368 :         if (isParallelApplyWorker(w) && w->proc && pid == w->proc->pid)
             -  -  -  - ]
    1677                 :             :         {
    1678                 :           0 :             leader_pid = w->leader_pid;
    1679                 :           0 :             break;
    1680                 :             :         }
    1681                 :             :     }
    1682                 :             : 
    1683                 :         842 :     LWLockRelease(LogicalRepWorkerLock);
    1684                 :             : 
    1685                 :         842 :     return leader_pid;
    1686                 :             : }
    1687                 :             : 
    1688                 :             : /*
    1689                 :             :  * Returns state of the subscriptions.
    1690                 :             :  */
    1691                 :             : Datum
    1692                 :           1 : pg_stat_get_subscription(PG_FUNCTION_ARGS)
    1693                 :             : {
    1694                 :             : #define PG_STAT_GET_SUBSCRIPTION_COLS   10
    1695         [ -  + ]:           1 :     Oid         subid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);
    1696                 :             :     int         i;
    1697                 :           1 :     ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
    1698                 :             : 
    1699                 :           1 :     InitMaterializedSRF(fcinfo, 0);
    1700                 :             : 
    1701                 :             :     /* Make sure we get consistent view of the workers. */
    1702                 :           1 :     LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
    1703                 :             : 
    1704         [ +  + ]:           5 :     for (i = 0; i < max_logical_replication_workers; i++)
    1705                 :             :     {
    1706                 :             :         /* for each row */
    1707                 :           4 :         Datum       values[PG_STAT_GET_SUBSCRIPTION_COLS] = {0};
    1708                 :           4 :         bool        nulls[PG_STAT_GET_SUBSCRIPTION_COLS] = {0};
    1709                 :             :         int         worker_pid;
    1710                 :             :         LogicalRepWorker worker;
    1711                 :             : 
    1712                 :           4 :         memcpy(&worker, &LogicalRepCtx->workers[i],
    1713                 :             :                sizeof(LogicalRepWorker));
    1714   [ +  +  -  + ]:           4 :         if (!worker.proc || !IsBackendPid(worker.proc->pid))
    1715                 :           2 :             continue;
    1716                 :             : 
    1717   [ -  +  -  - ]:           2 :         if (OidIsValid(subid) && worker.subid != subid)
    1718                 :           0 :             continue;
    1719                 :             : 
    1720                 :           2 :         worker_pid = worker.proc->pid;
    1721                 :             : 
    1722                 :           2 :         values[0] = ObjectIdGetDatum(worker.subid);
    1723   [ +  -  -  + ]:           2 :         if (isTableSyncWorker(&worker))
    1724                 :           0 :             values[1] = ObjectIdGetDatum(worker.relid);
    1725                 :             :         else
    1726                 :           2 :             nulls[1] = true;
    1727                 :           2 :         values[2] = Int32GetDatum(worker_pid);
    1728                 :             : 
    1729   [ +  -  -  + ]:           2 :         if (isParallelApplyWorker(&worker))
    1730                 :           0 :             values[3] = Int32GetDatum(worker.leader_pid);
    1731                 :             :         else
    1732                 :           2 :             nulls[3] = true;
    1733                 :             : 
    1734         [ -  + ]:           2 :         if (!XLogRecPtrIsValid(worker.last_lsn))
    1735                 :           0 :             nulls[4] = true;
    1736                 :             :         else
    1737                 :           2 :             values[4] = LSNGetDatum(worker.last_lsn);
    1738         [ -  + ]:           2 :         if (worker.last_send_time == 0)
    1739                 :           0 :             nulls[5] = true;
    1740                 :             :         else
    1741                 :           2 :             values[5] = TimestampTzGetDatum(worker.last_send_time);
    1742         [ -  + ]:           2 :         if (worker.last_recv_time == 0)
    1743                 :           0 :             nulls[6] = true;
    1744                 :             :         else
    1745                 :           2 :             values[6] = TimestampTzGetDatum(worker.last_recv_time);
    1746         [ -  + ]:           2 :         if (!XLogRecPtrIsValid(worker.reply_lsn))
    1747                 :           0 :             nulls[7] = true;
    1748                 :             :         else
    1749                 :           2 :             values[7] = LSNGetDatum(worker.reply_lsn);
    1750         [ -  + ]:           2 :         if (worker.reply_time == 0)
    1751                 :           0 :             nulls[8] = true;
    1752                 :             :         else
    1753                 :           2 :             values[8] = TimestampTzGetDatum(worker.reply_time);
    1754                 :             : 
    1755   [ +  -  -  -  :           2 :         switch (worker.type)
                   -  - ]
    1756                 :             :         {
    1757                 :           2 :             case WORKERTYPE_APPLY:
    1758                 :           2 :                 values[9] = CStringGetTextDatum("apply");
    1759                 :           2 :                 break;
    1760                 :           0 :             case WORKERTYPE_PARALLEL_APPLY:
    1761                 :           0 :                 values[9] = CStringGetTextDatum("parallel apply");
    1762                 :           0 :                 break;
    1763                 :           0 :             case WORKERTYPE_SEQUENCESYNC:
    1764                 :           0 :                 values[9] = CStringGetTextDatum("sequence synchronization");
    1765                 :           0 :                 break;
    1766                 :           0 :             case WORKERTYPE_TABLESYNC:
    1767                 :           0 :                 values[9] = CStringGetTextDatum("table synchronization");
    1768                 :           0 :                 break;
    1769                 :           0 :             case WORKERTYPE_UNKNOWN:
    1770                 :             :                 /* Should never happen. */
    1771         [ #  # ]:           0 :                 elog(ERROR, "unknown worker type");
    1772                 :             :         }
    1773                 :             : 
    1774                 :           2 :         tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
    1775                 :             :                              values, nulls);
    1776                 :             : 
    1777                 :             :         /*
    1778                 :             :          * If only a single subscription was requested, and we found it,
    1779                 :             :          * break.
    1780                 :             :          */
    1781         [ -  + ]:           2 :         if (OidIsValid(subid))
    1782                 :           0 :             break;
    1783                 :             :     }
    1784                 :             : 
    1785                 :           1 :     LWLockRelease(LogicalRepWorkerLock);
    1786                 :             : 
    1787                 :           1 :     return (Datum) 0;
    1788                 :             : }
        

Generated by: LCOV version 2.0-1