coverage.tar
application/x-tar
Filename: coverage.tar
Type: application/x-tar
Part: 1
0001-Add-logical_decoding_work_mem-to-limit-ReorderBuffer-patch_coverage/ 0000755 0001750 0000000 00000000000 13560456264 025734 5 ustar vignesh root 0001-Add-logical_decoding_work_mem-to-limit-ReorderBuffer-patch_coverage/walsender.c.gcov 0000664 0001750 0000000 00000616157 13560210457 031033 0 ustar vignesh root -: 0:Source:walsender.c
-: 0:Graph:./walsender.gcno
-: 0:Data:./walsender.gcda
-: 0:Runs:6533
-: 0:Programs:1
-: 1:/*-------------------------------------------------------------------------
-: 2: *
-: 3: * walsender.c
-: 4: *
-: 5: * The WAL sender process (walsender) is new as of Postgres 9.0. It takes
-: 6: * care of sending XLOG from the primary server to a single recipient.
-: 7: * (Note that there can be more than one walsender process concurrently.)
-: 8: * It is started by the postmaster when the walreceiver of a standby server
-: 9: * connects to the primary server and requests XLOG streaming replication.
-: 10: *
-: 11: * A walsender is similar to a regular backend, ie. there is a one-to-one
-: 12: * relationship between a connection and a walsender process, but instead
-: 13: * of processing SQL queries, it understands a small set of special
-: 14: * replication-mode commands. The START_REPLICATION command begins streaming
-: 15: * WAL to the client. While streaming, the walsender keeps reading XLOG
-: 16: * records from the disk and sends them to the standby server over the
-: 17: * COPY protocol, until either side ends the replication by exiting COPY
-: 18: * mode (or until the connection is closed).
-: 19: *
-: 20: * Normal termination is by SIGTERM, which instructs the walsender to
-: 21: * close the connection and exit(0) at the next convenient moment. Emergency
-: 22: * termination is by SIGQUIT; like any backend, the walsender will simply
-: 23: * abort and exit on SIGQUIT. A close of the connection and a FATAL error
-: 24: * are treated as not a crash but approximately normal termination;
-: 25: * the walsender will exit quickly without sending any more XLOG records.
-: 26: *
-: 27: * If the server is shut down, checkpointer sends us
-: 28: * PROCSIG_WALSND_INIT_STOPPING after all regular backends have exited. If
-: 29: * the backend is idle or runs an SQL query this causes the backend to
-: 30: * shutdown, if logical replication is in progress all existing WAL records
-: 31: * are processed followed by a shutdown. Otherwise this causes the walsender
-: 32: * to switch to the "stopping" state. In this state, the walsender will reject
-: 33: * any further replication commands. The checkpointer begins the shutdown
-: 34: * checkpoint once all walsenders are confirmed as stopping. When the shutdown
-: 35: * checkpoint finishes, the postmaster sends us SIGUSR2. This instructs
-: 36: * walsender to send any outstanding WAL, including the shutdown checkpoint
-: 37: * record, wait for it to be replicated to the standby, and then exit.
-: 38: *
-: 39: *
-: 40: * Portions Copyright (c) 2010-2019, PostgreSQL Global Development Group
-: 41: *
-: 42: * IDENTIFICATION
-: 43: * src/backend/replication/walsender.c
-: 44: *
-: 45: *-------------------------------------------------------------------------
-: 46: */
-: 47:#include "postgres.h"
-: 48:
-: 49:#include <signal.h>
-: 50:#include <unistd.h>
-: 51:
-: 52:#include "access/printtup.h"
-: 53:#include "access/timeline.h"
-: 54:#include "access/transam.h"
-: 55:#include "access/xact.h"
-: 56:#include "access/xlog_internal.h"
-: 57:#include "access/xlogutils.h"
-: 58:
-: 59:#include "catalog/pg_authid.h"
-: 60:#include "catalog/pg_type.h"
-: 61:#include "commands/dbcommands.h"
-: 62:#include "commands/defrem.h"
-: 63:#include "funcapi.h"
-: 64:#include "libpq/libpq.h"
-: 65:#include "libpq/pqformat.h"
-: 66:#include "miscadmin.h"
-: 67:#include "nodes/replnodes.h"
-: 68:#include "pgstat.h"
-: 69:#include "replication/basebackup.h"
-: 70:#include "replication/decode.h"
-: 71:#include "replication/logical.h"
-: 72:#include "replication/logicalfuncs.h"
-: 73:#include "replication/slot.h"
-: 74:#include "replication/snapbuild.h"
-: 75:#include "replication/syncrep.h"
-: 76:#include "replication/walreceiver.h"
-: 77:#include "replication/walsender.h"
-: 78:#include "replication/walsender_private.h"
-: 79:#include "storage/condition_variable.h"
-: 80:#include "storage/fd.h"
-: 81:#include "storage/ipc.h"
-: 82:#include "storage/pmsignal.h"
-: 83:#include "storage/proc.h"
-: 84:#include "storage/procarray.h"
-: 85:#include "tcop/dest.h"
-: 86:#include "tcop/tcopprot.h"
-: 87:#include "utils/builtins.h"
-: 88:#include "utils/guc.h"
-: 89:#include "utils/memutils.h"
-: 90:#include "utils/pg_lsn.h"
-: 91:#include "utils/portal.h"
-: 92:#include "utils/ps_status.h"
-: 93:#include "utils/timeout.h"
-: 94:#include "utils/timestamp.h"
-: 95:
-: 96:/*
-: 97: * Maximum data payload in a WAL data message. Must be >= XLOG_BLCKSZ.
-: 98: *
-: 99: * We don't have a good idea of what a good value would be; there's some
-: 100: * overhead per message in both walsender and walreceiver, but on the other
-: 101: * hand sending large batches makes walsender less responsive to signals
-: 102: * because signals are checked only between messages. 128kB (with
-: 103: * default 8k blocks) seems like a reasonable guess for now.
-: 104: */
-: 105:#define MAX_SEND_SIZE (XLOG_BLCKSZ * 16)
-: 106:
-: 107:/* Array of WalSnds in shared memory */
-: 108:WalSndCtlData *WalSndCtl = NULL;
-: 109:
-: 110:/* My slot in the shared memory array */
-: 111:WalSnd *MyWalSnd = NULL;
-: 112:
-: 113:/* Global state */
-: 114:bool am_walsender = false; /* Am I a walsender process? */
-: 115:bool am_cascading_walsender = false; /* Am I cascading WAL to another
-: 116: * standby? */
-: 117:bool am_db_walsender = false; /* Connected to a database? */
-: 118:
-: 119:/* User-settable parameters for walsender */
-: 120:int max_wal_senders = 0; /* the maximum number of concurrent
-: 121: * walsenders */
-: 122:int wal_sender_timeout = 60 * 1000; /* maximum time to send one WAL
-: 123: * data message */
-: 124:bool log_replication_commands = false;
-: 125:
-: 126:/*
-: 127: * State for WalSndWakeupRequest
-: 128: */
-: 129:bool wake_wal_senders = false;
-: 130:
-: 131:static WALOpenSegment *sendSeg = NULL;
-: 132:static WALSegmentContext *sendCxt = NULL;
-: 133:
-: 134:/*
-: 135: * These variables keep track of the state of the timeline we're currently
-: 136: * sending. sendTimeLine identifies the timeline. If sendTimeLineIsHistoric,
-: 137: * the timeline is not the latest timeline on this server, and the server's
-: 138: * history forked off from that timeline at sendTimeLineValidUpto.
-: 139: */
-: 140:static TimeLineID sendTimeLine = 0;
-: 141:static TimeLineID sendTimeLineNextTLI = 0;
-: 142:static bool sendTimeLineIsHistoric = false;
-: 143:static XLogRecPtr sendTimeLineValidUpto = InvalidXLogRecPtr;
-: 144:
-: 145:/*
-: 146: * How far have we sent WAL already? This is also advertised in
-: 147: * MyWalSnd->sentPtr. (Actually, this is the next WAL location to send.)
-: 148: */
-: 149:static XLogRecPtr sentPtr = 0;
-: 150:
-: 151:/* Buffers for constructing outgoing messages and processing reply messages. */
-: 152:static StringInfoData output_message;
-: 153:static StringInfoData reply_message;
-: 154:static StringInfoData tmpbuf;
-: 155:
-: 156:/* Timestamp of last ProcessRepliesIfAny(). */
-: 157:static TimestampTz last_processing = 0;
-: 158:
-: 159:/*
-: 160: * Timestamp of last ProcessRepliesIfAny() that saw a reply from the
-: 161: * standby. Set to 0 if wal_sender_timeout doesn't need to be active.
-: 162: */
-: 163:static TimestampTz last_reply_timestamp = 0;
-: 164:
-: 165:/* Have we sent a heartbeat message asking for reply, since last reply? */
-: 166:static bool waiting_for_ping_response = false;
-: 167:
-: 168:/*
-: 169: * While streaming WAL in Copy mode, streamingDoneSending is set to true
-: 170: * after we have sent CopyDone. We should not send any more CopyData messages
-: 171: * after that. streamingDoneReceiving is set to true when we receive CopyDone
-: 172: * from the other end. When both become true, it's time to exit Copy mode.
-: 173: */
-: 174:static bool streamingDoneSending;
-: 175:static bool streamingDoneReceiving;
-: 176:
-: 177:/* Are we there yet? */
-: 178:static bool WalSndCaughtUp = false;
-: 179:
-: 180:/* Flags set by signal handlers for later service in main loop */
-: 181:static volatile sig_atomic_t got_SIGUSR2 = false;
-: 182:static volatile sig_atomic_t got_STOPPING = false;
-: 183:
-: 184:/*
-: 185: * This is set while we are streaming. When not set
-: 186: * PROCSIG_WALSND_INIT_STOPPING signal will be handled like SIGTERM. When set,
-: 187: * the main loop is responsible for checking got_STOPPING and terminating when
-: 188: * it's set (after streaming any remaining WAL).
-: 189: */
-: 190:static volatile sig_atomic_t replication_active = false;
-: 191:
-: 192:static LogicalDecodingContext *logical_decoding_ctx = NULL;
-: 193:static XLogRecPtr logical_startptr = InvalidXLogRecPtr;
-: 194:
-: 195:/* A sample associating a WAL location with the time it was written. */
-: 196:typedef struct
-: 197:{
-: 198: XLogRecPtr lsn;
-: 199: TimestampTz time;
-: 200:} WalTimeSample;
-: 201:
-: 202:/* The size of our buffer of time samples. */
-: 203:#define LAG_TRACKER_BUFFER_SIZE 8192
-: 204:
-: 205:/* A mechanism for tracking replication lag. */
-: 206:typedef struct
-: 207:{
-: 208: XLogRecPtr last_lsn;
-: 209: WalTimeSample buffer[LAG_TRACKER_BUFFER_SIZE];
-: 210: int write_head;
-: 211: int read_heads[NUM_SYNC_REP_WAIT_MODE];
-: 212: WalTimeSample last_read[NUM_SYNC_REP_WAIT_MODE];
-: 213:} LagTracker;
-: 214:
-: 215:static LagTracker *lag_tracker;
-: 216:
-: 217:/* Signal handlers */
-: 218:static void WalSndLastCycleHandler(SIGNAL_ARGS);
-: 219:
-: 220:/* Prototypes for private functions */
-: 221:typedef void (*WalSndSendDataCallback) (void);
-: 222:static void WalSndLoop(WalSndSendDataCallback send_data);
-: 223:static void InitWalSenderSlot(void);
-: 224:static void WalSndKill(int code, Datum arg);
-: 225:static void WalSndShutdown(void) pg_attribute_noreturn();
-: 226:static void XLogSendPhysical(void);
-: 227:static void XLogSendLogical(void);
-: 228:static void WalSndDone(WalSndSendDataCallback send_data);
-: 229:static XLogRecPtr GetStandbyFlushRecPtr(void);
-: 230:static void IdentifySystem(void);
-: 231:static void CreateReplicationSlot(CreateReplicationSlotCmd *cmd);
-: 232:static void DropReplicationSlot(DropReplicationSlotCmd *cmd);
-: 233:static void StartReplication(StartReplicationCmd *cmd);
-: 234:static void StartLogicalReplication(StartReplicationCmd *cmd);
-: 235:static void ProcessStandbyMessage(void);
-: 236:static void ProcessStandbyReplyMessage(void);
-: 237:static void ProcessStandbyHSFeedbackMessage(void);
-: 238:static void ProcessRepliesIfAny(void);
-: 239:static void WalSndKeepalive(bool requestReply);
-: 240:static void WalSndKeepaliveIfNecessary(void);
-: 241:static void WalSndCheckTimeOut(void);
-: 242:static long WalSndComputeSleeptime(TimestampTz now);
-: 243:static void WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
-: 244:static void WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
-: 245:static void WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid);
-: 246:static XLogRecPtr WalSndWaitForWal(XLogRecPtr loc);
-: 247:static void LagTrackerWrite(XLogRecPtr lsn, TimestampTz local_flush_time);
-: 248:static TimeOffset LagTrackerRead(int head, XLogRecPtr lsn, TimestampTz now);
-: 249:static bool TransactionIdInRecentPast(TransactionId xid, uint32 epoch);
-: 250:
-: 251:static void UpdateSpillStats(LogicalDecodingContext *ctx);
-: 252:static void XLogRead(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count);
-: 253:
-: 254:
-: 255:/* Initialize walsender process before entering the main command loop */
-: 256:void
function InitWalSender called 229 returned 100% blocks executed 100%
229: 257:InitWalSender(void)
-: 258:{
229: 259: am_cascading_walsender = RecoveryInProgress();
call 0 returned 100%
-: 260:
-: 261: /* Create a per-walsender data structure in shared memory */
229: 262: InitWalSenderSlot();
call 0 returned 100%
-: 263:
-: 264: /*
-: 265: * We don't currently need any ResourceOwner in a walsender process, but
-: 266: * if we did, we could call CreateAuxProcessResourceOwner here.
-: 267: */
-: 268:
-: 269: /*
-: 270: * Let postmaster know that we're a WAL sender. Once we've declared us as
-: 271: * a WAL sender process, postmaster will let us outlive the bgwriter and
-: 272: * kill us last in the shutdown sequence, so we get a chance to stream all
-: 273: * remaining WAL at shutdown, including the shutdown checkpoint. Note that
-: 274: * there's no going back, and we mustn't write any WAL records after this.
-: 275: */
229: 276: MarkPostmasterChildWalSender();
call 0 returned 100%
229: 277: SendPostmasterSignal(PMSIGNAL_ADVANCE_STATE_MACHINE);
call 0 returned 100%
-: 278:
-: 279: /* Initialize empty timestamp buffer for lag tracking. */
229: 280: lag_tracker = MemoryContextAllocZero(TopMemoryContext, sizeof(LagTracker));
call 0 returned 100%
-: 281:
-: 282: /* Make sure we can remember the current read position in XLOG. */
229: 283: sendSeg = (WALOpenSegment *)
229: 284: MemoryContextAlloc(TopMemoryContext, sizeof(WALOpenSegment));
call 0 returned 100%
229: 285: sendCxt = (WALSegmentContext *)
229: 286: MemoryContextAlloc(TopMemoryContext, sizeof(WALSegmentContext));
call 0 returned 100%
229: 287: WALOpenSegmentInit(sendSeg, sendCxt, wal_segment_size, NULL);
call 0 returned 100%
229: 288:}
-: 289:
-: 290:/*
-: 291: * Clean up after an error.
-: 292: *
-: 293: * WAL sender processes don't use transactions like regular backends do.
-: 294: * This function does any cleanup required after an error in a WAL sender
-: 295: * process, similar to what transaction abort does in a regular backend.
-: 296: */
-: 297:void
function WalSndErrorCleanup called 8 returned 100% blocks executed 71%
8: 298:WalSndErrorCleanup(void)
-: 299:{
8: 300: LWLockReleaseAll();
call 0 returned 100%
8: 301: ConditionVariableCancelSleep();
call 0 returned 100%
8: 302: pgstat_report_wait_end();
call 0 returned 100%
-: 303:
8: 304: if (sendSeg->ws_file >= 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 305: {
#####: 306: close(sendSeg->ws_file);
call 0 never executed
#####: 307: sendSeg->ws_file = -1;
-: 308: }
-: 309:
8: 310: if (MyReplicationSlot != NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 311: ReplicationSlotRelease();
call 0 never executed
-: 312:
8: 313: ReplicationSlotCleanup();
call 0 returned 100%
-: 314:
8: 315: replication_active = false;
-: 316:
8: 317: if (got_STOPPING || got_SIGUSR2)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 318: proc_exit(0);
call 0 never executed
-: 319:
-: 320: /* Revert back to startup state */
8: 321: WalSndSetState(WALSNDSTATE_STARTUP);
call 0 returned 100%
8: 322:}
-: 323:
-: 324:/*
-: 325: * Handle a client's connection abort in an orderly manner.
-: 326: */
-: 327:static void
function WalSndShutdown called 0 returned 0% blocks executed 0%
#####: 328:WalSndShutdown(void)
-: 329:{
-: 330: /*
-: 331: * Reset whereToSendOutput to prevent ereport from attempting to send any
-: 332: * more messages to the standby.
-: 333: */
#####: 334: if (whereToSendOutput == DestRemote)
branch 0 never executed
branch 1 never executed
#####: 335: whereToSendOutput = DestNone;
-: 336:
#####: 337: proc_exit(0);
-: 338: abort(); /* keep the compiler quiet */
-: 339:}
-: 340:
-: 341:/*
-: 342: * Handle the IDENTIFY_SYSTEM command.
-: 343: */
-: 344:static void
function IdentifySystem called 165 returned 100% blocks executed 84%
165: 345:IdentifySystem(void)
-: 346:{
-: 347: char sysid[32];
-: 348: char xloc[MAXFNAMELEN];
-: 349: XLogRecPtr logptr;
165: 350: char *dbname = NULL;
-: 351: DestReceiver *dest;
-: 352: TupOutputState *tstate;
-: 353: TupleDesc tupdesc;
-: 354: Datum values[4];
-: 355: bool nulls[4];
-: 356:
-: 357: /*
-: 358: * Reply with a result set with one row, four columns. First col is system
-: 359: * ID, second is timeline ID, third is current xlog location and the
-: 360: * fourth contains the database name if we are connected to one.
-: 361: */
-: 362:
165: 363: snprintf(sysid, sizeof(sysid), UINT64_FORMAT,
call 0 returned 100%
call 1 returned 100%
-: 364: GetSystemIdentifier());
-: 365:
165: 366: am_cascading_walsender = RecoveryInProgress();
call 0 returned 100%
165: 367: if (am_cascading_walsender)
branch 0 taken 4% (fallthrough)
branch 1 taken 96%
-: 368: {
-: 369: /* this also updates ThisTimeLineID */
6: 370: logptr = GetStandbyFlushRecPtr();
call 0 returned 100%
-: 371: }
-: 372: else
159: 373: logptr = GetFlushRecPtr();
call 0 returned 100%
-: 374:
165: 375: snprintf(xloc, sizeof(xloc), "%X/%X", (uint32) (logptr >> 32), (uint32) logptr);
call 0 returned 100%
-: 376:
165: 377: if (MyDatabaseId != InvalidOid)
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
-: 378: {
24: 379: MemoryContext cur = CurrentMemoryContext;
-: 380:
-: 381: /* syscache access needs a transaction env. */
24: 382: StartTransactionCommand();
call 0 returned 100%
-: 383: /* make dbname live outside TX context */
24: 384: MemoryContextSwitchTo(cur);
call 0 returned 100%
24: 385: dbname = get_database_name(MyDatabaseId);
call 0 returned 100%
24: 386: CommitTransactionCommand();
call 0 returned 100%
-: 387: /* CommitTransactionCommand switches to TopMemoryContext */
24: 388: MemoryContextSwitchTo(cur);
call 0 returned 100%
-: 389: }
-: 390:
165: 391: dest = CreateDestReceiver(DestRemoteSimple);
call 0 returned 100%
165: 392: MemSet(nulls, false, sizeof(nulls));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
branch 4 never executed
branch 5 never executed
branch 6 never executed
branch 7 never executed
branch 8 never executed
branch 9 never executed
-: 393:
-: 394: /* need a tuple descriptor representing four columns */
165: 395: tupdesc = CreateTemplateTupleDesc(4);
call 0 returned 100%
165: 396: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "systemid",
call 0 returned 100%
-: 397: TEXTOID, -1, 0);
165: 398: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "timeline",
call 0 returned 100%
-: 399: INT4OID, -1, 0);
165: 400: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "xlogpos",
call 0 returned 100%
-: 401: TEXTOID, -1, 0);
165: 402: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 4, "dbname",
call 0 returned 100%
-: 403: TEXTOID, -1, 0);
-: 404:
-: 405: /* prepare for projection of tuples */
165: 406: tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
call 0 returned 100%
-: 407:
-: 408: /* column 1: system identifier */
165: 409: values[0] = CStringGetTextDatum(sysid);
call 0 returned 100%
-: 410:
-: 411: /* column 2: timeline */
165: 412: values[1] = Int32GetDatum(ThisTimeLineID);
-: 413:
-: 414: /* column 3: wal location */
165: 415: values[2] = CStringGetTextDatum(xloc);
call 0 returned 100%
-: 416:
-: 417: /* column 4: database name, or NULL if none */
165: 418: if (dbname)
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
24: 419: values[3] = CStringGetTextDatum(dbname);
call 0 returned 100%
-: 420: else
141: 421: nulls[3] = true;
-: 422:
-: 423: /* send it to dest */
165: 424: do_tup_output(tstate, values, nulls);
call 0 returned 100%
-: 425:
165: 426: end_tup_output(tstate);
call 0 returned 100%
165: 427:}
-: 428:
-: 429:
-: 430:/*
-: 431: * Handle TIMELINE_HISTORY command.
-: 432: */
-: 433:static void
function SendTimeLineHistory called 5 returned 100% blocks executed 51%
5: 434:SendTimeLineHistory(TimeLineHistoryCmd *cmd)
-: 435:{
-: 436: StringInfoData buf;
-: 437: char histfname[MAXFNAMELEN];
-: 438: char path[MAXPGPATH];
-: 439: int fd;
-: 440: off_t histfilelen;
-: 441: off_t bytesleft;
-: 442: Size len;
-: 443:
-: 444: /*
-: 445: * Reply with a result set with one row, and two columns. The first col is
-: 446: * the name of the history file, 2nd is the contents.
-: 447: */
-: 448:
5: 449: TLHistoryFileName(histfname, cmd->timeline);
call 0 returned 100%
5: 450: TLHistoryFilePath(path, cmd->timeline);
call 0 returned 100%
-: 451:
-: 452: /* Send a RowDescription message */
5: 453: pq_beginmessage(&buf, 'T');
call 0 returned 100%
5: 454: pq_sendint16(&buf, 2); /* 2 fields */
call 0 returned 100%
-: 455:
-: 456: /* first field */
5: 457: pq_sendstring(&buf, "filename"); /* col name */
call 0 returned 100%
5: 458: pq_sendint32(&buf, 0); /* table oid */
call 0 returned 100%
5: 459: pq_sendint16(&buf, 0); /* attnum */
call 0 returned 100%
5: 460: pq_sendint32(&buf, TEXTOID); /* type oid */
call 0 returned 100%
5: 461: pq_sendint16(&buf, -1); /* typlen */
call 0 returned 100%
5: 462: pq_sendint32(&buf, 0); /* typmod */
call 0 returned 100%
5: 463: pq_sendint16(&buf, 0); /* format code */
call 0 returned 100%
-: 464:
-: 465: /* second field */
5: 466: pq_sendstring(&buf, "content"); /* col name */
call 0 returned 100%
5: 467: pq_sendint32(&buf, 0); /* table oid */
call 0 returned 100%
5: 468: pq_sendint16(&buf, 0); /* attnum */
call 0 returned 100%
5: 469: pq_sendint32(&buf, BYTEAOID); /* type oid */
call 0 returned 100%
5: 470: pq_sendint16(&buf, -1); /* typlen */
call 0 returned 100%
5: 471: pq_sendint32(&buf, 0); /* typmod */
call 0 returned 100%
5: 472: pq_sendint16(&buf, 0); /* format code */
call 0 returned 100%
5: 473: pq_endmessage(&buf);
call 0 returned 100%
-: 474:
-: 475: /* Send a DataRow message */
5: 476: pq_beginmessage(&buf, 'D');
call 0 returned 100%
5: 477: pq_sendint16(&buf, 2); /* # of columns */
call 0 returned 100%
5: 478: len = strlen(histfname);
5: 479: pq_sendint32(&buf, len); /* col1 len */
call 0 returned 100%
5: 480: pq_sendbytes(&buf, histfname, len);
call 0 returned 100%
-: 481:
5: 482: fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
call 0 returned 100%
5: 483: if (fd < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 484: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 485: (errcode_for_file_access(),
-: 486: errmsg("could not open file \"%s\": %m", path)));
-: 487:
-: 488: /* Determine file length and send it to client */
5: 489: histfilelen = lseek(fd, 0, SEEK_END);
call 0 returned 100%
5: 490: if (histfilelen < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 491: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 492: (errcode_for_file_access(),
-: 493: errmsg("could not seek to end of file \"%s\": %m", path)));
5: 494: if (lseek(fd, 0, SEEK_SET) != 0)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 495: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 496: (errcode_for_file_access(),
-: 497: errmsg("could not seek to beginning of file \"%s\": %m", path)));
-: 498:
5: 499: pq_sendint32(&buf, histfilelen); /* col2 len */
call 0 returned 100%
-: 500:
5: 501: bytesleft = histfilelen;
15: 502: while (bytesleft > 0)
branch 0 taken 50%
branch 1 taken 50% (fallthrough)
-: 503: {
-: 504: PGAlignedBlock rbuf;
-: 505: int nread;
-: 506:
5: 507: pgstat_report_wait_start(WAIT_EVENT_WALSENDER_TIMELINE_HISTORY_READ);
call 0 returned 100%
5: 508: nread = read(fd, rbuf.data, sizeof(rbuf));
call 0 returned 100%
5: 509: pgstat_report_wait_end();
call 0 returned 100%
5: 510: if (nread < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 511: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 512: (errcode_for_file_access(),
-: 513: errmsg("could not read file \"%s\": %m",
-: 514: path)));
5: 515: else if (nread == 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 516: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 517: (errcode(ERRCODE_DATA_CORRUPTED),
-: 518: errmsg("could not read file \"%s\": read %d of %zu",
-: 519: path, nread, (Size) bytesleft)));
-: 520:
5: 521: pq_sendbytes(&buf, rbuf.data, nread);
call 0 returned 100%
5: 522: bytesleft -= nread;
-: 523: }
-: 524:
5: 525: if (CloseTransientFile(fd) != 0)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 526: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 527: (errcode_for_file_access(),
-: 528: errmsg("could not close file \"%s\": %m", path)));
-: 529:
5: 530: pq_endmessage(&buf);
call 0 returned 100%
5: 531:}
-: 532:
-: 533:/*
-: 534: * Handle START_REPLICATION command.
-: 535: *
-: 536: * At the moment, this never returns, but an ereport(ERROR) will take us back
-: 537: * to the main loop.
-: 538: */
-: 539:static void
function StartReplication called 89 returned 52% blocks executed 59%
89: 540:StartReplication(StartReplicationCmd *cmd)
-: 541:{
-: 542: StringInfoData buf;
-: 543: XLogRecPtr FlushPtr;
-: 544:
89: 545: if (ThisTimeLineID == 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 546: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 547: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 548: errmsg("IDENTIFY_SYSTEM has not been run before START_REPLICATION")));
-: 549:
-: 550: /*
-: 551: * We assume here that we're logging enough information in the WAL for
-: 552: * log-shipping, since this is checked in PostmasterMain().
-: 553: *
-: 554: * NOTE: wal_level can only change at shutdown, so in most cases it is
-: 555: * difficult for there to be WAL data that we can still see that was
-: 556: * written at wal_level='minimal'.
-: 557: */
-: 558:
89: 559: if (cmd->slotname)
branch 0 taken 49% (fallthrough)
branch 1 taken 51%
-: 560: {
44: 561: ReplicationSlotAcquire(cmd->slotname, true);
call 0 returned 98%
43: 562: if (SlotIsLogical(MyReplicationSlot))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 563: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 564: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 565: (errmsg("cannot use a logical replication slot for physical replication"))));
-: 566: }
-: 567:
-: 568: /*
-: 569: * Select the timeline. If it was given explicitly by the client, use
-: 570: * that. Otherwise use the timeline of the last replayed record, which is
-: 571: * kept in ThisTimeLineID.
-: 572: */
88: 573: if (am_cascading_walsender)
branch 0 taken 5% (fallthrough)
branch 1 taken 95%
-: 574: {
-: 575: /* this also updates ThisTimeLineID */
4: 576: FlushPtr = GetStandbyFlushRecPtr();
call 0 returned 100%
-: 577: }
-: 578: else
84: 579: FlushPtr = GetFlushRecPtr();
call 0 returned 100%
-: 580:
88: 581: if (cmd->timeline != 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 582: {
-: 583: XLogRecPtr switchpoint;
-: 584:
88: 585: sendTimeLine = cmd->timeline;
88: 586: if (sendTimeLine == ThisTimeLineID)
branch 0 taken 94% (fallthrough)
branch 1 taken 6%
-: 587: {
83: 588: sendTimeLineIsHistoric = false;
83: 589: sendTimeLineValidUpto = InvalidXLogRecPtr;
-: 590: }
-: 591: else
-: 592: {
-: 593: List *timeLineHistory;
-: 594:
5: 595: sendTimeLineIsHistoric = true;
-: 596:
-: 597: /*
-: 598: * Check that the timeline the client requested exists, and the
-: 599: * requested start location is on that timeline.
-: 600: */
5: 601: timeLineHistory = readTimeLineHistory(ThisTimeLineID);
call 0 returned 100%
5: 602: switchpoint = tliSwitchPoint(cmd->timeline, timeLineHistory,
call 0 returned 100%
-: 603: &sendTimeLineNextTLI);
5: 604: list_free_deep(timeLineHistory);
call 0 returned 100%
-: 605:
-: 606: /*
-: 607: * Found the requested timeline in the history. Check that
-: 608: * requested startpoint is on that timeline in our history.
-: 609: *
-: 610: * This is quite loose on purpose. We only check that we didn't
-: 611: * fork off the requested timeline before the switchpoint. We
-: 612: * don't check that we switched *to* it before the requested
-: 613: * starting point. This is because the client can legitimately
-: 614: * request to start replication from the beginning of the WAL
-: 615: * segment that contains switchpoint, but on the new timeline, so
-: 616: * that it doesn't end up with a partial segment. If you ask for
-: 617: * too old a starting point, you'll get an error later when we
-: 618: * fail to find the requested WAL segment in pg_wal.
-: 619: *
-: 620: * XXX: we could be more strict here and only allow a startpoint
-: 621: * that's older than the switchpoint, if it's still in the same
-: 622: * WAL segment.
-: 623: */
10: 624: if (!XLogRecPtrIsInvalid(switchpoint) &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
5: 625: switchpoint < cmd->startpoint)
-: 626: {
#####: 627: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 628: (errmsg("requested starting point %X/%X on timeline %u is not in this server's history",
-: 629: (uint32) (cmd->startpoint >> 32),
-: 630: (uint32) (cmd->startpoint),
-: 631: cmd->timeline),
-: 632: errdetail("This server's history forked from timeline %u at %X/%X.",
-: 633: cmd->timeline,
-: 634: (uint32) (switchpoint >> 32),
-: 635: (uint32) (switchpoint))));
-: 636: }
5: 637: sendTimeLineValidUpto = switchpoint;
-: 638: }
-: 639: }
-: 640: else
-: 641: {
#####: 642: sendTimeLine = ThisTimeLineID;
#####: 643: sendTimeLineValidUpto = InvalidXLogRecPtr;
#####: 644: sendTimeLineIsHistoric = false;
-: 645: }
-: 646:
88: 647: streamingDoneSending = streamingDoneReceiving = false;
-: 648:
-: 649: /* If there is nothing to stream, don't even enter COPY mode */
88: 650: if (!sendTimeLineIsHistoric || cmd->startpoint < sendTimeLineValidUpto)
branch 0 taken 6% (fallthrough)
branch 1 taken 94%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
-: 651: {
-: 652: /*
-: 653: * When we first start replication the standby will be behind the
-: 654: * primary. For some applications, for example synchronous
-: 655: * replication, it is important to have a clear state for this initial
-: 656: * catchup mode, so we can trigger actions when we change streaming
-: 657: * state later. We may stay in this state for a long time, which is
-: 658: * exactly why we want to be able to monitor whether or not we are
-: 659: * still here.
-: 660: */
88: 661: WalSndSetState(WALSNDSTATE_CATCHUP);
call 0 returned 100%
-: 662:
-: 663: /* Send a CopyBothResponse message, and start streaming */
88: 664: pq_beginmessage(&buf, 'W');
call 0 returned 100%
88: 665: pq_sendbyte(&buf, 0);
call 0 returned 100%
88: 666: pq_sendint16(&buf, 0);
call 0 returned 100%
88: 667: pq_endmessage(&buf);
call 0 returned 100%
88: 668: pq_flush();
call 0 returned 100%
-: 669:
-: 670: /*
-: 671: * Don't allow a request to stream from a future point in WAL that
-: 672: * hasn't been flushed to disk in this server yet.
-: 673: */
88: 674: if (FlushPtr < cmd->startpoint)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 675: {
#####: 676: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 677: (errmsg("requested starting point %X/%X is ahead of the WAL flush position of this server %X/%X",
-: 678: (uint32) (cmd->startpoint >> 32),
-: 679: (uint32) (cmd->startpoint),
-: 680: (uint32) (FlushPtr >> 32),
-: 681: (uint32) (FlushPtr))));
-: 682: }
-: 683:
-: 684: /* Start streaming from the requested point */
88: 685: sentPtr = cmd->startpoint;
-: 686:
-: 687: /* Initialize shared memory status, too */
88: 688: SpinLockAcquire(&MyWalSnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
88: 689: MyWalSnd->sentPtr = sentPtr;
88: 690: SpinLockRelease(&MyWalSnd->mutex);
call 0 returned 100%
-: 691:
88: 692: SyncRepInitConfig();
call 0 returned 100%
-: 693:
-: 694: /* Main loop of walsender */
88: 695: replication_active = true;
-: 696:
88: 697: WalSndLoop(XLogSendPhysical);
call 0 returned 52%
-: 698:
46: 699: replication_active = false;
46: 700: if (got_STOPPING)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 701: proc_exit(0);
call 0 never executed
46: 702: WalSndSetState(WALSNDSTATE_STARTUP);
call 0 returned 100%
-: 703:
46: 704: Assert(streamingDoneSending && streamingDoneReceiving);
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
call 4 never executed
-: 705: }
-: 706:
46: 707: if (cmd->slotname)
branch 0 taken 85% (fallthrough)
branch 1 taken 15%
39: 708: ReplicationSlotRelease();
call 0 returned 100%
-: 709:
-: 710: /*
-: 711: * Copy is finished now. Send a single-row result set indicating the next
-: 712: * timeline.
-: 713: */
46: 714: if (sendTimeLineIsHistoric)
branch 0 taken 11% (fallthrough)
branch 1 taken 89%
-: 715: {
-: 716: char startpos_str[8 + 1 + 8 + 1];
-: 717: DestReceiver *dest;
-: 718: TupOutputState *tstate;
-: 719: TupleDesc tupdesc;
-: 720: Datum values[2];
-: 721: bool nulls[2];
-: 722:
10: 723: snprintf(startpos_str, sizeof(startpos_str), "%X/%X",
call 0 returned 100%
5: 724: (uint32) (sendTimeLineValidUpto >> 32),
-: 725: (uint32) sendTimeLineValidUpto);
-: 726:
5: 727: dest = CreateDestReceiver(DestRemoteSimple);
call 0 returned 100%
5: 728: MemSet(nulls, false, sizeof(nulls));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
branch 4 never executed
branch 5 never executed
branch 6 never executed
branch 7 never executed
branch 8 never executed
branch 9 never executed
-: 729:
-: 730: /*
-: 731: * Need a tuple descriptor representing two columns. int8 may seem
-: 732: * like a surprising data type for this, but in theory int4 would not
-: 733: * be wide enough for this, as TimeLineID is unsigned.
-: 734: */
5: 735: tupdesc = CreateTemplateTupleDesc(2);
call 0 returned 100%
5: 736: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "next_tli",
call 0 returned 100%
-: 737: INT8OID, -1, 0);
5: 738: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "next_tli_startpos",
call 0 returned 100%
-: 739: TEXTOID, -1, 0);
-: 740:
-: 741: /* prepare for projection of tuple */
5: 742: tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
call 0 returned 100%
-: 743:
5: 744: values[0] = Int64GetDatum((int64) sendTimeLineNextTLI);
5: 745: values[1] = CStringGetTextDatum(startpos_str);
call 0 returned 100%
-: 746:
-: 747: /* send it to dest */
5: 748: do_tup_output(tstate, values, nulls);
call 0 returned 100%
-: 749:
5: 750: end_tup_output(tstate);
call 0 returned 100%
-: 751: }
-: 752:
-: 753: /* Send CommandComplete message */
46: 754: pq_puttextmessage('C', "START_STREAMING");
call 0 returned 100%
46: 755:}
-: 756:
-: 757:/*
-: 758: * read_page callback for logical decoding contexts, as a walsender process.
-: 759: *
-: 760: * Inside the walsender we can do better than logical_read_local_xlog_page,
-: 761: * which has to do a plain sleep/busy loop, because the walsender's latch gets
-: 762: * set every time WAL is flushed.
-: 763: */
-: 764:static int
function logical_read_xlog_page called 19964 returned 99% blocks executed 100%
19964: 765:logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
-: 766: XLogRecPtr targetRecPtr, char *cur_page)
-: 767:{
-: 768: XLogRecPtr flushptr;
-: 769: int count;
-: 770:
19964: 771: XLogReadDetermineTimeline(state, targetPagePtr, reqLen);
call 0 returned 100%
19964: 772: sendTimeLineIsHistoric = (state->currTLI != ThisTimeLineID);
19964: 773: sendTimeLine = state->currTLI;
19964: 774: sendTimeLineValidUpto = state->currTLIValidUntil;
19964: 775: sendTimeLineNextTLI = state->nextTLI;
-: 776:
-: 777: /* make sure we have enough WAL available */
19964: 778: flushptr = WalSndWaitForWal(targetPagePtr + reqLen);
call 0 returned 99%
-: 779:
-: 780: /* fail if not (implies we are going to shut down) */
19948: 781: if (flushptr < targetPagePtr + reqLen)
branch 0 taken 49% (fallthrough)
branch 1 taken 51%
9749: 782: return -1;
-: 783:
10199: 784: if (targetPagePtr + XLOG_BLCKSZ <= flushptr)
branch 0 taken 98% (fallthrough)
branch 1 taken 2%
9967: 785: count = XLOG_BLCKSZ; /* more than one block available */
-: 786: else
232: 787: count = flushptr - targetPagePtr; /* part of the page available */
-: 788:
-: 789: /* now actually read the data, we know it's there */
10199: 790: XLogRead(sendCxt, cur_page, targetPagePtr, XLOG_BLCKSZ);
call 0 returned 100%
-: 791:
10199: 792: return count;
-: 793:}
-: 794:
-: 795:/*
-: 796: * Process extra options given to CREATE_REPLICATION_SLOT.
-: 797: */
-: 798:static void
function parseCreateReplSlotOptions called 98 returned 100% blocks executed 48%
98: 799:parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
-: 800: bool *reserve_wal,
-: 801: CRSSnapshotAction *snapshot_action)
-: 802:{
-: 803: ListCell *lc;
98: 804: bool snapshot_action_given = false;
98: 805: bool reserve_wal_given = false;
-: 806:
-: 807: /* Parse options */
195: 808: foreach(lc, cmd->options)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 50% (fallthrough)
branch 3 taken 50%
branch 4 taken 50%
branch 5 taken 50% (fallthrough)
-: 809: {
97: 810: DefElem *defel = (DefElem *) lfirst(lc);
-: 811:
97: 812: if (strcmp(defel->defname, "export_snapshot") == 0)
branch 0 taken 19% (fallthrough)
branch 1 taken 81%
-: 813: {
18: 814: if (snapshot_action_given || cmd->kind != REPLICATION_KIND_LOGICAL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 815: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 816: (errcode(ERRCODE_SYNTAX_ERROR),
-: 817: errmsg("conflicting or redundant options")));
-: 818:
18: 819: snapshot_action_given = true;
18: 820: *snapshot_action = defGetBoolean(defel) ? CRS_EXPORT_SNAPSHOT :
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 821: CRS_NOEXPORT_SNAPSHOT;
-: 822: }
79: 823: else if (strcmp(defel->defname, "use_snapshot") == 0)
branch 0 taken 47% (fallthrough)
branch 1 taken 53%
-: 824: {
37: 825: if (snapshot_action_given || cmd->kind != REPLICATION_KIND_LOGICAL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 826: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 827: (errcode(ERRCODE_SYNTAX_ERROR),
-: 828: errmsg("conflicting or redundant options")));
-: 829:
37: 830: snapshot_action_given = true;
37: 831: *snapshot_action = CRS_USE_SNAPSHOT;
-: 832: }
42: 833: else if (strcmp(defel->defname, "reserve_wal") == 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 834: {
42: 835: if (reserve_wal_given || cmd->kind != REPLICATION_KIND_PHYSICAL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 836: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 837: (errcode(ERRCODE_SYNTAX_ERROR),
-: 838: errmsg("conflicting or redundant options")));
-: 839:
42: 840: reserve_wal_given = true;
42: 841: *reserve_wal = true;
-: 842: }
-: 843: else
#####: 844: elog(ERROR, "unrecognized option: %s", defel->defname);
call 0 never executed
call 1 never executed
call 2 never executed
-: 845: }
98: 846:}
-: 847:
-: 848:/*
-: 849: * Create a new replication slot.
-: 850: */
-: 851:static void
function CreateReplicationSlot called 98 returned 99% blocks executed 59%
98: 852:CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
-: 853:{
98: 854: const char *snapshot_name = NULL;
-: 855: char xloc[MAXFNAMELEN];
-: 856: char *slot_name;
98: 857: bool reserve_wal = false;
98: 858: CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
-: 859: DestReceiver *dest;
-: 860: TupOutputState *tstate;
-: 861: TupleDesc tupdesc;
-: 862: Datum values[4];
-: 863: bool nulls[4];
-: 864:
98: 865: Assert(!MyReplicationSlot);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 866:
98: 867: parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action);
call 0 returned 100%
-: 868:
-: 869: /* setup state for XLogRead */
98: 870: sendTimeLineIsHistoric = false;
98: 871: sendTimeLine = ThisTimeLineID;
-: 872:
98: 873: if (cmd->kind == REPLICATION_KIND_PHYSICAL)
branch 0 taken 44% (fallthrough)
branch 1 taken 56%
-: 874: {
43: 875: ReplicationSlotCreate(cmd->slotname, false,
branch 0 taken 93% (fallthrough)
branch 1 taken 7%
call 2 returned 98%
43: 876: cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT);
-: 877: }
-: 878: else
-: 879: {
55: 880: CheckLogicalDecodingRequirements();
call 0 returned 100%
-: 881:
-: 882: /*
-: 883: * Initially create persistent slot as ephemeral - that allows us to
-: 884: * nicely handle errors during initialization because it'll get
-: 885: * dropped if this transaction fails. We'll make it persistent at the
-: 886: * end. Temporary slots can be created as temporary from beginning as
-: 887: * they get dropped on error as well.
-: 888: */
55: 889: ReplicationSlotCreate(cmd->slotname, true,
branch 0 taken 67% (fallthrough)
branch 1 taken 33%
call 2 returned 100%
55: 890: cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL);
-: 891: }
-: 892:
97: 893: if (cmd->kind == REPLICATION_KIND_LOGICAL)
branch 0 taken 57% (fallthrough)
branch 1 taken 43%
-: 894: {
-: 895: LogicalDecodingContext *ctx;
55: 896: bool need_full_snapshot = false;
-: 897:
-: 898: /*
-: 899: * Do options check early so that we can bail before calling the
-: 900: * DecodingContextFindStartpoint which can take long time.
-: 901: */
55: 902: if (snapshot_action == CRS_EXPORT_SNAPSHOT)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 903: {
#####: 904: if (IsTransactionBlock())
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 905: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 906: /*- translator: %s is a CREATE_REPLICATION_SLOT statement */
-: 907: (errmsg("%s must not be called inside a transaction",
-: 908: "CREATE_REPLICATION_SLOT ... EXPORT_SNAPSHOT")));
-: 909:
#####: 910: need_full_snapshot = true;
-: 911: }
55: 912: else if (snapshot_action == CRS_USE_SNAPSHOT)
branch 0 taken 67% (fallthrough)
branch 1 taken 33%
-: 913: {
37: 914: if (!IsTransactionBlock())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 915: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 916: /*- translator: %s is a CREATE_REPLICATION_SLOT statement */
-: 917: (errmsg("%s must be called inside a transaction",
-: 918: "CREATE_REPLICATION_SLOT ... USE_SNAPSHOT")));
-: 919:
37: 920: if (XactIsoLevel != XACT_REPEATABLE_READ)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 921: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 922: /*- translator: %s is a CREATE_REPLICATION_SLOT statement */
-: 923: (errmsg("%s must be called in REPEATABLE READ isolation mode transaction",
-: 924: "CREATE_REPLICATION_SLOT ... USE_SNAPSHOT")));
-: 925:
37: 926: if (FirstSnapshotSet)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 927: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 928: /*- translator: %s is a CREATE_REPLICATION_SLOT statement */
-: 929: (errmsg("%s must be called before any query",
-: 930: "CREATE_REPLICATION_SLOT ... USE_SNAPSHOT")));
-: 931:
37: 932: if (IsSubTransaction())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 933: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 934: /*- translator: %s is a CREATE_REPLICATION_SLOT statement */
-: 935: (errmsg("%s must not be called in a subtransaction",
-: 936: "CREATE_REPLICATION_SLOT ... USE_SNAPSHOT")));
-: 937:
37: 938: need_full_snapshot = true;
-: 939: }
-: 940:
55: 941: ctx = CreateInitDecodingContext(cmd->plugin, NIL, need_full_snapshot,
call 0 returned 100%
-: 942: InvalidXLogRecPtr,
-: 943: logical_read_xlog_page,
-: 944: WalSndPrepareWrite, WalSndWriteData,
-: 945: WalSndUpdateProgress);
-: 946:
-: 947: /*
-: 948: * Signal that we don't need the timeout mechanism. We're just
-: 949: * creating the replication slot and don't yet accept feedback
-: 950: * messages or send keepalives. As we possibly need to wait for
-: 951: * further WAL the walsender would otherwise possibly be killed too
-: 952: * soon.
-: 953: */
55: 954: last_reply_timestamp = 0;
-: 955:
-: 956: /* build initial snapshot, might take a while */
55: 957: DecodingContextFindStartpoint(ctx);
call 0 returned 100%
-: 958:
-: 959: /*
-: 960: * Export or use the snapshot if we've been asked to do so.
-: 961: *
-: 962: * NB. We will convert the snapbuild.c kind of snapshot to normal
-: 963: * snapshot when doing this.
-: 964: */
55: 965: if (snapshot_action == CRS_EXPORT_SNAPSHOT)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 966: {
#####: 967: snapshot_name = SnapBuildExportSnapshot(ctx->snapshot_builder);
call 0 never executed
-: 968: }
55: 969: else if (snapshot_action == CRS_USE_SNAPSHOT)
branch 0 taken 67% (fallthrough)
branch 1 taken 33%
-: 970: {
-: 971: Snapshot snap;
-: 972:
37: 973: snap = SnapBuildInitialSnapshot(ctx->snapshot_builder);
call 0 returned 100%
37: 974: RestoreTransactionSnapshot(snap, MyProc);
call 0 returned 100%
-: 975: }
-: 976:
-: 977: /* don't need the decoding context anymore */
55: 978: FreeDecodingContext(ctx);
call 0 returned 100%
-: 979:
55: 980: if (!cmd->temporary)
branch 0 taken 33% (fallthrough)
branch 1 taken 67%
18: 981: ReplicationSlotPersist();
call 0 returned 100%
-: 982: }
42: 983: else if (cmd->kind == REPLICATION_KIND_PHYSICAL && reserve_wal)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 98% (fallthrough)
branch 3 taken 2%
-: 984: {
41: 985: ReplicationSlotReserveWal();
call 0 returned 100%
-: 986:
41: 987: ReplicationSlotMarkDirty();
call 0 returned 100%
-: 988:
-: 989: /* Write this slot to disk if it's a permanent one. */
41: 990: if (!cmd->temporary)
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
1: 991: ReplicationSlotSave();
call 0 returned 100%
-: 992: }
-: 993:
194: 994: snprintf(xloc, sizeof(xloc), "%X/%X",
call 0 returned 100%
97: 995: (uint32) (MyReplicationSlot->data.confirmed_flush >> 32),
97: 996: (uint32) MyReplicationSlot->data.confirmed_flush);
-: 997:
97: 998: dest = CreateDestReceiver(DestRemoteSimple);
call 0 returned 100%
97: 999: MemSet(nulls, false, sizeof(nulls));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
branch 4 never executed
branch 5 never executed
branch 6 never executed
branch 7 never executed
branch 8 never executed
branch 9 never executed
-: 1000:
-: 1001: /*----------
-: 1002: * Need a tuple descriptor representing four columns:
-: 1003: * - first field: the slot name
-: 1004: * - second field: LSN at which we became consistent
-: 1005: * - third field: exported snapshot's name
-: 1006: * - fourth field: output plugin
-: 1007: *----------
-: 1008: */
97: 1009: tupdesc = CreateTemplateTupleDesc(4);
call 0 returned 100%
97: 1010: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "slot_name",
call 0 returned 100%
-: 1011: TEXTOID, -1, 0);
97: 1012: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "consistent_point",
call 0 returned 100%
-: 1013: TEXTOID, -1, 0);
97: 1014: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "snapshot_name",
call 0 returned 100%
-: 1015: TEXTOID, -1, 0);
97: 1016: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 4, "output_plugin",
call 0 returned 100%
-: 1017: TEXTOID, -1, 0);
-: 1018:
-: 1019: /* prepare for projection of tuples */
97: 1020: tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
call 0 returned 100%
-: 1021:
-: 1022: /* slot_name */
97: 1023: slot_name = NameStr(MyReplicationSlot->data.name);
97: 1024: values[0] = CStringGetTextDatum(slot_name);
call 0 returned 100%
-: 1025:
-: 1026: /* consistent wal location */
97: 1027: values[1] = CStringGetTextDatum(xloc);
call 0 returned 100%
-: 1028:
-: 1029: /* snapshot name, or NULL if none */
97: 1030: if (snapshot_name != NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1031: values[2] = CStringGetTextDatum(snapshot_name);
call 0 never executed
-: 1032: else
97: 1033: nulls[2] = true;
-: 1034:
-: 1035: /* plugin, or NULL if none */
97: 1036: if (cmd->plugin != NULL)
branch 0 taken 57% (fallthrough)
branch 1 taken 43%
55: 1037: values[3] = CStringGetTextDatum(cmd->plugin);
call 0 returned 100%
-: 1038: else
42: 1039: nulls[3] = true;
-: 1040:
-: 1041: /* send it to dest */
97: 1042: do_tup_output(tstate, values, nulls);
call 0 returned 100%
97: 1043: end_tup_output(tstate);
call 0 returned 100%
-: 1044:
97: 1045: ReplicationSlotRelease();
call 0 returned 100%
97: 1046:}
-: 1047:
-: 1048:/*
-: 1049: * Get rid of a replication slot that is no longer wanted.
-: 1050: */
-: 1051:static void
function DropReplicationSlot called 7 returned 100% blocks executed 100%
7: 1052:DropReplicationSlot(DropReplicationSlotCmd *cmd)
-: 1053:{
7: 1054: ReplicationSlotDrop(cmd->slotname, !cmd->wait);
call 0 returned 100%
7: 1055: EndCommand("DROP_REPLICATION_SLOT", DestRemote);
call 0 returned 100%
7: 1056:}
-: 1057:
-: 1058:/*
-: 1059: * Load previously initiated logical slot and prepare for sending data (via
-: 1060: * WalSndLoop).
-: 1061: */
-: 1062:static void
function StartLogicalReplication called 26 returned 12% blocks executed 68%
26: 1063:StartLogicalReplication(StartReplicationCmd *cmd)
-: 1064:{
-: 1065: StringInfoData buf;
-: 1066:
-: 1067: /* make sure that our requirements are still fulfilled */
26: 1068: CheckLogicalDecodingRequirements();
call 0 returned 100%
-: 1069:
26: 1070: Assert(!MyReplicationSlot);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1071:
26: 1072: ReplicationSlotAcquire(cmd->slotname, true);
call 0 returned 100%
-: 1073:
-: 1074: /*
-: 1075: * Force a disconnect, so that the decoding code doesn't need to care
-: 1076: * about an eventual switch from running in recovery, to running in a
-: 1077: * normal environment. Client code is expected to handle reconnects.
-: 1078: */
26: 1079: if (am_cascading_walsender && !RecoveryInProgress())
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
branch 3 never executed
branch 4 never executed
-: 1080: {
#####: 1081: ereport(LOG,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 1082: (errmsg("terminating walsender process after promotion")));
#####: 1083: got_STOPPING = true;
-: 1084: }
-: 1085:
-: 1086: /*
-: 1087: * Create our decoding context, making it start at the previously ack'ed
-: 1088: * position.
-: 1089: *
-: 1090: * Do this before sending a CopyBothResponse message, so that any errors
-: 1091: * are reported early.
-: 1092: */
26: 1093: logical_decoding_ctx =
26: 1094: CreateDecodingContext(cmd->startpoint, cmd->options, false,
call 0 returned 100%
-: 1095: logical_read_xlog_page,
-: 1096: WalSndPrepareWrite, WalSndWriteData,
-: 1097: WalSndUpdateProgress);
-: 1098:
-: 1099:
26: 1100: WalSndSetState(WALSNDSTATE_CATCHUP);
call 0 returned 100%
-: 1101:
-: 1102: /* Send a CopyBothResponse message, and start streaming */
26: 1103: pq_beginmessage(&buf, 'W');
call 0 returned 100%
26: 1104: pq_sendbyte(&buf, 0);
call 0 returned 100%
26: 1105: pq_sendint16(&buf, 0);
call 0 returned 100%
26: 1106: pq_endmessage(&buf);
call 0 returned 100%
26: 1107: pq_flush();
call 0 returned 100%
-: 1108:
-: 1109:
-: 1110: /* Start reading WAL from the oldest required WAL. */
26: 1111: logical_startptr = MyReplicationSlot->data.restart_lsn;
-: 1112:
-: 1113: /*
-: 1114: * Report the location after which we'll send out further commits as the
-: 1115: * current sentPtr.
-: 1116: */
26: 1117: sentPtr = MyReplicationSlot->data.confirmed_flush;
-: 1118:
-: 1119: /* Also update the sent position status in shared memory */
26: 1120: SpinLockAcquire(&MyWalSnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
26: 1121: MyWalSnd->sentPtr = MyReplicationSlot->data.restart_lsn;
26: 1122: SpinLockRelease(&MyWalSnd->mutex);
call 0 returned 100%
-: 1123:
26: 1124: replication_active = true;
-: 1125:
26: 1126: SyncRepInitConfig();
call 0 returned 100%
-: 1127:
-: 1128: /* Main loop of walsender */
26: 1129: WalSndLoop(XLogSendLogical);
call 0 returned 12%
-: 1130:
3: 1131: FreeDecodingContext(logical_decoding_ctx);
call 0 returned 100%
3: 1132: ReplicationSlotRelease();
call 0 returned 100%
-: 1133:
3: 1134: replication_active = false;
3: 1135: if (got_STOPPING)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1136: proc_exit(0);
call 0 never executed
3: 1137: WalSndSetState(WALSNDSTATE_STARTUP);
call 0 returned 100%
-: 1138:
-: 1139: /* Get out of COPY mode (CommandComplete). */
3: 1140: EndCommand("COPY 0", DestRemote);
call 0 returned 100%
3: 1141:}
-: 1142:
-: 1143:/*
-: 1144: * LogicalDecodingContext 'prepare_write' callback.
-: 1145: *
-: 1146: * Prepare a write into a StringInfo.
-: 1147: *
-: 1148: * Don't do anything lasting in here, it's quite possible that nothing will be done
-: 1149: * with the data.
-: 1150: */
-: 1151:static void
function WalSndPrepareWrite called 969 returned 100% blocks executed 100%
969: 1152:WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write)
-: 1153:{
-: 1154: /* can't have sync rep confused by sending the same LSN several times */
969: 1155: if (!last_write)
branch 0 taken 6% (fallthrough)
branch 1 taken 94%
55: 1156: lsn = InvalidXLogRecPtr;
-: 1157:
969: 1158: resetStringInfo(ctx->out);
call 0 returned 100%
-: 1159:
969: 1160: pq_sendbyte(ctx->out, 'w');
call 0 returned 100%
969: 1161: pq_sendint64(ctx->out, lsn); /* dataStart */
call 0 returned 100%
969: 1162: pq_sendint64(ctx->out, lsn); /* walEnd */
call 0 returned 100%
-: 1163:
-: 1164: /*
-: 1165: * Fill out the sendtime later, just as it's done in XLogSendPhysical, but
-: 1166: * reserve space here.
-: 1167: */
969: 1168: pq_sendint64(ctx->out, 0); /* sendtime */
call 0 returned 100%
969: 1169:}
-: 1170:
-: 1171:/*
-: 1172: * LogicalDecodingContext 'write' callback.
-: 1173: *
-: 1174: * Actually write out data previously prepared by WalSndPrepareWrite out to
-: 1175: * the network. Take as long as needed, but process replies from the other
-: 1176: * side and check timeouts during that.
-: 1177: */
-: 1178:static void
function WalSndWriteData called 969 returned 100% blocks executed 35%
969: 1179:WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid,
-: 1180: bool last_write)
-: 1181:{
-: 1182: TimestampTz now;
-: 1183:
-: 1184: /* output previously gathered data in a CopyData packet */
969: 1185: pq_putmessage_noblock('d', ctx->out->data, ctx->out->len);
call 0 returned 100%
-: 1186:
-: 1187: /*
-: 1188: * Fill the send timestamp last, so that it is taken as late as possible.
-: 1189: * This is somewhat ugly, but the protocol is set as it's already used for
-: 1190: * several releases by streaming physical replication.
-: 1191: */
969: 1192: resetStringInfo(&tmpbuf);
call 0 returned 100%
969: 1193: now = GetCurrentTimestamp();
call 0 returned 100%
969: 1194: pq_sendint64(&tmpbuf, now);
call 0 returned 100%
969: 1195: memcpy(&ctx->out->data[1 + sizeof(int64) + sizeof(int64)],
969: 1196: tmpbuf.data, sizeof(int64));
-: 1197:
969: 1198: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1199:
-: 1200: /* Try to flush pending output to the client */
969: 1201: if (pq_flush_if_writable() != 0)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 1202: WalSndShutdown();
call 0 never executed
-: 1203:
-: 1204: /* Try taking fast path unless we get too close to walsender timeout. */
969: 1205: if (now < TimestampTzPlusMilliseconds(last_reply_timestamp,
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
969: 1206: wal_sender_timeout / 2) &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
969: 1207: !pq_is_send_pending())
call 0 returned 100%
-: 1208: {
1938: 1209: return;
-: 1210: }
-: 1211:
-: 1212: /* If we have pending write here, go to slow path */
-: 1213: for (;;)
-: 1214: {
-: 1215: int wakeEvents;
-: 1216: long sleeptime;
-: 1217:
-: 1218: /* Check for input from the client */
#####: 1219: ProcessRepliesIfAny();
call 0 never executed
-: 1220:
-: 1221: /* die if timeout was reached */
#####: 1222: WalSndCheckTimeOut();
call 0 never executed
-: 1223:
-: 1224: /* Send keepalive if the time has come */
#####: 1225: WalSndKeepaliveIfNecessary();
call 0 never executed
-: 1226:
#####: 1227: if (!pq_is_send_pending())
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 1228: break;
-: 1229:
#####: 1230: sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
call 0 never executed
call 1 never executed
-: 1231:
#####: 1232: wakeEvents = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH |
-: 1233: WL_SOCKET_WRITEABLE | WL_SOCKET_READABLE | WL_TIMEOUT;
-: 1234:
-: 1235: /* Sleep until something happens or we time out */
#####: 1236: (void) WaitLatchOrSocket(MyLatch, wakeEvents,
call 0 never executed
#####: 1237: MyProcPort->sock, sleeptime,
-: 1238: WAIT_EVENT_WAL_SENDER_WRITE_DATA);
-: 1239:
-: 1240: /* Clear any already-pending wakeups */
#####: 1241: ResetLatch(MyLatch);
call 0 never executed
-: 1242:
#####: 1243: CHECK_FOR_INTERRUPTS();
branch 0 never executed
branch 1 never executed
call 2 never executed
-: 1244:
-: 1245: /* Process any requests or signals received recently */
#####: 1246: if (ConfigReloadPending)
branch 0 never executed
branch 1 never executed
-: 1247: {
#####: 1248: ConfigReloadPending = false;
#####: 1249: ProcessConfigFile(PGC_SIGHUP);
call 0 never executed
#####: 1250: SyncRepInitConfig();
call 0 never executed
-: 1251: }
-: 1252:
-: 1253: /* Try to flush pending output to the client */
#####: 1254: if (pq_flush_if_writable() != 0)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 1255: WalSndShutdown();
call 0 never executed
#####: 1256: }
-: 1257:
-: 1258: /* reactivate latch so WalSndLoop knows to continue */
#####: 1259: SetLatch(MyLatch);
call 0 never executed
-: 1260:}
-: 1261:
-: 1262:/*
-: 1263: * LogicalDecodingContext 'update_progress' callback.
-: 1264: *
-: 1265: * Write the current position to the lag tracker (see XLogSendPhysical),
-: 1266: * and update the spill statistics.
-: 1267: */
-: 1268:static void
function WalSndUpdateProgress called 117 returned 100% blocks executed 100%
117: 1269:WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid)
-: 1270:{
-: 1271: static TimestampTz sendTime = 0;
117: 1272: TimestampTz now = GetCurrentTimestamp();
call 0 returned 100%
-: 1273:
-: 1274: /*
-: 1275: * Track lag no more than once per WALSND_LOGICAL_LAG_TRACK_INTERVAL_MS to
-: 1276: * avoid flooding the lag tracker when we commit frequently.
-: 1277: */
-: 1278:#define WALSND_LOGICAL_LAG_TRACK_INTERVAL_MS 1000
117: 1279: if (!TimestampDifferenceExceeds(sendTime, now,
call 0 returned 100%
branch 1 taken 87% (fallthrough)
branch 2 taken 13%
-: 1280: WALSND_LOGICAL_LAG_TRACK_INTERVAL_MS))
219: 1281: return;
-: 1282:
15: 1283: LagTrackerWrite(lsn, now);
call 0 returned 100%
15: 1284: sendTime = now;
-: 1285:
-: 1286: /*
-: 1287: * Update statistics about transactions that spilled to disk.
-: 1288: */
15: 1289: UpdateSpillStats(ctx);
call 0 returned 100%
-: 1290:}
-: 1291:
-: 1292:/*
-: 1293: * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
-: 1294: *
-: 1295: * Returns end LSN of flushed WAL. Normally this will be >= loc, but
-: 1296: * if we detect a shutdown request (either from postmaster or client)
-: 1297: * we will return early, so caller must always check.
-: 1298: */
-: 1299:static XLogRecPtr
function WalSndWaitForWal called 19964 returned 99% blocks executed 83%
19964: 1300:WalSndWaitForWal(XLogRecPtr loc)
-: 1301:{
-: 1302: int wakeEvents;
-: 1303: static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
-: 1304:
-: 1305: /*
-: 1306: * Fast path to avoid acquiring the spinlock in case we already know we
-: 1307: * have enough WAL available. This is particularly interesting if we're
-: 1308: * far behind.
-: 1309: */
39850: 1310: if (RecentFlushPtr != InvalidXLogRecPtr &&
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 50% (fallthrough)
branch 3 taken 50%
19886: 1311: loc <= RecentFlushPtr)
9969: 1312: return RecentFlushPtr;
-: 1313:
-: 1314: /* Get a more recent flush pointer. */
9995: 1315: if (!RecoveryInProgress())
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
9995: 1316: RecentFlushPtr = GetFlushRecPtr();
call 0 returned 100%
-: 1317: else
#####: 1318: RecentFlushPtr = GetXLogReplayRecPtr(NULL);
call 0 never executed
-: 1319:
-: 1320: for (;;)
-: 1321: {
-: 1322: long sleeptime;
-: 1323:
-: 1324: /* Clear any already-pending wakeups */
10186: 1325: ResetLatch(MyLatch);
call 0 returned 100%
-: 1326:
10186: 1327: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1328:
-: 1329: /* Process any requests or signals received recently */
10186: 1330: if (ConfigReloadPending)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1331: {
#####: 1332: ConfigReloadPending = false;
#####: 1333: ProcessConfigFile(PGC_SIGHUP);
call 0 never executed
#####: 1334: SyncRepInitConfig();
call 0 never executed
-: 1335: }
-: 1336:
-: 1337: /* Check for input from the client */
10186: 1338: ProcessRepliesIfAny();
call 0 returned 99%
-: 1339:
-: 1340: /*
-: 1341: * If we're shutting down, trigger pending WAL to be written out,
-: 1342: * otherwise we'd possibly end up waiting for WAL that never gets
-: 1343: * written, because walwriter has shut down already.
-: 1344: */
10170: 1345: if (got_STOPPING)
branch 0 taken 96% (fallthrough)
branch 1 taken 4%
9746: 1346: XLogBackgroundFlush();
call 0 returned 100%
-: 1347:
-: 1348: /* Update our idea of the currently flushed position. */
10170: 1349: if (!RecoveryInProgress())
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
10170: 1350: RecentFlushPtr = GetFlushRecPtr();
call 0 returned 100%
-: 1351: else
#####: 1352: RecentFlushPtr = GetXLogReplayRecPtr(NULL);
call 0 never executed
-: 1353:
-: 1354: /*
-: 1355: * If postmaster asked us to stop, don't wait anymore.
-: 1356: *
-: 1357: * It's important to do this check after the recomputation of
-: 1358: * RecentFlushPtr, so we can send all remaining data before shutting
-: 1359: * down.
-: 1360: */
10170: 1361: if (got_STOPPING)
branch 0 taken 96% (fallthrough)
branch 1 taken 4%
9746: 1362: break;
-: 1363:
-: 1364: /*
-: 1365: * We only send regular messages to the client for full decoded
-: 1366: * transactions, but a synchronous replication and walsender shutdown
-: 1367: * possibly are waiting for a later location. So we send pings
-: 1368: * containing the flush location every now and then.
-: 1369: */
662: 1370: if (MyWalSnd->flush < sentPtr &&
branch 0 taken 56% (fallthrough)
branch 1 taken 44%
branch 2 taken 46% (fallthrough)
branch 3 taken 54%
347: 1371: MyWalSnd->write < sentPtr &&
branch 0 taken 75% (fallthrough)
branch 1 taken 25%
109: 1372: !waiting_for_ping_response)
-: 1373: {
82: 1374: WalSndKeepalive(false);
call 0 returned 100%
82: 1375: waiting_for_ping_response = true;
-: 1376: }
-: 1377:
-: 1378: /* check whether we're done */
424: 1379: if (loc <= RecentFlushPtr)
branch 0 taken 54% (fallthrough)
branch 1 taken 46%
230: 1380: break;
-: 1381:
-: 1382: /* Waiting for new WAL. Since we need to wait, we're now caught up. */
194: 1383: WalSndCaughtUp = true;
-: 1384:
-: 1385: /*
-: 1386: * Try to flush any pending output to the client.
-: 1387: */
194: 1388: if (pq_flush_if_writable() != 0)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 1389: WalSndShutdown();
call 0 never executed
-: 1390:
-: 1391: /*
-: 1392: * If we have received CopyDone from the client, sent CopyDone
-: 1393: * ourselves, and the output buffer is empty, it's time to exit
-: 1394: * streaming, so fail the current WAL fetch request.
-: 1395: */
197: 1396: if (streamingDoneReceiving && streamingDoneSending &&
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 100% (fallthrough)
branch 5 taken 0%
3: 1397: !pq_is_send_pending())
call 0 returned 100%
3: 1398: break;
-: 1399:
-: 1400: /* die if timeout was reached */
191: 1401: WalSndCheckTimeOut();
call 0 returned 100%
-: 1402:
-: 1403: /* Send keepalive if the time has come */
191: 1404: WalSndKeepaliveIfNecessary();
call 0 returned 100%
-: 1405:
-: 1406: /*
-: 1407: * Sleep until something happens or we time out. Also wait for the
-: 1408: * socket becoming writable, if there's still pending output.
-: 1409: * Otherwise we might sit on sendable output data while waiting for
-: 1410: * new WAL to be generated. (But if we have nothing to send, we don't
-: 1411: * want to wake on socket-writable.)
-: 1412: */
191: 1413: sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
call 0 returned 100%
call 1 returned 100%
-: 1414:
191: 1415: wakeEvents = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH |
-: 1416: WL_SOCKET_READABLE | WL_TIMEOUT;
-: 1417:
191: 1418: if (pq_is_send_pending())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 1419: wakeEvents |= WL_SOCKET_WRITEABLE;
-: 1420:
191: 1421: (void) WaitLatchOrSocket(MyLatch, wakeEvents,
call 0 returned 100%
191: 1422: MyProcPort->sock, sleeptime,
-: 1423: WAIT_EVENT_WAL_SENDER_WAIT_WAL);
191: 1424: }
-: 1425:
-: 1426: /* reactivate latch so WalSndLoop knows to continue */
9979: 1427: SetLatch(MyLatch);
call 0 returned 100%
9979: 1428: return RecentFlushPtr;
-: 1429:}
-: 1430:
-: 1431:/*
-: 1432: * Execute an incoming replication command.
-: 1433: *
-: 1434: * Returns true if the cmd_string was recognized as WalSender command, false
-: 1435: * if not.
-: 1436: */
-: 1437:bool
function exec_replication_command called 808 returned 91% blocks executed 63%
808: 1438:exec_replication_command(const char *cmd_string)
-: 1439:{
-: 1440: int parse_rc;
-: 1441: Node *cmd_node;
-: 1442: MemoryContext cmd_context;
-: 1443: MemoryContext old_context;
-: 1444:
-: 1445: /*
-: 1446: * If WAL sender has been told that shutdown is getting close, switch its
-: 1447: * status accordingly to handle the next replication commands correctly.
-: 1448: */
808: 1449: if (got_STOPPING)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1450: WalSndSetState(WALSNDSTATE_STOPPING);
call 0 never executed
-: 1451:
-: 1452: /*
-: 1453: * Throw error if in stopping mode. We need prevent commands that could
-: 1454: * generate WAL while the shutdown checkpoint is being written. To be
-: 1455: * safe, we just prohibit all new commands.
-: 1456: */
808: 1457: if (MyWalSnd->state == WALSNDSTATE_STOPPING)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1458: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 1459: (errmsg("cannot execute new commands while WAL sender is in stopping mode")));
-: 1460:
-: 1461: /*
-: 1462: * CREATE_REPLICATION_SLOT ... LOGICAL exports a snapshot until the next
-: 1463: * command arrives. Clean up the old stuff if there's anything.
-: 1464: */
808: 1465: SnapBuildClearExportedSnapshot();
call 0 returned 100%
-: 1466:
808: 1467: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1468:
808: 1469: cmd_context = AllocSetContextCreate(CurrentMemoryContext,
call 0 returned 100%
-: 1470: "Replication command context",
-: 1471: ALLOCSET_DEFAULT_SIZES);
808: 1472: old_context = MemoryContextSwitchTo(cmd_context);
call 0 returned 100%
-: 1473:
808: 1474: replication_scanner_init(cmd_string);
call 0 returned 100%
808: 1475: parse_rc = replication_yyparse();
call 0 returned 100%
808: 1476: if (parse_rc != 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1477: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 1478: (errcode(ERRCODE_SYNTAX_ERROR),
-: 1479: (errmsg_internal("replication command parser returned %d",
-: 1480: parse_rc))));
-: 1481:
808: 1482: cmd_node = replication_parse_result;
-: 1483:
-: 1484: /*
-: 1485: * Log replication command if log_replication_commands is enabled. Even
-: 1486: * when it's disabled, log the command with DEBUG1 level for backward
-: 1487: * compatibility. Note that SQL commands are not logged here, and will be
-: 1488: * logged later if log_statement is enabled.
-: 1489: */
808: 1490: if (cmd_node->type != T_SQLCmd)
branch 0 taken 74% (fallthrough)
branch 1 taken 26%
600: 1491: ereport(log_replication_commands ? LOG : DEBUG1,
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
call 2 returned 100%
branch 3 taken 100% (fallthrough)
branch 4 taken 0%
call 5 returned 100%
call 6 returned 100%
-: 1492: (errmsg("received replication command: %s", cmd_string)));
-: 1493:
-: 1494: /*
-: 1495: * CREATE_REPLICATION_SLOT ... LOGICAL exports a snapshot. If it was
-: 1496: * called outside of transaction the snapshot should be cleared here.
-: 1497: */
808: 1498: if (!IsTransactionBlock())
call 0 returned 100%
branch 1 taken 77% (fallthrough)
branch 2 taken 23%
625: 1499: SnapBuildClearExportedSnapshot();
call 0 returned 100%
-: 1500:
-: 1501: /*
-: 1502: * For aborted transactions, don't allow anything except pure SQL, the
-: 1503: * exec_simple_query() will handle it correctly.
-: 1504: */
808: 1505: if (IsAbortedTransactionBlockState() && !IsA(cmd_node, SQLCmd))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
branch 3 never executed
branch 4 never executed
#####: 1506: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 1507: (errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
-: 1508: errmsg("current transaction is aborted, "
-: 1509: "commands ignored until end of transaction block")));
-: 1510:
808: 1511: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1512:
-: 1513: /*
-: 1514: * Allocate buffers that will be used for each outgoing and incoming
-: 1515: * message. We do this just once per command to reduce palloc overhead.
-: 1516: */
808: 1517: initStringInfo(&output_message);
call 0 returned 100%
808: 1518: initStringInfo(&reply_message);
call 0 returned 100%
808: 1519: initStringInfo(&tmpbuf);
call 0 returned 100%
-: 1520:
-: 1521: /* Report to pgstat that this process is running */
808: 1522: pgstat_report_activity(STATE_RUNNING, NULL);
call 0 returned 100%
-: 1523:
808: 1524: switch (cmd_node->type)
branch 0 taken 20%
branch 1 taken 6%
branch 2 taken 12%
branch 3 taken 1%
branch 4 taken 14%
branch 5 taken 1%
branch 6 taken 20%
branch 7 taken 26%
branch 8 taken 0%
-: 1525: {
-: 1526: case T_IdentifySystemCmd:
165: 1527: IdentifySystem();
call 0 returned 100%
165: 1528: break;
-: 1529:
-: 1530: case T_BaseBackupCmd:
49: 1531: PreventInTransactionBlock(true, "BASE_BACKUP");
call 0 returned 100%
49: 1532: SendBaseBackup((BaseBackupCmd *) cmd_node);
call 0 returned 88%
43: 1533: break;
-: 1534:
-: 1535: case T_CreateReplicationSlotCmd:
98: 1536: CreateReplicationSlot((CreateReplicationSlotCmd *) cmd_node);
call 0 returned 99%
97: 1537: break;
-: 1538:
-: 1539: case T_DropReplicationSlotCmd:
7: 1540: DropReplicationSlot((DropReplicationSlotCmd *) cmd_node);
call 0 returned 100%
7: 1541: break;
-: 1542:
-: 1543: case T_StartReplicationCmd:
-: 1544: {
115: 1545: StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
-: 1546:
115: 1547: PreventInTransactionBlock(true, "START_REPLICATION");
call 0 returned 100%
-: 1548:
115: 1549: if (cmd->kind == REPLICATION_KIND_PHYSICAL)
branch 0 taken 77% (fallthrough)
branch 1 taken 23%
89: 1550: StartReplication(cmd);
call 0 returned 52%
-: 1551: else
26: 1552: StartLogicalReplication(cmd);
call 0 returned 12%
49: 1553: break;
-: 1554: }
-: 1555:
-: 1556: case T_TimeLineHistoryCmd:
5: 1557: PreventInTransactionBlock(true, "TIMELINE_HISTORY");
call 0 returned 100%
5: 1558: SendTimeLineHistory((TimeLineHistoryCmd *) cmd_node);
call 0 returned 100%
5: 1559: break;
-: 1560:
-: 1561: case T_VariableShowStmt:
-: 1562: {
161: 1563: DestReceiver *dest = CreateDestReceiver(DestRemoteSimple);
call 0 returned 100%
161: 1564: VariableShowStmt *n = (VariableShowStmt *) cmd_node;
-: 1565:
-: 1566: /* syscache access needs a transaction environment */
161: 1567: StartTransactionCommand();
call 0 returned 100%
161: 1568: GetPGVariable(n->name, dest);
call 0 returned 100%
161: 1569: CommitTransactionCommand();
call 0 returned 100%
-: 1570: }
161: 1571: break;
-: 1572:
-: 1573: case T_SQLCmd:
208: 1574: if (MyDatabaseId == InvalidOid)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1575: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 1576: (errmsg("cannot execute SQL commands in WAL sender for physical replication")));
-: 1577:
-: 1578: /* Report to pgstat that this process is now idle */
208: 1579: pgstat_report_activity(STATE_IDLE, NULL);
call 0 returned 100%
-: 1580:
-: 1581: /* Tell the caller that this wasn't a WalSender command. */
208: 1582: return false;
-: 1583:
-: 1584: default:
#####: 1585: elog(ERROR, "unrecognized replication command node tag: %u",
call 0 never executed
call 1 never executed
call 2 never executed
-: 1586: cmd_node->type);
-: 1587: }
-: 1588:
-: 1589: /* done */
527: 1590: MemoryContextSwitchTo(old_context);
call 0 returned 100%
527: 1591: MemoryContextDelete(cmd_context);
call 0 returned 100%
-: 1592:
-: 1593: /* Send CommandComplete message */
527: 1594: EndCommand("SELECT", DestRemote);
call 0 returned 100%
-: 1595:
-: 1596: /* Report to pgstat that this process is now idle */
527: 1597: pgstat_report_activity(STATE_IDLE, NULL);
call 0 returned 100%
-: 1598:
527: 1599: return true;
-: 1600:}
-: 1601:
-: 1602:/*
-: 1603: * Process any incoming messages while streaming. Also checks if the remote
-: 1604: * end has closed the connection.
-: 1605: */
-: 1606:static void
function ProcessRepliesIfAny called 38408 returned 99% blocks executed 60%
38408: 1607:ProcessRepliesIfAny(void)
-: 1608:{
-: 1609: unsigned char firstchar;
-: 1610: int r;
38408: 1611: bool received = false;
-: 1612:
38408: 1613: last_processing = GetCurrentTimestamp();
call 0 returned 100%
-: 1614:
-: 1615: for (;;)
-: 1616: {
39886: 1617: pq_startmsgread();
call 0 returned 100%
39886: 1618: r = pq_getbyte_if_available(&firstchar);
call 0 returned 100%
39886: 1619: if (r < 0)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 1620: {
-: 1621: /* unexpected error or EOF */
11: 1622: ereport(COMMERROR,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 100%
-: 1623: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 1624: errmsg("unexpected EOF on standby connection")));
11: 1625: proc_exit(0);
call 0 returned 0%
-: 1626: }
39875: 1627: if (r == 0)
branch 0 taken 96% (fallthrough)
branch 1 taken 4%
-: 1628: {
-: 1629: /* no data available without blocking */
38359: 1630: pq_endmsgread();
call 0 returned 100%
38359: 1631: break;
-: 1632: }
-: 1633:
-: 1634: /* Read the message contents */
1516: 1635: resetStringInfo(&reply_message);
call 0 returned 100%
1516: 1636: if (pq_getmessage(&reply_message, 0))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 1637: {
#####: 1638: ereport(COMMERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 1639: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 1640: errmsg("unexpected EOF on standby connection")));
#####: 1641: proc_exit(0);
call 0 never executed
-: 1642: }
-: 1643:
-: 1644: /*
-: 1645: * If we already received a CopyDone from the frontend, the frontend
-: 1646: * should not send us anything until we've closed our end of the COPY.
-: 1647: * XXX: In theory, the frontend could already send the next command
-: 1648: * before receiving the CopyDone, but libpq doesn't currently allow
-: 1649: * that.
-: 1650: */
1516: 1651: if (streamingDoneReceiving && firstchar != 'X')
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 1652: ereport(FATAL,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 1653: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 1654: errmsg("unexpected standby message type \"%c\", after receiving CopyDone",
-: 1655: firstchar)));
-: 1656:
-: 1657: /* Handle the very limited subset of commands expected in this phase */
1516: 1658: switch (firstchar)
branch 0 taken 94%
branch 1 taken 3%
branch 2 taken 3%
branch 3 taken 0%
-: 1659: {
-: 1660: /*
-: 1661: * 'd' means a standby reply wrapped in a CopyData packet.
-: 1662: */
-: 1663: case 'd':
1426: 1664: ProcessStandbyMessage();
call 0 returned 100%
1426: 1665: received = true;
1426: 1666: break;
-: 1667:
-: 1668: /*
-: 1669: * CopyDone means the standby requested to finish streaming.
-: 1670: * Reply with CopyDone, if we had not sent that already.
-: 1671: */
-: 1672: case 'c':
52: 1673: if (!streamingDoneSending)
branch 0 taken 90% (fallthrough)
branch 1 taken 10%
-: 1674: {
47: 1675: pq_putmessage_noblock('c', NULL, 0);
call 0 returned 100%
47: 1676: streamingDoneSending = true;
-: 1677: }
-: 1678:
52: 1679: streamingDoneReceiving = true;
52: 1680: received = true;
52: 1681: break;
-: 1682:
-: 1683: /*
-: 1684: * 'X' means that the standby is closing down the socket.
-: 1685: */
-: 1686: case 'X':
38: 1687: proc_exit(0);
call 0 returned 0%
-: 1688:
-: 1689: default:
#####: 1690: ereport(FATAL,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 1691: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 1692: errmsg("invalid standby message type \"%c\"",
-: 1693: firstchar)));
-: 1694: }
1478: 1695: }
-: 1696:
-: 1697: /*
-: 1698: * Save the last reply timestamp if we've received at least one reply.
-: 1699: */
38359: 1700: if (received)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
-: 1701: {
1275: 1702: last_reply_timestamp = last_processing;
1275: 1703: waiting_for_ping_response = false;
-: 1704: }
38359: 1705:}
-: 1706:
-: 1707:/*
-: 1708: * Process a status update message received from standby.
-: 1709: */
-: 1710:static void
function ProcessStandbyMessage called 1426 returned 100% blocks executed 50%
1426: 1711:ProcessStandbyMessage(void)
-: 1712:{
-: 1713: char msgtype;
-: 1714:
-: 1715: /*
-: 1716: * Check message type from the first byte.
-: 1717: */
1426: 1718: msgtype = pq_getmsgbyte(&reply_message);
call 0 returned 100%
-: 1719:
1426: 1720: switch (msgtype)
branch 0 taken 97%
branch 1 taken 3%
branch 2 taken 0%
-: 1721: {
-: 1722: case 'r':
1389: 1723: ProcessStandbyReplyMessage();
call 0 returned 100%
1389: 1724: break;
-: 1725:
-: 1726: case 'h':
37: 1727: ProcessStandbyHSFeedbackMessage();
call 0 returned 100%
37: 1728: break;
-: 1729:
-: 1730: default:
#####: 1731: ereport(COMMERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 1732: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 1733: errmsg("unexpected message type \"%c\"", msgtype)));
#####: 1734: proc_exit(0);
call 0 never executed
-: 1735: }
1426: 1736:}
-: 1737:
-: 1738:/*
-: 1739: * Remember that a walreceiver just confirmed receipt of lsn `lsn`.
-: 1740: */
-: 1741:static void
function PhysicalConfirmReceivedLocation called 43 returned 100% blocks executed 85%
43: 1742:PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
-: 1743:{
43: 1744: bool changed = false;
43: 1745: ReplicationSlot *slot = MyReplicationSlot;
-: 1746:
43: 1747: Assert(lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
43: 1748: SpinLockAcquire(&slot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
43: 1749: if (slot->data.restart_lsn != lsn)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1750: {
43: 1751: changed = true;
43: 1752: slot->data.restart_lsn = lsn;
-: 1753: }
43: 1754: SpinLockRelease(&slot->mutex);
call 0 returned 100%
-: 1755:
43: 1756: if (changed)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1757: {
43: 1758: ReplicationSlotMarkDirty();
call 0 returned 100%
43: 1759: ReplicationSlotsComputeRequiredLSN();
call 0 returned 100%
-: 1760: }
-: 1761:
-: 1762: /*
-: 1763: * One could argue that the slot should be saved to disk now, but that'd
-: 1764: * be energy wasted - the worst lost information can do here is give us
-: 1765: * wrong information in a statistics view - we'll just potentially be more
-: 1766: * conservative in removing files.
-: 1767: */
43: 1768:}
-: 1769:
-: 1770:/*
-: 1771: * Regular reply from standby advising of WAL locations on standby server.
-: 1772: */
-: 1773:static void
function ProcessStandbyReplyMessage called 1389 returned 100% blocks executed 79%
1389: 1774:ProcessStandbyReplyMessage(void)
-: 1775:{
-: 1776: XLogRecPtr writePtr,
-: 1777: flushPtr,
-: 1778: applyPtr;
-: 1779: bool replyRequested;
-: 1780: TimeOffset writeLag,
-: 1781: flushLag,
-: 1782: applyLag;
-: 1783: bool clearLagTimes;
-: 1784: TimestampTz now;
-: 1785: TimestampTz replyTime;
-: 1786:
-: 1787: static bool fullyAppliedLastTime = false;
-: 1788:
-: 1789: /* the caller already consumed the msgtype byte */
1389: 1790: writePtr = pq_getmsgint64(&reply_message);
call 0 returned 100%
1389: 1791: flushPtr = pq_getmsgint64(&reply_message);
call 0 returned 100%
1389: 1792: applyPtr = pq_getmsgint64(&reply_message);
call 0 returned 100%
1389: 1793: replyTime = pq_getmsgint64(&reply_message);
call 0 returned 100%
1389: 1794: replyRequested = pq_getmsgbyte(&reply_message);
call 0 returned 100%
-: 1795:
1389: 1796: if (log_min_messages <= DEBUG2)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1797: {
-: 1798: char *replyTimeStr;
-: 1799:
-: 1800: /* Copy because timestamptz_to_str returns a static buffer */
#####: 1801: replyTimeStr = pstrdup(timestamptz_to_str(replyTime));
call 0 never executed
call 1 never executed
-: 1802:
#####: 1803: elog(DEBUG2, "write %X/%X flush %X/%X apply %X/%X%s reply_time %s",
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
-: 1804: (uint32) (writePtr >> 32), (uint32) writePtr,
-: 1805: (uint32) (flushPtr >> 32), (uint32) flushPtr,
-: 1806: (uint32) (applyPtr >> 32), (uint32) applyPtr,
-: 1807: replyRequested ? " (reply requested)" : "",
-: 1808: replyTimeStr);
-: 1809:
#####: 1810: pfree(replyTimeStr);
call 0 never executed
-: 1811: }
-: 1812:
-: 1813: /* See if we can compute the round-trip lag for these positions. */
1389: 1814: now = GetCurrentTimestamp();
call 0 returned 100%
1389: 1815: writeLag = LagTrackerRead(SYNC_REP_WAIT_WRITE, writePtr, now);
call 0 returned 100%
1389: 1816: flushLag = LagTrackerRead(SYNC_REP_WAIT_FLUSH, flushPtr, now);
call 0 returned 100%
1389: 1817: applyLag = LagTrackerRead(SYNC_REP_WAIT_APPLY, applyPtr, now);
call 0 returned 100%
-: 1818:
-: 1819: /*
-: 1820: * If the standby reports that it has fully replayed the WAL in two
-: 1821: * consecutive reply messages, then the second such message must result
-: 1822: * from wal_receiver_status_interval expiring on the standby. This is a
-: 1823: * convenient time to forget the lag times measured when it last
-: 1824: * wrote/flushed/applied a WAL record, to avoid displaying stale lag data
-: 1825: * until more WAL traffic arrives.
-: 1826: */
1389: 1827: clearLagTimes = false;
1389: 1828: if (applyPtr == sentPtr)
branch 0 taken 21% (fallthrough)
branch 1 taken 79%
-: 1829: {
288: 1830: if (fullyAppliedLastTime)
branch 0 taken 44% (fallthrough)
branch 1 taken 56%
126: 1831: clearLagTimes = true;
288: 1832: fullyAppliedLastTime = true;
-: 1833: }
-: 1834: else
1101: 1835: fullyAppliedLastTime = false;
-: 1836:
-: 1837: /* Send a reply if the standby requested one. */
1389: 1838: if (replyRequested)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1839: WalSndKeepalive(false);
call 0 never executed
-: 1840:
-: 1841: /*
-: 1842: * Update shared state for this WalSender process based on reply data from
-: 1843: * standby.
-: 1844: */
-: 1845: {
1389: 1846: WalSnd *walsnd = MyWalSnd;
-: 1847:
1389: 1848: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
1389: 1849: walsnd->write = writePtr;
1389: 1850: walsnd->flush = flushPtr;
1389: 1851: walsnd->apply = applyPtr;
1389: 1852: if (writeLag != -1 || clearLagTimes)
branch 0 taken 84% (fallthrough)
branch 1 taken 16%
branch 2 taken 10% (fallthrough)
branch 3 taken 90%
342: 1853: walsnd->writeLag = writeLag;
1389: 1854: if (flushLag != -1 || clearLagTimes)
branch 0 taken 20% (fallthrough)
branch 1 taken 80%
branch 2 taken 38% (fallthrough)
branch 3 taken 62%
1221: 1855: walsnd->flushLag = flushLag;
1389: 1856: if (applyLag != -1 || clearLagTimes)
branch 0 taken 67% (fallthrough)
branch 1 taken 33%
branch 2 taken 12% (fallthrough)
branch 3 taken 88%
578: 1857: walsnd->applyLag = applyLag;
1389: 1858: walsnd->replyTime = replyTime;
1389: 1859: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 1860: }
-: 1861:
1389: 1862: if (!am_cascading_walsender)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
1371: 1863: SyncRepReleaseWaiters();
call 0 returned 100%
-: 1864:
-: 1865: /*
-: 1866: * Advance our local xmin horizon when the client confirmed a flush.
-: 1867: */
1389: 1868: if (MyReplicationSlot && flushPtr != InvalidXLogRecPtr)
branch 0 taken 71% (fallthrough)
branch 1 taken 29%
branch 2 taken 99% (fallthrough)
branch 3 taken 1%
-: 1869: {
977: 1870: if (SlotIsLogical(MyReplicationSlot))
branch 0 taken 96% (fallthrough)
branch 1 taken 4%
934: 1871: LogicalConfirmReceivedLocation(flushPtr);
call 0 returned 100%
-: 1872: else
43: 1873: PhysicalConfirmReceivedLocation(flushPtr);
call 0 returned 100%
-: 1874: }
1389: 1875:}
-: 1876:
-: 1877:/* compute new replication slot xmin horizon if needed */
-: 1878:static void
function PhysicalReplicationSlotNewXmin called 0 returned 0% blocks executed 0%
#####: 1879:PhysicalReplicationSlotNewXmin(TransactionId feedbackXmin, TransactionId feedbackCatalogXmin)
-: 1880:{
#####: 1881: bool changed = false;
#####: 1882: ReplicationSlot *slot = MyReplicationSlot;
-: 1883:
#####: 1884: SpinLockAcquire(&slot->mutex);
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
#####: 1885: MyPgXact->xmin = InvalidTransactionId;
-: 1886:
-: 1887: /*
-: 1888: * For physical replication we don't need the interlock provided by xmin
-: 1889: * and effective_xmin since the consequences of a missed increase are
-: 1890: * limited to query cancellations, so set both at once.
-: 1891: */
#####: 1892: if (!TransactionIdIsNormal(slot->data.xmin) ||
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
#####: 1893: !TransactionIdIsNormal(feedbackXmin) ||
branch 0 never executed
branch 1 never executed
#####: 1894: TransactionIdPrecedes(slot->data.xmin, feedbackXmin))
call 0 never executed
-: 1895: {
#####: 1896: changed = true;
#####: 1897: slot->data.xmin = feedbackXmin;
#####: 1898: slot->effective_xmin = feedbackXmin;
-: 1899: }
#####: 1900: if (!TransactionIdIsNormal(slot->data.catalog_xmin) ||
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
#####: 1901: !TransactionIdIsNormal(feedbackCatalogXmin) ||
branch 0 never executed
branch 1 never executed
#####: 1902: TransactionIdPrecedes(slot->data.catalog_xmin, feedbackCatalogXmin))
call 0 never executed
-: 1903: {
#####: 1904: changed = true;
#####: 1905: slot->data.catalog_xmin = feedbackCatalogXmin;
#####: 1906: slot->effective_catalog_xmin = feedbackCatalogXmin;
-: 1907: }
#####: 1908: SpinLockRelease(&slot->mutex);
call 0 never executed
-: 1909:
#####: 1910: if (changed)
branch 0 never executed
branch 1 never executed
-: 1911: {
#####: 1912: ReplicationSlotMarkDirty();
call 0 never executed
#####: 1913: ReplicationSlotsComputeRequiredXmin(false);
call 0 never executed
-: 1914: }
#####: 1915:}
-: 1916:
-: 1917:/*
-: 1918: * Check that the provided xmin/epoch are sane, that is, not in the future
-: 1919: * and not so far back as to be already wrapped around.
-: 1920: *
-: 1921: * Epoch of nextXid should be same as standby, or if the counter has
-: 1922: * wrapped, then one greater than standby.
-: 1923: *
-: 1924: * This check doesn't care about whether clog exists for these xids
-: 1925: * at all.
-: 1926: */
-: 1927:static bool
function TransactionIdInRecentPast called 0 returned 0% blocks executed 0%
#####: 1928:TransactionIdInRecentPast(TransactionId xid, uint32 epoch)
-: 1929:{
-: 1930: FullTransactionId nextFullXid;
-: 1931: TransactionId nextXid;
-: 1932: uint32 nextEpoch;
-: 1933:
#####: 1934: nextFullXid = ReadNextFullTransactionId();
call 0 never executed
#####: 1935: nextXid = XidFromFullTransactionId(nextFullXid);
#####: 1936: nextEpoch = EpochFromFullTransactionId(nextFullXid);
-: 1937:
#####: 1938: if (xid <= nextXid)
branch 0 never executed
branch 1 never executed
-: 1939: {
#####: 1940: if (epoch != nextEpoch)
branch 0 never executed
branch 1 never executed
#####: 1941: return false;
-: 1942: }
-: 1943: else
-: 1944: {
#####: 1945: if (epoch + 1 != nextEpoch)
branch 0 never executed
branch 1 never executed
#####: 1946: return false;
-: 1947: }
-: 1948:
#####: 1949: if (!TransactionIdPrecedesOrEquals(xid, nextXid))
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 1950: return false; /* epoch OK, but it's wrapped around */
-: 1951:
#####: 1952: return true;
-: 1953:}
-: 1954:
-: 1955:/*
-: 1956: * Hot Standby feedback
-: 1957: */
-: 1958:static void
function ProcessStandbyHSFeedbackMessage called 37 returned 100% blocks executed 41%
37: 1959:ProcessStandbyHSFeedbackMessage(void)
-: 1960:{
-: 1961: TransactionId feedbackXmin;
-: 1962: uint32 feedbackEpoch;
-: 1963: TransactionId feedbackCatalogXmin;
-: 1964: uint32 feedbackCatalogEpoch;
-: 1965: TimestampTz replyTime;
-: 1966:
-: 1967: /*
-: 1968: * Decipher the reply message. The caller already consumed the msgtype
-: 1969: * byte. See XLogWalRcvSendHSFeedback() in walreceiver.c for the creation
-: 1970: * of this message.
-: 1971: */
37: 1972: replyTime = pq_getmsgint64(&reply_message);
call 0 returned 100%
37: 1973: feedbackXmin = pq_getmsgint(&reply_message, 4);
call 0 returned 100%
37: 1974: feedbackEpoch = pq_getmsgint(&reply_message, 4);
call 0 returned 100%
37: 1975: feedbackCatalogXmin = pq_getmsgint(&reply_message, 4);
call 0 returned 100%
37: 1976: feedbackCatalogEpoch = pq_getmsgint(&reply_message, 4);
call 0 returned 100%
-: 1977:
37: 1978: if (log_min_messages <= DEBUG2)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1979: {
-: 1980: char *replyTimeStr;
-: 1981:
-: 1982: /* Copy because timestamptz_to_str returns a static buffer */
#####: 1983: replyTimeStr = pstrdup(timestamptz_to_str(replyTime));
call 0 never executed
call 1 never executed
-: 1984:
#####: 1985: elog(DEBUG2, "hot standby feedback xmin %u epoch %u, catalog_xmin %u epoch %u reply_time %s",
call 0 never executed
call 1 never executed
-: 1986: feedbackXmin,
-: 1987: feedbackEpoch,
-: 1988: feedbackCatalogXmin,
-: 1989: feedbackCatalogEpoch,
-: 1990: replyTimeStr);
-: 1991:
#####: 1992: pfree(replyTimeStr);
call 0 never executed
-: 1993: }
-: 1994:
-: 1995: /*
-: 1996: * Update shared state for this WalSender process based on reply data from
-: 1997: * standby.
-: 1998: */
-: 1999: {
37: 2000: WalSnd *walsnd = MyWalSnd;
-: 2001:
37: 2002: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
37: 2003: walsnd->replyTime = replyTime;
37: 2004: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 2005: }
-: 2006:
-: 2007: /*
-: 2008: * Unset WalSender's xmins if the feedback message values are invalid.
-: 2009: * This happens when the downstream turned hot_standby_feedback off.
-: 2010: */
37: 2011: if (!TransactionIdIsNormal(feedbackXmin)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
37: 2012: && !TransactionIdIsNormal(feedbackCatalogXmin))
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 2013: {
37: 2014: MyPgXact->xmin = InvalidTransactionId;
37: 2015: if (MyReplicationSlot != NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2016: PhysicalReplicationSlotNewXmin(feedbackXmin, feedbackCatalogXmin);
call 0 never executed
37: 2017: return;
-: 2018: }
-: 2019:
-: 2020: /*
-: 2021: * Check that the provided xmin/epoch are sane, that is, not in the future
-: 2022: * and not so far back as to be already wrapped around. Ignore if not.
-: 2023: */
#####: 2024: if (TransactionIdIsNormal(feedbackXmin) &&
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
#####: 2025: !TransactionIdInRecentPast(feedbackXmin, feedbackEpoch))
call 0 never executed
#####: 2026: return;
-: 2027:
#####: 2028: if (TransactionIdIsNormal(feedbackCatalogXmin) &&
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
#####: 2029: !TransactionIdInRecentPast(feedbackCatalogXmin, feedbackCatalogEpoch))
call 0 never executed
#####: 2030: return;
-: 2031:
-: 2032: /*
-: 2033: * Set the WalSender's xmin equal to the standby's requested xmin, so that
-: 2034: * the xmin will be taken into account by GetOldestXmin. This will hold
-: 2035: * back the removal of dead rows and thereby prevent the generation of
-: 2036: * cleanup conflicts on the standby server.
-: 2037: *
-: 2038: * There is a small window for a race condition here: although we just
-: 2039: * checked that feedbackXmin precedes nextXid, the nextXid could have
-: 2040: * gotten advanced between our fetching it and applying the xmin below,
-: 2041: * perhaps far enough to make feedbackXmin wrap around. In that case the
-: 2042: * xmin we set here would be "in the future" and have no effect. No point
-: 2043: * in worrying about this since it's too late to save the desired data
-: 2044: * anyway. Assuming that the standby sends us an increasing sequence of
-: 2045: * xmins, this could only happen during the first reply cycle, else our
-: 2046: * own xmin would prevent nextXid from advancing so far.
-: 2047: *
-: 2048: * We don't bother taking the ProcArrayLock here. Setting the xmin field
-: 2049: * is assumed atomic, and there's no real need to prevent a concurrent
-: 2050: * GetOldestXmin. (If we're moving our xmin forward, this is obviously
-: 2051: * safe, and if we're moving it backwards, well, the data is at risk
-: 2052: * already since a VACUUM could have just finished calling GetOldestXmin.)
-: 2053: *
-: 2054: * If we're using a replication slot we reserve the xmin via that,
-: 2055: * otherwise via the walsender's PGXACT entry. We can only track the
-: 2056: * catalog xmin separately when using a slot, so we store the least of the
-: 2057: * two provided when not using a slot.
-: 2058: *
-: 2059: * XXX: It might make sense to generalize the ephemeral slot concept and
-: 2060: * always use the slot mechanism to handle the feedback xmin.
-: 2061: */
#####: 2062: if (MyReplicationSlot != NULL) /* XXX: persistency configurable? */
branch 0 never executed
branch 1 never executed
#####: 2063: PhysicalReplicationSlotNewXmin(feedbackXmin, feedbackCatalogXmin);
call 0 never executed
-: 2064: else
-: 2065: {
#####: 2066: if (TransactionIdIsNormal(feedbackCatalogXmin)
branch 0 never executed
branch 1 never executed
#####: 2067: && TransactionIdPrecedes(feedbackCatalogXmin, feedbackXmin))
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 2068: MyPgXact->xmin = feedbackCatalogXmin;
-: 2069: else
#####: 2070: MyPgXact->xmin = feedbackXmin;
-: 2071: }
-: 2072:}
-: 2073:
-: 2074:/*
-: 2075: * Compute how long send/receive loops should sleep.
-: 2076: *
-: 2077: * If wal_sender_timeout is enabled we want to wake up in time to send
-: 2078: * keepalives and to abort the connection if wal_sender_timeout has been
-: 2079: * reached.
-: 2080: */
-: 2081:static long
function WalSndComputeSleeptime called 6331 returned 100% blocks executed 100%
6331: 2082:WalSndComputeSleeptime(TimestampTz now)
-: 2083:{
6331: 2084: long sleeptime = 10000; /* 10 s */
-: 2085:
6331: 2086: if (wal_sender_timeout > 0 && last_reply_timestamp > 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
-: 2087: {
-: 2088: TimestampTz wakeup_time;
-: 2089: long sec_to_timeout;
-: 2090: int microsec_to_timeout;
-: 2091:
-: 2092: /*
-: 2093: * At the latest stop sleeping once wal_sender_timeout has been
-: 2094: * reached.
-: 2095: */
6331: 2096: wakeup_time = TimestampTzPlusMilliseconds(last_reply_timestamp,
-: 2097: wal_sender_timeout);
-: 2098:
-: 2099: /*
-: 2100: * If no ping has been sent yet, wakeup when it's time to do so.
-: 2101: * WalSndKeepaliveIfNecessary() wants to send a keepalive once half of
-: 2102: * the timeout passed without a response.
-: 2103: */
6331: 2104: if (!waiting_for_ping_response)
branch 0 taken 21% (fallthrough)
branch 1 taken 79%
1355: 2105: wakeup_time = TimestampTzPlusMilliseconds(last_reply_timestamp,
-: 2106: wal_sender_timeout / 2);
-: 2107:
-: 2108: /* Compute relative time until wakeup. */
6331: 2109: TimestampDifference(now, wakeup_time,
call 0 returned 100%
-: 2110: &sec_to_timeout, µsec_to_timeout);
-: 2111:
12662: 2112: sleeptime = sec_to_timeout * 1000 +
6331: 2113: microsec_to_timeout / 1000;
-: 2114: }
-: 2115:
6331: 2116: return sleeptime;
-: 2117:}
-: 2118:
-: 2119:/*
-: 2120: * Check whether there have been responses by the client within
-: 2121: * wal_sender_timeout and shutdown if not. Using last_processing as the
-: 2122: * reference point avoids counting server-side stalls against the client.
-: 2123: * However, a long server-side stall can make WalSndKeepaliveIfNecessary()
-: 2124: * postdate last_processing by more than wal_sender_timeout. If that happens,
-: 2125: * the client must reply almost immediately to avoid a timeout. This rarely
-: 2126: * affects the default configuration, under which clients spontaneously send a
-: 2127: * message every standby_message_timeout = wal_sender_timeout/6 = 10s. We
-: 2128: * could eliminate that problem by recognizing timeout expiration at
-: 2129: * wal_sender_timeout/2 after the keepalive.
-: 2130: */
-: 2131:static void
function WalSndCheckTimeOut called 28299 returned 100% blocks executed 36%
28299: 2132:WalSndCheckTimeOut(void)
-: 2133:{
-: 2134: TimestampTz timeout;
-: 2135:
-: 2136: /* don't bail out if we're doing something that doesn't require timeouts */
28299: 2137: if (last_reply_timestamp <= 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
28299: 2138: return;
-: 2139:
28299: 2140: timeout = TimestampTzPlusMilliseconds(last_reply_timestamp,
-: 2141: wal_sender_timeout);
-: 2142:
28299: 2143: if (wal_sender_timeout > 0 && last_processing >= timeout)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
-: 2144: {
-: 2145: /*
-: 2146: * Since typically expiration of replication timeout means
-: 2147: * communication problem, we don't send the error message to the
-: 2148: * standby.
-: 2149: */
#####: 2150: ereport(COMMERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 2151: (errmsg("terminating walsender process due to replication timeout")));
-: 2152:
#####: 2153: WalSndShutdown();
call 0 never executed
-: 2154: }
-: 2155:}
-: 2156:
-: 2157:/* Main loop of walsender process that streams the WAL over Copy messages. */
-: 2158:static void
function WalSndLoop called 114 returned 43% blocks executed 96%
114: 2159:WalSndLoop(WalSndSendDataCallback send_data)
-: 2160:{
-: 2161: /*
-: 2162: * Initialize the last reply timestamp. That enables timeout processing
-: 2163: * from hereon.
-: 2164: */
114: 2165: last_reply_timestamp = GetCurrentTimestamp();
call 0 returned 100%
114: 2166: waiting_for_ping_response = false;
-: 2167:
-: 2168: /*
-: 2169: * Loop until we reach the end of this timeline or the client requests to
-: 2170: * stop streaming.
-: 2171: */
-: 2172: for (;;)
-: 2173: {
-: 2174: /* Clear any already-pending wakeups */
28222: 2175: ResetLatch(MyLatch);
call 0 returned 100%
-: 2176:
28222: 2177: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2178:
-: 2179: /* Process any requests or signals received recently */
28222: 2180: if (ConfigReloadPending)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 2181: {
12: 2182: ConfigReloadPending = false;
12: 2183: ProcessConfigFile(PGC_SIGHUP);
call 0 returned 100%
12: 2184: SyncRepInitConfig();
call 0 returned 100%
-: 2185: }
-: 2186:
-: 2187: /* Check for input from the client */
28222: 2188: ProcessRepliesIfAny();
call 0 returned 99%
-: 2189:
-: 2190: /*
-: 2191: * If we have received CopyDone from the client, sent CopyDone
-: 2192: * ourselves, and the output buffer is empty, it's time to exit
-: 2193: * streaming.
-: 2194: */
28279: 2195: if (streamingDoneReceiving && streamingDoneSending &&
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 54% (fallthrough)
branch 5 taken 46%
90: 2196: !pq_is_send_pending())
call 0 returned 100%
49: 2197: break;
-: 2198:
-: 2199: /*
-: 2200: * If we don't have any pending data in the output buffer, try to send
-: 2201: * some more. If there is some, we don't bother to call send_data
-: 2202: * again until we've flushed it ... but we'd better assume we are not
-: 2203: * caught up.
-: 2204: */
28140: 2205: if (!pq_is_send_pending())
call 0 returned 100%
branch 1 taken 95% (fallthrough)
branch 2 taken 5%
26845: 2206: send_data();
call 0 returned 99%
-: 2207: else
1295: 2208: WalSndCaughtUp = false;
-: 2209:
-: 2210: /* Try to flush pending output to the client */
28124: 2211: if (pq_flush_if_writable() != 0)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 2212: WalSndShutdown();
call 0 never executed
-: 2213:
-: 2214: /* If nothing remains to be sent right now ... */
28124: 2215: if (WalSndCaughtUp && !pq_is_send_pending())
branch 0 taken 58% (fallthrough)
branch 1 taken 42%
call 2 returned 100%
branch 3 taken 99% (fallthrough)
branch 4 taken 1%
-: 2216: {
-: 2217: /*
-: 2218: * If we're in catchup state, move to streaming. This is an
-: 2219: * important state change for users to know about, since before
-: 2220: * this point data loss might occur if the primary dies and we
-: 2221: * need to failover to the standby. The state change is also
-: 2222: * important for synchronous replication, since commits that
-: 2223: * started to wait at that point might wait for some time.
-: 2224: */
16171: 2225: if (MyWalSnd->state == WALSNDSTATE_CATCHUP)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 2226: {
114: 2227: ereport(DEBUG1,
call 0 returned 100%
branch 1 taken 1% (fallthrough)
branch 2 taken 99%
call 3 returned 100%
call 4 returned 100%
-: 2228: (errmsg("\"%s\" has now caught up with upstream server",
-: 2229: application_name)));
114: 2230: WalSndSetState(WALSNDSTATE_STREAMING);
call 0 returned 100%
-: 2231: }
-: 2232:
-: 2233: /*
-: 2234: * When SIGUSR2 arrives, we send any outstanding logs up to the
-: 2235: * shutdown checkpoint record (i.e., the latest record), wait for
-: 2236: * them to be replicated to the standby, and exit. This may be a
-: 2237: * normal termination at shutdown, or a promotion, the walsender
-: 2238: * is not sure which.
-: 2239: */
16171: 2240: if (got_SIGUSR2)
branch 0 taken 30% (fallthrough)
branch 1 taken 70%
4887: 2241: WalSndDone(send_data);
call 0 returned 99%
-: 2242: }
-: 2243:
-: 2244: /* Check for replication timeout. */
28108: 2245: WalSndCheckTimeOut();
call 0 returned 100%
-: 2246:
-: 2247: /* Send keepalive if the time has come */
28108: 2248: WalSndKeepaliveIfNecessary();
call 0 returned 100%
-: 2249:
-: 2250: /*
-: 2251: * We don't block if not caught up, unless there is unsent data
-: 2252: * pending in which case we'd better block until the socket is
-: 2253: * write-ready. This test is only needed for the case where the
-: 2254: * send_data callback handled a subset of the available data but then
-: 2255: * pq_flush_if_writable flushed it all --- we should immediately try
-: 2256: * to send more.
-: 2257: */
28108: 2258: if ((WalSndCaughtUp && !streamingDoneSending) || pq_is_send_pending())
branch 0 taken 57% (fallthrough)
branch 1 taken 43%
branch 2 taken 65% (fallthrough)
branch 3 taken 35%
call 4 returned 100%
branch 5 taken 2% (fallthrough)
branch 6 taken 98%
-: 2259: {
-: 2260: long sleeptime;
-: 2261: int wakeEvents;
-: 2262:
6140: 2263: wakeEvents = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH | WL_TIMEOUT |
-: 2264: WL_SOCKET_READABLE;
-: 2265:
-: 2266: /*
-: 2267: * Use fresh timestamp, not last_processing, to reduce the chance
-: 2268: * of reaching wal_sender_timeout before sending a keepalive.
-: 2269: */
6140: 2270: sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
call 0 returned 100%
call 1 returned 100%
-: 2271:
6140: 2272: if (pq_is_send_pending())
call 0 returned 100%
branch 1 taken 21% (fallthrough)
branch 2 taken 79%
1277: 2273: wakeEvents |= WL_SOCKET_WRITEABLE;
-: 2274:
-: 2275: /* Sleep until something happens or we time out */
6140: 2276: (void) WaitLatchOrSocket(MyLatch, wakeEvents,
call 0 returned 100%
6140: 2277: MyProcPort->sock, sleeptime,
-: 2278: WAIT_EVENT_WAL_SENDER_MAIN);
-: 2279: }
28108: 2280: }
49: 2281: return;
-: 2282:}
-: 2283:
-: 2284:/* Initialize a per-walsender data structure for this walsender process */
-: 2285:static void
function InitWalSenderSlot called 229 returned 100% blocks executed 79%
229: 2286:InitWalSenderSlot(void)
-: 2287:{
-: 2288: int i;
-: 2289:
-: 2290: /*
-: 2291: * WalSndCtl should be set up already (we inherit this by fork() or
-: 2292: * EXEC_BACKEND mechanism from the postmaster).
-: 2293: */
229: 2294: Assert(WalSndCtl != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
229: 2295: Assert(MyWalSnd == NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2296:
-: 2297: /*
-: 2298: * Find a free walsender slot and reserve it. This must not fail due to
-: 2299: * the prior check for free WAL senders in InitProcess().
-: 2300: */
692: 2301: for (i = 0; i < max_wal_senders; i++)
branch 0 taken 100%
branch 1 taken 0% (fallthrough)
-: 2302: {
346: 2303: WalSnd *walsnd = &WalSndCtl->walsnds[i];
-: 2304:
346: 2305: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 2306:
346: 2307: if (walsnd->pid != 0)
branch 0 taken 34% (fallthrough)
branch 1 taken 66%
-: 2308: {
117: 2309: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
117: 2310: continue;
-: 2311: }
-: 2312: else
-: 2313: {
-: 2314: /*
-: 2315: * Found a free slot. Reserve it for us.
-: 2316: */
229: 2317: walsnd->pid = MyProcPid;
229: 2318: walsnd->sentPtr = InvalidXLogRecPtr;
229: 2319: walsnd->write = InvalidXLogRecPtr;
229: 2320: walsnd->flush = InvalidXLogRecPtr;
229: 2321: walsnd->apply = InvalidXLogRecPtr;
229: 2322: walsnd->writeLag = -1;
229: 2323: walsnd->flushLag = -1;
229: 2324: walsnd->applyLag = -1;
229: 2325: walsnd->state = WALSNDSTATE_STARTUP;
229: 2326: walsnd->latch = &MyProc->procLatch;
229: 2327: walsnd->replyTime = 0;
229: 2328: walsnd->spillTxns = 0;
229: 2329: walsnd->spillCount = 0;
229: 2330: walsnd->spillBytes = 0;
229: 2331: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 2332: /* don't need the lock anymore */
229: 2333: MyWalSnd = (WalSnd *) walsnd;
-: 2334:
229: 2335: break;
-: 2336: }
-: 2337: }
-: 2338:
229: 2339: Assert(MyWalSnd != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2340:
-: 2341: /* Arrange to clean up at walsender exit */
229: 2342: on_shmem_exit(WalSndKill, 0);
call 0 returned 100%
229: 2343:}
-: 2344:
-: 2345:/* Destroy the per-walsender data structure for this walsender process */
-: 2346:static void
function WalSndKill called 229 returned 100% blocks executed 75%
229: 2347:WalSndKill(int code, Datum arg)
-: 2348:{
229: 2349: WalSnd *walsnd = MyWalSnd;
-: 2350:
229: 2351: Assert(walsnd != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2352:
229: 2353: MyWalSnd = NULL;
-: 2354:
229: 2355: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 2356: /* clear latch while holding the spinlock, so it can safely be read */
229: 2357: walsnd->latch = NULL;
-: 2358: /* Mark WalSnd struct as no longer being in use. */
229: 2359: walsnd->pid = 0;
229: 2360: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
229: 2361:}
-: 2362:
-: 2363:/*
-: 2364: * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
-: 2365: *
-: 2366: * XXX probably this should be improved to suck data directly from the
-: 2367: * WAL buffers when possible.
-: 2368: *
-: 2369: * Will open, and keep open, one WAL segment stored in the global file
-: 2370: * descriptor sendFile. This means if XLogRead is used once, there will
-: 2371: * always be one descriptor left open until the process ends, but never
-: 2372: * more than one.
-: 2373: */
-: 2374:static void
function XLogRead called 11777 returned 100% blocks executed 41%
11777: 2375:XLogRead(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count)
-: 2376:{
-: 2377: char *p;
-: 2378: XLogRecPtr recptr;
-: 2379: Size nbytes;
-: 2380: XLogSegNo segno;
-: 2381:
-: 2382:retry:
11777: 2383: p = buf;
11777: 2384: recptr = startptr;
11777: 2385: nbytes = count;
-: 2386:
35331: 2387: while (nbytes > 0)
branch 0 taken 50%
branch 1 taken 50% (fallthrough)
-: 2388: {
-: 2389: uint32 startoff;
-: 2390: int segbytes;
-: 2391: int readbytes;
-: 2392:
11777: 2393: startoff = XLogSegmentOffset(recptr, segcxt->ws_segsize);
-: 2394:
23396: 2395: if (sendSeg->ws_file < 0 ||
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
11619: 2396: !XLByteInSeg(recptr, sendSeg->ws_segno, segcxt->ws_segsize))
-: 2397: {
-: 2398: char path[MAXPGPATH];
-: 2399:
-: 2400: /* Switch to another logfile segment */
158: 2401: if (sendSeg->ws_file >= 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2402: close(sendSeg->ws_file);
call 0 never executed
-: 2403:
158: 2404: XLByteToSeg(recptr, sendSeg->ws_segno, segcxt->ws_segsize);
-: 2405:
-: 2406: /*-------
-: 2407: * When reading from a historic timeline, and there is a timeline
-: 2408: * switch within this segment, read from the WAL segment belonging
-: 2409: * to the new timeline.
-: 2410: *
-: 2411: * For example, imagine that this server is currently on timeline
-: 2412: * 5, and we're streaming timeline 4. The switch from timeline 4
-: 2413: * to 5 happened at 0/13002088. In pg_wal, we have these files:
-: 2414: *
-: 2415: * ...
-: 2416: * 000000040000000000000012
-: 2417: * 000000040000000000000013
-: 2418: * 000000050000000000000013
-: 2419: * 000000050000000000000014
-: 2420: * ...
-: 2421: *
-: 2422: * In this situation, when requested to send the WAL from
-: 2423: * segment 0x13, on timeline 4, we read the WAL from file
-: 2424: * 000000050000000000000013. Archive recovery prefers files from
-: 2425: * newer timelines, so if the segment was restored from the
-: 2426: * archive on this server, the file belonging to the old timeline,
-: 2427: * 000000040000000000000013, might not exist. Their contents are
-: 2428: * equal up to the switchpoint, because at a timeline switch, the
-: 2429: * used portion of the old segment is copied to the new file.
-: 2430: *-------
-: 2431: */
158: 2432: sendSeg->ws_tli = sendTimeLine;
158: 2433: if (sendTimeLineIsHistoric)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
-: 2434: {
-: 2435: XLogSegNo endSegNo;
-: 2436:
5: 2437: XLByteToSeg(sendTimeLineValidUpto, endSegNo, segcxt->ws_segsize);
5: 2438: if (sendSeg->ws_segno == endSegNo)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
5: 2439: sendSeg->ws_tli = sendTimeLineNextTLI;
-: 2440: }
-: 2441:
158: 2442: XLogFilePath(path, sendSeg->ws_tli, sendSeg->ws_segno, segcxt->ws_segsize);
call 0 returned 100%
-: 2443:
158: 2444: sendSeg->ws_file = BasicOpenFile(path, O_RDONLY | PG_BINARY);
call 0 returned 100%
158: 2445: if (sendSeg->ws_file < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 2446: {
-: 2447: /*
-: 2448: * If the file is not found, assume it's because the standby
-: 2449: * asked for a too old WAL segment that has already been
-: 2450: * removed or recycled.
-: 2451: */
#####: 2452: if (errno == ENOENT)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 2453: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 2454: (errcode_for_file_access(),
-: 2455: errmsg("requested WAL segment %s has already been removed",
-: 2456: XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno))));
-: 2457: else
#####: 2458: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2459: (errcode_for_file_access(),
-: 2460: errmsg("could not open file \"%s\": %m",
-: 2461: path)));
-: 2462: }
158: 2463: sendSeg->ws_off = 0;
-: 2464: }
-: 2465:
-: 2466: /* Need to seek in the file? */
11777: 2467: if (sendSeg->ws_off != startoff)
branch 0 taken 85% (fallthrough)
branch 1 taken 15%
-: 2468: {
9979: 2469: if (lseek(sendSeg->ws_file, (off_t) startoff, SEEK_SET) < 0)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 2470: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 2471: (errcode_for_file_access(),
-: 2472: errmsg("could not seek in log segment %s to offset %u: %m",
-: 2473: XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
-: 2474: startoff)));
9979: 2475: sendSeg->ws_off = startoff;
-: 2476: }
-: 2477:
-: 2478: /* How many bytes are within this segment? */
11777: 2479: if (nbytes > (segcxt->ws_segsize - startoff))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2480: segbytes = segcxt->ws_segsize - startoff;
-: 2481: else
11777: 2482: segbytes = nbytes;
-: 2483:
11777: 2484: pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
call 0 returned 100%
11777: 2485: readbytes = read(sendSeg->ws_file, p, segbytes);
call 0 returned 100%
11777: 2486: pgstat_report_wait_end();
call 0 returned 100%
11777: 2487: if (readbytes < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 2488: {
#####: 2489: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 2490: (errcode_for_file_access(),
-: 2491: errmsg("could not read from log segment %s, offset %u, length %zu: %m",
-: 2492: XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
-: 2493: sendSeg->ws_off, (Size) segbytes)));
-: 2494: }
11777: 2495: else if (readbytes == 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 2496: {
#####: 2497: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 2498: (errcode(ERRCODE_DATA_CORRUPTED),
-: 2499: errmsg("could not read from log segment %s, offset %u: read %d of %zu",
-: 2500: XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
-: 2501: sendSeg->ws_off, readbytes, (Size) segbytes)));
-: 2502: }
-: 2503:
-: 2504: /* Update state for read */
11777: 2505: recptr += readbytes;
-: 2506:
11777: 2507: sendSeg->ws_off += readbytes;
11777: 2508: nbytes -= readbytes;
11777: 2509: p += readbytes;
-: 2510: }
-: 2511:
-: 2512: /*
-: 2513: * After reading into the buffer, check that what we read was valid. We do
-: 2514: * this after reading, because even though the segment was present when we
-: 2515: * opened it, it might get recycled or removed while we read it. The
-: 2516: * read() succeeds in that case, but the data we tried to read might
-: 2517: * already have been overwritten with new WAL records.
-: 2518: */
11777: 2519: XLByteToSeg(startptr, segno, segcxt->ws_segsize);
11777: 2520: CheckXLogRemoved(segno, ThisTimeLineID);
call 0 returned 100%
-: 2521:
-: 2522: /*
-: 2523: * During recovery, the currently-open WAL file might be replaced with the
-: 2524: * file of the same name retrieved from archive. So we always need to
-: 2525: * check what we read was valid after reading into the buffer. If it's
-: 2526: * invalid, we try to open and read the file again.
-: 2527: */
11777: 2528: if (am_cascading_walsender)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 2529: {
137: 2530: WalSnd *walsnd = MyWalSnd;
-: 2531: bool reload;
-: 2532:
137: 2533: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
137: 2534: reload = walsnd->needreload;
137: 2535: walsnd->needreload = false;
137: 2536: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 2537:
137: 2538: if (reload && sendSeg->ws_file >= 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
branch 2 never executed
branch 3 never executed
-: 2539: {
#####: 2540: close(sendSeg->ws_file);
call 0 never executed
#####: 2541: sendSeg->ws_file = -1;
-: 2542:
#####: 2543: goto retry;
-: 2544: }
-: 2545: }
11777: 2546:}
-: 2547:
-: 2548:/*
-: 2549: * Send out the WAL in its normal physical/stored form.
-: 2550: *
-: 2551: * Read up to MAX_SEND_SIZE bytes of WAL that's been flushed to disk,
-: 2552: * but not yet sent to the client, and buffer it in the libpq output
-: 2553: * buffer.
-: 2554: *
-: 2555: * If there is no unsent WAL remaining, WalSndCaughtUp is set to true,
-: 2556: * otherwise WalSndCaughtUp is set to false.
-: 2557: */
-: 2558:static void
function XLogSendPhysical called 12461 returned 100% blocks executed 83%
12461: 2559:XLogSendPhysical(void)
-: 2560:{
-: 2561: XLogRecPtr SendRqstPtr;
-: 2562: XLogRecPtr startptr;
-: 2563: XLogRecPtr endptr;
-: 2564: Size nbytes;
-: 2565:
-: 2566: /* If requested switch the WAL sender to the stopping state. */
12461: 2567: if (got_STOPPING)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
83: 2568: WalSndSetState(WALSNDSTATE_STOPPING);
call 0 returned 100%
-: 2569:
12461: 2570: if (streamingDoneSending)
branch 0 taken 84% (fallthrough)
branch 1 taken 16%
-: 2571: {
10526: 2572: WalSndCaughtUp = true;
10526: 2573: return;
-: 2574: }
-: 2575:
-: 2576: /* Figure out how far we can safely send the WAL. */
1935: 2577: if (sendTimeLineIsHistoric)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 2578: {
-: 2579: /*
-: 2580: * Streaming an old timeline that's in this server's history, but is
-: 2581: * not the one we're currently inserting or replaying. It can be
-: 2582: * streamed up to the point where we switched off that timeline.
-: 2583: */
16: 2584: SendRqstPtr = sendTimeLineValidUpto;
-: 2585: }
1919: 2586: else if (am_cascading_walsender)
branch 0 taken 8% (fallthrough)
branch 1 taken 92%
-: 2587: {
-: 2588: /*
-: 2589: * Streaming the latest timeline on a standby.
-: 2590: *
-: 2591: * Attempt to send all WAL that has already been replayed, so that we
-: 2592: * know it's valid. If we're receiving WAL through streaming
-: 2593: * replication, it's also OK to send any WAL that has been received
-: 2594: * but not replayed.
-: 2595: *
-: 2596: * The timeline we're recovering from can change, or we can be
-: 2597: * promoted. In either case, the current timeline becomes historic. We
-: 2598: * need to detect that so that we don't try to stream past the point
-: 2599: * where we switched to another timeline. We check for promotion or
-: 2600: * timeline switch after calculating FlushPtr, to avoid a race
-: 2601: * condition: if the timeline becomes historic just after we checked
-: 2602: * that it was still current, it's still be OK to stream it up to the
-: 2603: * FlushPtr that was calculated before it became historic.
-: 2604: */
149: 2605: bool becameHistoric = false;
-: 2606:
149: 2607: SendRqstPtr = GetStandbyFlushRecPtr();
call 0 returned 100%
-: 2608:
149: 2609: if (!RecoveryInProgress())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 2610: {
-: 2611: /*
-: 2612: * We have been promoted. RecoveryInProgress() updated
-: 2613: * ThisTimeLineID to the new current timeline.
-: 2614: */
#####: 2615: am_cascading_walsender = false;
#####: 2616: becameHistoric = true;
-: 2617: }
-: 2618: else
-: 2619: {
-: 2620: /*
-: 2621: * Still a cascading standby. But is the timeline we're sending
-: 2622: * still the one recovery is recovering from? ThisTimeLineID was
-: 2623: * updated by the GetStandbyFlushRecPtr() call above.
-: 2624: */
149: 2625: if (sendTimeLine != ThisTimeLineID)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2626: becameHistoric = true;
-: 2627: }
-: 2628:
149: 2629: if (becameHistoric)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 2630: {
-: 2631: /*
-: 2632: * The timeline we were sending has become historic. Read the
-: 2633: * timeline history file of the new timeline to see where exactly
-: 2634: * we forked off from the timeline we were sending.
-: 2635: */
-: 2636: List *history;
-: 2637:
#####: 2638: history = readTimeLineHistory(ThisTimeLineID);
call 0 never executed
#####: 2639: sendTimeLineValidUpto = tliSwitchPoint(sendTimeLine, history, &sendTimeLineNextTLI);
call 0 never executed
-: 2640:
#####: 2641: Assert(sendTimeLine < sendTimeLineNextTLI);
branch 0 never executed
branch 1 never executed
call 2 never executed
#####: 2642: list_free_deep(history);
call 0 never executed
-: 2643:
#####: 2644: sendTimeLineIsHistoric = true;
-: 2645:
#####: 2646: SendRqstPtr = sendTimeLineValidUpto;
-: 2647: }
-: 2648: }
-: 2649: else
-: 2650: {
-: 2651: /*
-: 2652: * Streaming the current timeline on a master.
-: 2653: *
-: 2654: * Attempt to send all data that's already been written out and
-: 2655: * fsync'd to disk. We cannot go further than what's been written out
-: 2656: * given the current implementation of XLogRead(). And in any case
-: 2657: * it's unsafe to send WAL that is not securely down to disk on the
-: 2658: * master: if the master subsequently crashes and restarts, standbys
-: 2659: * must not have applied any WAL that got lost on the master.
-: 2660: */
1770: 2661: SendRqstPtr = GetFlushRecPtr();
call 0 returned 100%
-: 2662: }
-: 2663:
-: 2664: /*
-: 2665: * Record the current system time as an approximation of the time at which
-: 2666: * this WAL location was written for the purposes of lag tracking.
-: 2667: *
-: 2668: * In theory we could make XLogFlush() record a time in shmem whenever WAL
-: 2669: * is flushed and we could get that time as well as the LSN when we call
-: 2670: * GetFlushRecPtr() above (and likewise for the cascading standby
-: 2671: * equivalent), but rather than putting any new code into the hot WAL path
-: 2672: * it seems good enough to capture the time here. We should reach this
-: 2673: * after XLogFlush() runs WalSndWakeupProcessRequests(), and although that
-: 2674: * may take some time, we read the WAL flush pointer and take the time
-: 2675: * very close to together here so that we'll get a later position if it is
-: 2676: * still moving.
-: 2677: *
-: 2678: * Because LagTrackerWrite ignores samples when the LSN hasn't advanced,
-: 2679: * this gives us a cheap approximation for the WAL flush time for this
-: 2680: * LSN.
-: 2681: *
-: 2682: * Note that the LSN is not necessarily the LSN for the data contained in
-: 2683: * the present message; it's the end of the WAL, which might be further
-: 2684: * ahead. All the lag tracking machinery cares about is finding out when
-: 2685: * that arbitrary LSN is eventually reported as written, flushed and
-: 2686: * applied, so that it can measure the elapsed time.
-: 2687: */
1935: 2688: LagTrackerWrite(SendRqstPtr, GetCurrentTimestamp());
call 0 returned 100%
call 1 returned 100%
-: 2689:
-: 2690: /*
-: 2691: * If this is a historic timeline and we've reached the point where we
-: 2692: * forked to the next timeline, stop streaming.
-: 2693: *
-: 2694: * Note: We might already have sent WAL > sendTimeLineValidUpto. The
-: 2695: * startup process will normally replay all WAL that has been received
-: 2696: * from the master, before promoting, but if the WAL streaming is
-: 2697: * terminated at a WAL page boundary, the valid portion of the timeline
-: 2698: * might end in the middle of a WAL record. We might've already sent the
-: 2699: * first half of that partial WAL record to the cascading standby, so that
-: 2700: * sentPtr > sendTimeLineValidUpto. That's OK; the cascading standby can't
-: 2701: * replay the partial WAL record either, so it can still follow our
-: 2702: * timeline switch.
-: 2703: */
1935: 2704: if (sendTimeLineIsHistoric && sendTimeLineValidUpto <= sentPtr)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
branch 2 taken 31% (fallthrough)
branch 3 taken 69%
-: 2705: {
-: 2706: /* close the current file. */
5: 2707: if (sendSeg->ws_file >= 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
5: 2708: close(sendSeg->ws_file);
call 0 returned 100%
5: 2709: sendSeg->ws_file = -1;
-: 2710:
-: 2711: /* Send CopyDone */
5: 2712: pq_putmessage_noblock('c', NULL, 0);
call 0 returned 100%
5: 2713: streamingDoneSending = true;
-: 2714:
5: 2715: WalSndCaughtUp = true;
-: 2716:
5: 2717: elog(DEBUG1, "walsender reached end of timeline at %X/%X (sent up to %X/%X)",
call 0 returned 100%
call 1 returned 100%
-: 2718: (uint32) (sendTimeLineValidUpto >> 32), (uint32) sendTimeLineValidUpto,
-: 2719: (uint32) (sentPtr >> 32), (uint32) sentPtr);
5: 2720: return;
-: 2721: }
-: 2722:
-: 2723: /* Do we have any work to do? */
1930: 2724: Assert(sentPtr <= SendRqstPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1930: 2725: if (SendRqstPtr <= sentPtr)
branch 0 taken 18% (fallthrough)
branch 1 taken 82%
-: 2726: {
352: 2727: WalSndCaughtUp = true;
352: 2728: return;
-: 2729: }
-: 2730:
-: 2731: /*
-: 2732: * Figure out how much to send in one message. If there's no more than
-: 2733: * MAX_SEND_SIZE bytes to send, send everything. Otherwise send
-: 2734: * MAX_SEND_SIZE bytes, but round back to logfile or page boundary.
-: 2735: *
-: 2736: * The rounding is not only for performance reasons. Walreceiver relies on
-: 2737: * the fact that we never split a WAL record across two messages. Since a
-: 2738: * long WAL record is split at page boundary into continuation records,
-: 2739: * page boundary is always a safe cut-off point. We also assume that
-: 2740: * SendRqstPtr never points to the middle of a WAL record.
-: 2741: */
1578: 2742: startptr = sentPtr;
1578: 2743: endptr = startptr;
1578: 2744: endptr += MAX_SEND_SIZE;
-: 2745:
-: 2746: /* if we went beyond SendRqstPtr, back off */
1578: 2747: if (SendRqstPtr <= endptr)
branch 0 taken 12% (fallthrough)
branch 1 taken 88%
-: 2748: {
186: 2749: endptr = SendRqstPtr;
186: 2750: if (sendTimeLineIsHistoric)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
5: 2751: WalSndCaughtUp = false;
-: 2752: else
181: 2753: WalSndCaughtUp = true;
-: 2754: }
-: 2755: else
-: 2756: {
-: 2757: /* round down to page boundary. */
1392: 2758: endptr -= (endptr % XLOG_BLCKSZ);
1392: 2759: WalSndCaughtUp = false;
-: 2760: }
-: 2761:
1578: 2762: nbytes = endptr - startptr;
1578: 2763: Assert(nbytes <= MAX_SEND_SIZE);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2764:
-: 2765: /*
-: 2766: * OK to read and send the slice.
-: 2767: */
1578: 2768: resetStringInfo(&output_message);
call 0 returned 100%
1578: 2769: pq_sendbyte(&output_message, 'w');
call 0 returned 100%
-: 2770:
1578: 2771: pq_sendint64(&output_message, startptr); /* dataStart */
call 0 returned 100%
1578: 2772: pq_sendint64(&output_message, SendRqstPtr); /* walEnd */
call 0 returned 100%
1578: 2773: pq_sendint64(&output_message, 0); /* sendtime, filled in last */
call 0 returned 100%
-: 2774:
-: 2775: /*
-: 2776: * Read the log directly into the output buffer to avoid extra memcpy
-: 2777: * calls.
-: 2778: */
1578: 2779: enlargeStringInfo(&output_message, nbytes);
call 0 returned 100%
1578: 2780: XLogRead(sendCxt, &output_message.data[output_message.len], startptr, nbytes);
call 0 returned 100%
1578: 2781: output_message.len += nbytes;
1578: 2782: output_message.data[output_message.len] = '\0';
-: 2783:
-: 2784: /*
-: 2785: * Fill the send timestamp last, so that it is taken as late as possible.
-: 2786: */
1578: 2787: resetStringInfo(&tmpbuf);
call 0 returned 100%
1578: 2788: pq_sendint64(&tmpbuf, GetCurrentTimestamp());
call 0 returned 100%
call 1 returned 100%
1578: 2789: memcpy(&output_message.data[1 + sizeof(int64) + sizeof(int64)],
1578: 2790: tmpbuf.data, sizeof(int64));
-: 2791:
1578: 2792: pq_putmessage_noblock('d', output_message.data, output_message.len);
call 0 returned 100%
-: 2793:
1578: 2794: sentPtr = endptr;
-: 2795:
-: 2796: /* Update shared memory status */
-: 2797: {
1578: 2798: WalSnd *walsnd = MyWalSnd;
-: 2799:
1578: 2800: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
1578: 2801: walsnd->sentPtr = sentPtr;
1578: 2802: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 2803: }
-: 2804:
-: 2805: /* Report progress of XLOG streaming in PS display */
1578: 2806: if (update_process_title)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 2807: {
-: 2808: char activitymsg[50];
-: 2809:
3156: 2810: snprintf(activitymsg, sizeof(activitymsg), "streaming %X/%X",
call 0 returned 100%
1578: 2811: (uint32) (sentPtr >> 32), (uint32) sentPtr);
1578: 2812: set_ps_display(activitymsg, false);
call 0 returned 100%
-: 2813: }
-: 2814:
1578: 2815: return;
-: 2816:}
-: 2817:
-: 2818:/*
-: 2819: * Stream out logically decoded data.
-: 2820: */
-: 2821:static void
function XLogSendLogical called 19271 returned 99% blocks executed 80%
19271: 2822:XLogSendLogical(void)
-: 2823:{
-: 2824: XLogRecord *record;
-: 2825: char *errm;
-: 2826: XLogRecPtr flushPtr;
-: 2827:
-: 2828: /*
-: 2829: * Don't know whether we've caught up yet. We'll set WalSndCaughtUp to
-: 2830: * true in WalSndWaitForWal, if we're actually waiting. We also set to
-: 2831: * true if XLogReadRecord() had to stop reading but WalSndWaitForWal
-: 2832: * didn't wait - i.e. when we're shutting down.
-: 2833: */
19271: 2834: WalSndCaughtUp = false;
-: 2835:
19271: 2836: record = XLogReadRecord(logical_decoding_ctx->reader, logical_startptr, &errm);
call 0 returned 99%
19255: 2837: logical_startptr = InvalidXLogRecPtr;
-: 2838:
-: 2839: /* xlog record was invalid */
19255: 2840: if (errm != NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2841: elog(ERROR, "%s", errm);
call 0 never executed
call 1 never executed
call 2 never executed
-: 2842:
-: 2843: /*
-: 2844: * We'll use the current flush point to determine whether we've caught up.
-: 2845: */
19255: 2846: flushPtr = GetFlushRecPtr();
call 0 returned 100%
-: 2847:
19255: 2848: if (record != NULL)
branch 0 taken 49% (fallthrough)
branch 1 taken 51%
-: 2849: {
-: 2850: /*
-: 2851: * Note the lack of any call to LagTrackerWrite() which is handled by
-: 2852: * WalSndUpdateProgress which is called by output plugin through
-: 2853: * logical decoding write api.
-: 2854: */
9506: 2855: LogicalDecodingProcessRecord(logical_decoding_ctx, logical_decoding_ctx->reader);
call 0 returned 100%
-: 2856:
9506: 2857: sentPtr = logical_decoding_ctx->reader->EndRecPtr;
-: 2858: }
-: 2859:
-: 2860: /* Set flag if we're caught up. */
19255: 2861: if (logical_decoding_ctx->reader->EndRecPtr >= flushPtr)
branch 0 taken 52% (fallthrough)
branch 1 taken 48%
9923: 2862: WalSndCaughtUp = true;
-: 2863:
-: 2864: /*
-: 2865: * If we're caught up and have been requested to stop, have WalSndLoop()
-: 2866: * terminate the connection in an orderly manner, after writing out all
-: 2867: * the pending data.
-: 2868: */
19255: 2869: if (WalSndCaughtUp && got_STOPPING)
branch 0 taken 52% (fallthrough)
branch 1 taken 48%
branch 2 taken 97% (fallthrough)
branch 3 taken 3%
9746: 2870: got_SIGUSR2 = true;
-: 2871:
-: 2872: /* Update shared memory status */
-: 2873: {
19255: 2874: WalSnd *walsnd = MyWalSnd;
-: 2875:
19255: 2876: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
19255: 2877: walsnd->sentPtr = sentPtr;
19255: 2878: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 2879: }
19255: 2880:}
-: 2881:
-: 2882:/*
-: 2883: * Shutdown if the sender is caught up.
-: 2884: *
-: 2885: * NB: This should only be called when the shutdown signal has been received
-: 2886: * from postmaster.
-: 2887: *
-: 2888: * Note that if we determine that there's still more data to send, this
-: 2889: * function will return control to the caller.
-: 2890: */
-: 2891:static void
function WalSndDone called 4887 returned 99% blocks executed 93%
4887: 2892:WalSndDone(WalSndSendDataCallback send_data)
-: 2893:{
-: 2894: XLogRecPtr replicatedPtr;
-: 2895:
-: 2896: /* ... let's just be real sure we're caught up ... */
4887: 2897: send_data();
call 0 returned 100%
-: 2898:
-: 2899: /*
-: 2900: * To figure out whether all WAL has successfully been replicated, check
-: 2901: * flush location if valid, write otherwise. Tools like pg_receivewal will
-: 2902: * usually (unless in synchronous mode) return an invalid flush location.
-: 2903: */
9774: 2904: replicatedPtr = XLogRecPtrIsInvalid(MyWalSnd->flush) ?
4887: 2905: MyWalSnd->write : MyWalSnd->flush;
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 2906:
4903: 2907: if (WalSndCaughtUp && sentPtr == replicatedPtr &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 1% (fallthrough)
branch 3 taken 99%
branch 4 taken 100% (fallthrough)
branch 5 taken 0%
16: 2908: !pq_is_send_pending())
call 0 returned 100%
-: 2909: {
-: 2910: /* Inform the standby that XLOG streaming is done */
16: 2911: EndCommand("COPY 0", DestRemote);
call 0 returned 100%
16: 2912: pq_flush();
call 0 returned 100%
-: 2913:
16: 2914: proc_exit(0);
call 0 returned 0%
-: 2915: }
4871: 2916: if (!waiting_for_ping_response)
branch 0 taken 16% (fallthrough)
branch 1 taken 84%
-: 2917: {
758: 2918: WalSndKeepalive(true);
call 0 returned 100%
758: 2919: waiting_for_ping_response = true;
-: 2920: }
4871: 2921:}
-: 2922:
-: 2923:/*
-: 2924: * Returns the latest point in WAL that has been safely flushed to disk, and
-: 2925: * can be sent to the standby. This should only be called when in recovery,
-: 2926: * ie. we're streaming to a cascaded standby.
-: 2927: *
-: 2928: * As a side-effect, ThisTimeLineID is updated to the TLI of the last
-: 2929: * replayed WAL record.
-: 2930: */
-: 2931:static XLogRecPtr
function GetStandbyFlushRecPtr called 159 returned 100% blocks executed 100%
159: 2932:GetStandbyFlushRecPtr(void)
-: 2933:{
-: 2934: XLogRecPtr replayPtr;
-: 2935: TimeLineID replayTLI;
-: 2936: XLogRecPtr receivePtr;
-: 2937: TimeLineID receiveTLI;
-: 2938: XLogRecPtr result;
-: 2939:
-: 2940: /*
-: 2941: * We can safely send what's already been replayed. Also, if walreceiver
-: 2942: * is streaming WAL from the same timeline, we can send anything that it
-: 2943: * has streamed, but hasn't been replayed yet.
-: 2944: */
-: 2945:
159: 2946: receivePtr = GetWalRcvWriteRecPtr(NULL, &receiveTLI);
call 0 returned 100%
159: 2947: replayPtr = GetXLogReplayRecPtr(&replayTLI);
call 0 returned 100%
-: 2948:
159: 2949: ThisTimeLineID = replayTLI;
-: 2950:
159: 2951: result = replayPtr;
159: 2952: if (receiveTLI == ThisTimeLineID && receivePtr > replayPtr)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 3% (fallthrough)
branch 3 taken 97%
4: 2953: result = receivePtr;
-: 2954:
159: 2955: return result;
-: 2956:}
-: 2957:
-: 2958:/*
-: 2959: * Request walsenders to reload the currently-open WAL file
-: 2960: */
-: 2961:void
function WalSndRqstFileReload called 5 returned 100% blocks executed 77%
5: 2962:WalSndRqstFileReload(void)
-: 2963:{
-: 2964: int i;
-: 2965:
27: 2966: for (i = 0; i < max_wal_senders; i++)
branch 0 taken 81%
branch 1 taken 19% (fallthrough)
-: 2967: {
22: 2968: WalSnd *walsnd = &WalSndCtl->walsnds[i];
-: 2969:
22: 2970: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
22: 2971: if (walsnd->pid == 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 2972: {
22: 2973: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
22: 2974: continue;
-: 2975: }
#####: 2976: walsnd->needreload = true;
#####: 2977: SpinLockRelease(&walsnd->mutex);
call 0 never executed
-: 2978: }
5: 2979:}
-: 2980:
-: 2981:/*
-: 2982: * Handle PROCSIG_WALSND_INIT_STOPPING signal.
-: 2983: */
-: 2984:void
function HandleWalSndInitStopping called 16 returned 100% blocks executed 67%
16: 2985:HandleWalSndInitStopping(void)
-: 2986:{
16: 2987: Assert(am_walsender);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2988:
-: 2989: /*
-: 2990: * If replication has not yet started, die like with SIGTERM. If
-: 2991: * replication is active, only set a flag and wake up the main loop. It
-: 2992: * will send any outstanding WAL, wait for it to be replicated to the
-: 2993: * standby, and then exit gracefully.
-: 2994: */
16: 2995: if (!replication_active)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2996: kill(MyProcPid, SIGTERM);
call 0 never executed
-: 2997: else
16: 2998: got_STOPPING = true;
16: 2999:}
-: 3000:
-: 3001:/*
-: 3002: * SIGUSR2: set flag to do a last cycle and shut down afterwards. The WAL
-: 3003: * sender should already have been switched to WALSNDSTATE_STOPPING at
-: 3004: * this point.
-: 3005: */
-: 3006:static void
function WalSndLastCycleHandler called 14 returned 100% blocks executed 100%
14: 3007:WalSndLastCycleHandler(SIGNAL_ARGS)
-: 3008:{
14: 3009: int save_errno = errno;
call 0 returned 100%
-: 3010:
14: 3011: got_SIGUSR2 = true;
14: 3012: SetLatch(MyLatch);
call 0 returned 100%
-: 3013:
14: 3014: errno = save_errno;
call 0 returned 100%
14: 3015:}
-: 3016:
-: 3017:/* Set up signal handlers */
-: 3018:void
function WalSndSignals called 231 returned 100% blocks executed 100%
231: 3019:WalSndSignals(void)
-: 3020:{
-: 3021: /* Set up signal handlers */
231: 3022: pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config
call 0 returned 100%
-: 3023: * file */
231: 3024: pqsignal(SIGINT, StatementCancelHandler); /* query cancel */
call 0 returned 100%
231: 3025: pqsignal(SIGTERM, die); /* request shutdown */
call 0 returned 100%
231: 3026: pqsignal(SIGQUIT, quickdie); /* hard crash time */
call 0 returned 100%
231: 3027: InitializeTimeouts(); /* establishes SIGALRM handler */
call 0 returned 100%
231: 3028: pqsignal(SIGPIPE, SIG_IGN);
call 0 returned 100%
231: 3029: pqsignal(SIGUSR1, procsignal_sigusr1_handler);
call 0 returned 100%
231: 3030: pqsignal(SIGUSR2, WalSndLastCycleHandler); /* request a last cycle and
call 0 returned 100%
-: 3031: * shutdown */
-: 3032:
-: 3033: /* Reset some signals that are accepted by postmaster but not here */
231: 3034: pqsignal(SIGCHLD, SIG_DFL);
call 0 returned 100%
231: 3035:}
-: 3036:
-: 3037:/* Report shared-memory space needed by WalSndShmemInit */
-: 3038:Size
function WalSndShmemSize called 18626 returned 100% blocks executed 100%
18626: 3039:WalSndShmemSize(void)
-: 3040:{
18626: 3041: Size size = 0;
-: 3042:
18626: 3043: size = offsetof(WalSndCtlData, walsnds);
18626: 3044: size = add_size(size, mul_size(max_wal_senders, sizeof(WalSnd)));
call 0 returned 100%
call 1 returned 100%
-: 3045:
18626: 3046: return size;
-: 3047:}
-: 3048:
-: 3049:/* Allocate and initialize walsender-related shared memory */
-: 3050:void
function WalSndShmemInit called 6208 returned 100% blocks executed 100%
6208: 3051:WalSndShmemInit(void)
-: 3052:{
-: 3053: bool found;
-: 3054: int i;
-: 3055:
6208: 3056: WalSndCtl = (WalSndCtlData *)
6208: 3057: ShmemInitStruct("Wal Sender Ctl", WalSndShmemSize(), &found);
call 0 returned 100%
call 1 returned 100%
-: 3058:
6208: 3059: if (!found)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 3060: {
-: 3061: /* First time through, so initialize */
6208: 3062: MemSet(WalSndCtl, 0, WalSndShmemSize());
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
branch 3 taken 100% (fallthrough)
branch 4 taken 0%
branch 5 taken 100% (fallthrough)
branch 6 taken 0%
branch 7 taken 43% (fallthrough)
branch 8 taken 57%
branch 9 taken 99%
branch 10 taken 1% (fallthrough)
-: 3063:
24832: 3064: for (i = 0; i < NUM_SYNC_REP_WAIT_MODE; i++)
branch 0 taken 75%
branch 1 taken 25% (fallthrough)
18624: 3065: SHMQueueInit(&(WalSndCtl->SyncRepQueue[i]));
call 0 returned 100%
-: 3066:
50703: 3067: for (i = 0; i < max_wal_senders; i++)
branch 0 taken 88%
branch 1 taken 12% (fallthrough)
-: 3068: {
44495: 3069: WalSnd *walsnd = &WalSndCtl->walsnds[i];
-: 3070:
44495: 3071: SpinLockInit(&walsnd->mutex);
call 0 returned 100%
-: 3072: }
-: 3073: }
6208: 3074:}
-: 3075:
-: 3076:/*
-: 3077: * Wake up all walsenders
-: 3078: *
-: 3079: * This will be called inside critical sections, so throwing an error is not
-: 3080: * advisable.
-: 3081: */
-: 3082:void
function WalSndWakeup called 112319 returned 100% blocks executed 91%
112319: 3083:WalSndWakeup(void)
-: 3084:{
-: 3085: int i;
-: 3086:
1228088: 3087: for (i = 0; i < max_wal_senders; i++)
branch 0 taken 91%
branch 1 taken 9% (fallthrough)
-: 3088: {
-: 3089: Latch *latch;
1115769: 3090: WalSnd *walsnd = &WalSndCtl->walsnds[i];
-: 3091:
-: 3092: /*
-: 3093: * Get latch pointer with spinlock held, for the unlikely case that
-: 3094: * pointer reads aren't atomic (as they're 8 bytes).
-: 3095: */
1115769: 3096: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
1115769: 3097: latch = walsnd->latch;
1115769: 3098: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 3099:
1115769: 3100: if (latch != NULL)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
643: 3101: SetLatch(latch);
call 0 returned 100%
-: 3102: }
112319: 3103:}
-: 3104:
-: 3105:/*
-: 3106: * Signal all walsenders to move to stopping state.
-: 3107: *
-: 3108: * This will trigger walsenders to move to a state where no further WAL can be
-: 3109: * generated. See this file's header for details.
-: 3110: */
-: 3111:void
function WalSndInitStopping called 465 returned 100% blocks executed 92%
465: 3112:WalSndInitStopping(void)
-: 3113:{
-: 3114: int i;
-: 3115:
4590: 3116: for (i = 0; i < max_wal_senders; i++)
branch 0 taken 90%
branch 1 taken 10% (fallthrough)
-: 3117: {
4125: 3118: WalSnd *walsnd = &WalSndCtl->walsnds[i];
-: 3119: pid_t pid;
-: 3120:
4125: 3121: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
4125: 3122: pid = walsnd->pid;
4125: 3123: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 3124:
4125: 3125: if (pid == 0)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
4109: 3126: continue;
-: 3127:
16: 3128: SendProcSignal(pid, PROCSIG_WALSND_INIT_STOPPING, InvalidBackendId);
call 0 returned 100%
-: 3129: }
465: 3130:}
-: 3131:
-: 3132:/*
-: 3133: * Wait that all the WAL senders have quit or reached the stopping state. This
-: 3134: * is used by the checkpointer to control when the shutdown checkpoint can
-: 3135: * safely be performed.
-: 3136: */
-: 3137:void
function WalSndWaitStopping called 465 returned 100% blocks executed 95%
489: 3138:WalSndWaitStopping(void)
-: 3139:{
-: 3140: for (;;)
-: 3141: {
-: 3142: int i;
489: 3143: bool all_stopped = true;
-: 3144:
4614: 3145: for (i = 0; i < max_wal_senders; i++)
branch 0 taken 90%
branch 1 taken 10% (fallthrough)
-: 3146: {
4149: 3147: WalSnd *walsnd = &WalSndCtl->walsnds[i];
-: 3148:
4149: 3149: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 3150:
4149: 3151: if (walsnd->pid == 0)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
-: 3152: {
4111: 3153: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
4111: 3154: continue;
-: 3155: }
-: 3156:
38: 3157: if (walsnd->state != WALSNDSTATE_STOPPING)
branch 0 taken 63% (fallthrough)
branch 1 taken 37%
-: 3158: {
24: 3159: all_stopped = false;
24: 3160: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
24: 3161: break;
-: 3162: }
14: 3163: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 3164: }
-: 3165:
-: 3166: /* safe to leave if confirmation is done for all WAL senders */
489: 3167: if (all_stopped)
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
930: 3168: return;
-: 3169:
24: 3170: pg_usleep(10000L); /* wait for 10 msec */
call 0 returned 100%
24: 3171: }
-: 3172:}
-: 3173:
-: 3174:/* Set state for current walsender (only called in walsender) */
-: 3175:void
function WalSndSetState called 417 returned 100% blocks executed 82%
417: 3176:WalSndSetState(WalSndState state)
-: 3177:{
417: 3178: WalSnd *walsnd = MyWalSnd;
-: 3179:
417: 3180: Assert(am_walsender);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3181:
417: 3182: if (walsnd->state == state)
branch 0 taken 17% (fallthrough)
branch 1 taken 83%
488: 3183: return;
-: 3184:
346: 3185: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
346: 3186: walsnd->state = state;
346: 3187: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 3188:}
-: 3189:
-: 3190:/*
-: 3191: * Return a string constant representing the state. This is used
-: 3192: * in system views, and should *not* be translated.
-: 3193: */
-: 3194:static const char *
function WalSndGetStateString called 150 returned 100% blocks executed 50%
150: 3195:WalSndGetStateString(WalSndState state)
-: 3196:{
150: 3197: switch (state)
branch 0 taken 4%
branch 1 taken 0%
branch 2 taken 0%
branch 3 taken 96%
branch 4 taken 0%
branch 5 taken 0%
-: 3198: {
-: 3199: case WALSNDSTATE_STARTUP:
6: 3200: return "startup";
-: 3201: case WALSNDSTATE_BACKUP:
#####: 3202: return "backup";
-: 3203: case WALSNDSTATE_CATCHUP:
#####: 3204: return "catchup";
-: 3205: case WALSNDSTATE_STREAMING:
144: 3206: return "streaming";
-: 3207: case WALSNDSTATE_STOPPING:
#####: 3208: return "stopping";
-: 3209: }
#####: 3210: return "UNKNOWN";
-: 3211:}
-: 3212:
-: 3213:static Interval *
function offset_to_interval called 243 returned 100% blocks executed 100%
243: 3214:offset_to_interval(TimeOffset offset)
-: 3215:{
243: 3216: Interval *result = palloc(sizeof(Interval));
call 0 returned 100%
-: 3217:
243: 3218: result->month = 0;
243: 3219: result->day = 0;
243: 3220: result->time = offset;
-: 3221:
243: 3222: return result;
-: 3223:}
-: 3224:
-: 3225:/*
-: 3226: * Returns activity of walsenders, including pids and xlog locations sent to
-: 3227: * standby servers.
-: 3228: */
-: 3229:Datum
function pg_stat_get_wal_senders called 107 returned 100% blocks executed 71%
107: 3230:pg_stat_get_wal_senders(PG_FUNCTION_ARGS)
-: 3231:{
-: 3232:#define PG_STAT_GET_WAL_SENDERS_COLS 15
107: 3233: ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-: 3234: TupleDesc tupdesc;
-: 3235: Tuplestorestate *tupstore;
-: 3236: MemoryContext per_query_ctx;
-: 3237: MemoryContext oldcontext;
-: 3238: List *sync_standbys;
-: 3239: int i;
-: 3240:
-: 3241: /* check to see if caller supports us returning a tuplestore */
107: 3242: if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 3243: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 3244: (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-: 3245: errmsg("set-valued function called in context that cannot accept a set")));
107: 3246: if (!(rsinfo->allowedModes & SFRM_Materialize))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3247: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 3248: (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-: 3249: errmsg("materialize mode required, but it is not " \
-: 3250: "allowed in this context")));
-: 3251:
-: 3252: /* Build a tuple descriptor for our result type */
107: 3253: if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 3254: elog(ERROR, "return type must be a row type");
call 0 never executed
call 1 never executed
call 2 never executed
-: 3255:
107: 3256: per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
107: 3257: oldcontext = MemoryContextSwitchTo(per_query_ctx);
call 0 returned 100%
-: 3258:
107: 3259: tupstore = tuplestore_begin_heap(true, false, work_mem);
call 0 returned 100%
107: 3260: rsinfo->returnMode = SFRM_Materialize;
107: 3261: rsinfo->setResult = tupstore;
107: 3262: rsinfo->setDesc = tupdesc;
-: 3263:
107: 3264: MemoryContextSwitchTo(oldcontext);
call 0 returned 100%
-: 3265:
-: 3266: /*
-: 3267: * Get the currently active synchronous standbys.
-: 3268: */
107: 3269: LWLockAcquire(SyncRepLock, LW_SHARED);
call 0 returned 100%
107: 3270: sync_standbys = SyncRepGetSyncStandbys(NULL);
call 0 returned 100%
107: 3271: LWLockRelease(SyncRepLock);
call 0 returned 100%
-: 3272:
639: 3273: for (i = 0; i < max_wal_senders; i++)
branch 0 taken 83%
branch 1 taken 17% (fallthrough)
-: 3274: {
532: 3275: WalSnd *walsnd = &WalSndCtl->walsnds[i];
-: 3276: XLogRecPtr sentPtr;
-: 3277: XLogRecPtr write;
-: 3278: XLogRecPtr flush;
-: 3279: XLogRecPtr apply;
-: 3280: TimeOffset writeLag;
-: 3281: TimeOffset flushLag;
-: 3282: TimeOffset applyLag;
-: 3283: int priority;
-: 3284: int pid;
-: 3285: WalSndState state;
-: 3286: TimestampTz replyTime;
-: 3287: int64 spillTxns;
-: 3288: int64 spillCount;
-: 3289: int64 spillBytes;
-: 3290: Datum values[PG_STAT_GET_WAL_SENDERS_COLS];
-: 3291: bool nulls[PG_STAT_GET_WAL_SENDERS_COLS];
-: 3292:
532: 3293: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
532: 3294: if (walsnd->pid == 0)
branch 0 taken 72% (fallthrough)
branch 1 taken 28%
-: 3295: {
382: 3296: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
382: 3297: continue;
-: 3298: }
150: 3299: pid = walsnd->pid;
150: 3300: sentPtr = walsnd->sentPtr;
150: 3301: state = walsnd->state;
150: 3302: write = walsnd->write;
150: 3303: flush = walsnd->flush;
150: 3304: apply = walsnd->apply;
150: 3305: writeLag = walsnd->writeLag;
150: 3306: flushLag = walsnd->flushLag;
150: 3307: applyLag = walsnd->applyLag;
150: 3308: priority = walsnd->sync_standby_priority;
150: 3309: replyTime = walsnd->replyTime;
150: 3310: spillTxns = walsnd->spillTxns;
150: 3311: spillCount = walsnd->spillCount;
150: 3312: spillBytes = walsnd->spillBytes;
150: 3313: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 3314:
150: 3315: memset(nulls, 0, sizeof(nulls));
150: 3316: values[0] = Int32GetDatum(pid);
-: 3317:
150: 3318: if (!is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_STATS))
call 0 returned 100%
call 1 returned 100%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
-: 3319: {
-: 3320: /*
-: 3321: * Only superusers and members of pg_read_all_stats can see
-: 3322: * details. Other users only get the pid value to know it's a
-: 3323: * walsender, but no details.
-: 3324: */
#####: 3325: MemSet(&nulls[1], true, PG_STAT_GET_WAL_SENDERS_COLS - 1);
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
branch 4 never executed
branch 5 never executed
branch 6 never executed
branch 7 never executed
branch 8 never executed
branch 9 never executed
-: 3326: }
-: 3327: else
-: 3328: {
150: 3329: values[1] = CStringGetTextDatum(WalSndGetStateString(state));
call 0 returned 100%
call 1 returned 100%
-: 3330:
150: 3331: if (XLogRecPtrIsInvalid(sentPtr))
branch 0 taken 4% (fallthrough)
branch 1 taken 96%
6: 3332: nulls[2] = true;
150: 3333: values[2] = LSNGetDatum(sentPtr);
-: 3334:
150: 3335: if (XLogRecPtrIsInvalid(write))
branch 0 taken 10% (fallthrough)
branch 1 taken 90%
15: 3336: nulls[3] = true;
150: 3337: values[3] = LSNGetDatum(write);
-: 3338:
150: 3339: if (XLogRecPtrIsInvalid(flush))
branch 0 taken 10% (fallthrough)
branch 1 taken 90%
15: 3340: nulls[4] = true;
150: 3341: values[4] = LSNGetDatum(flush);
-: 3342:
150: 3343: if (XLogRecPtrIsInvalid(apply))
branch 0 taken 10% (fallthrough)
branch 1 taken 90%
15: 3344: nulls[5] = true;
150: 3345: values[5] = LSNGetDatum(apply);
-: 3346:
-: 3347: /*
-: 3348: * Treat a standby such as a pg_basebackup background process
-: 3349: * which always returns an invalid flush location, as an
-: 3350: * asynchronous standby.
-: 3351: */
150: 3352: priority = XLogRecPtrIsInvalid(flush) ? 0 : priority;
branch 0 taken 90% (fallthrough)
branch 1 taken 10%
-: 3353:
150: 3354: if (writeLag < 0)
branch 0 taken 47% (fallthrough)
branch 1 taken 53%
70: 3355: nulls[6] = true;
-: 3356: else
80: 3357: values[6] = IntervalPGetDatum(offset_to_interval(writeLag));
call 0 returned 100%
-: 3358:
150: 3359: if (flushLag < 0)
branch 0 taken 45% (fallthrough)
branch 1 taken 55%
67: 3360: nulls[7] = true;
-: 3361: else
83: 3362: values[7] = IntervalPGetDatum(offset_to_interval(flushLag));
call 0 returned 100%
-: 3363:
150: 3364: if (applyLag < 0)
branch 0 taken 47% (fallthrough)
branch 1 taken 53%
70: 3365: nulls[8] = true;
-: 3366: else
80: 3367: values[8] = IntervalPGetDatum(offset_to_interval(applyLag));
call 0 returned 100%
-: 3368:
150: 3369: values[9] = Int32GetDatum(priority);
-: 3370:
-: 3371: /*
-: 3372: * More easily understood version of standby state. This is purely
-: 3373: * informational.
-: 3374: *
-: 3375: * In quorum-based sync replication, the role of each standby
-: 3376: * listed in synchronous_standby_names can be changing very
-: 3377: * frequently. Any standbys considered as "sync" at one moment can
-: 3378: * be switched to "potential" ones at the next moment. So, it's
-: 3379: * basically useless to report "sync" or "potential" as their sync
-: 3380: * states. We report just "quorum" for them.
-: 3381: */
150: 3382: if (priority == 0)
branch 0 taken 75% (fallthrough)
branch 1 taken 25%
113: 3383: values[10] = CStringGetTextDatum("async");
call 0 returned 100%
37: 3384: else if (list_member_int(sync_standbys, i))
call 0 returned 100%
branch 1 taken 76% (fallthrough)
branch 2 taken 24%
56: 3385: values[10] = SyncRepConfig->syncrep_method == SYNC_REP_PRIORITY ?
28: 3386: CStringGetTextDatum("sync") : CStringGetTextDatum("quorum");
branch 0 taken 82% (fallthrough)
branch 1 taken 18%
call 2 returned 100%
call 3 returned 100%
-: 3387: else
9: 3388: values[10] = CStringGetTextDatum("potential");
call 0 returned 100%
-: 3389:
150: 3390: if (replyTime == 0)
branch 0 taken 10% (fallthrough)
branch 1 taken 90%
15: 3391: nulls[11] = true;
-: 3392: else
135: 3393: values[11] = TimestampTzGetDatum(replyTime);
-: 3394:
-: 3395: /* spill to disk */
150: 3396: values[12] = Int64GetDatum(spillTxns);
150: 3397: values[13] = Int64GetDatum(spillCount);
150: 3398: values[14] = Int64GetDatum(spillBytes);
-: 3399: }
-: 3400:
150: 3401: tuplestore_putvalues(tupstore, tupdesc, values, nulls);
call 0 returned 100%
-: 3402: }
-: 3403:
-: 3404: /* clean up and return the tuplestore */
-: 3405: tuplestore_donestoring(tupstore);
-: 3406:
107: 3407: return (Datum) 0;
-: 3408:}
-: 3409:
-: 3410:/*
-: 3411: * This function is used to send a keepalive message to standby.
-: 3412: * If requestReply is set, sets a flag in the message requesting the standby
-: 3413: * to send a message back to us, for heartbeat purposes.
-: 3414: */
-: 3415:static void
function WalSndKeepalive called 840 returned 100% blocks executed 100%
840: 3416:WalSndKeepalive(bool requestReply)
-: 3417:{
840: 3418: elog(DEBUG2, "sending replication keepalive");
call 0 returned 100%
call 1 returned 100%
-: 3419:
-: 3420: /* construct the message... */
840: 3421: resetStringInfo(&output_message);
call 0 returned 100%
840: 3422: pq_sendbyte(&output_message, 'k');
call 0 returned 100%
840: 3423: pq_sendint64(&output_message, sentPtr);
call 0 returned 100%
840: 3424: pq_sendint64(&output_message, GetCurrentTimestamp());
call 0 returned 100%
call 1 returned 100%
840: 3425: pq_sendbyte(&output_message, requestReply ? 1 : 0);
call 0 returned 100%
-: 3426:
-: 3427: /* ... and send it wrapped in CopyData */
840: 3428: pq_putmessage_noblock('d', output_message.data, output_message.len);
call 0 returned 100%
840: 3429:}
-: 3430:
-: 3431:/*
-: 3432: * Send keepalive message if too much time has elapsed.
-: 3433: */
-: 3434:static void
function WalSndKeepaliveIfNecessary called 28299 returned 100% blocks executed 55%
28299: 3435:WalSndKeepaliveIfNecessary(void)
-: 3436:{
-: 3437: TimestampTz ping_time;
-: 3438:
-: 3439: /*
-: 3440: * Don't send keepalive messages if timeouts are globally disabled or
-: 3441: * we're doing something not partaking in timeouts.
-: 3442: */
28299: 3443: if (wal_sender_timeout <= 0 || last_reply_timestamp <= 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 3444: return;
-: 3445:
28299: 3446: if (waiting_for_ping_response)
branch 0 taken 37% (fallthrough)
branch 1 taken 63%
10469: 3447: return;
-: 3448:
-: 3449: /*
-: 3450: * If half of wal_sender_timeout has lapsed without receiving any reply
-: 3451: * from the standby, send a keep-alive message to the standby requesting
-: 3452: * an immediate reply.
-: 3453: */
17830: 3454: ping_time = TimestampTzPlusMilliseconds(last_reply_timestamp,
-: 3455: wal_sender_timeout / 2);
17830: 3456: if (last_processing >= ping_time)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 3457: {
#####: 3458: WalSndKeepalive(true);
call 0 never executed
#####: 3459: waiting_for_ping_response = true;
-: 3460:
-: 3461: /* Try to flush pending output to the client */
#####: 3462: if (pq_flush_if_writable() != 0)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 3463: WalSndShutdown();
call 0 never executed
-: 3464: }
-: 3465:}
-: 3466:
-: 3467:/*
-: 3468: * Record the end of the WAL and the time it was flushed locally, so that
-: 3469: * LagTrackerRead can compute the elapsed time (lag) when this WAL location is
-: 3470: * eventually reported to have been written, flushed and applied by the
-: 3471: * standby in a reply message.
-: 3472: */
-: 3473:static void
function LagTrackerWrite called 1950 returned 100% blocks executed 67%
1950: 3474:LagTrackerWrite(XLogRecPtr lsn, TimestampTz local_flush_time)
-: 3475:{
-: 3476: bool buffer_full;
-: 3477: int new_write_head;
-: 3478: int i;
-: 3479:
1950: 3480: if (!am_walsender)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3481: return;
-: 3482:
-: 3483: /*
-: 3484: * If the lsn hasn't advanced since last time, then do nothing. This way
-: 3485: * we only record a new sample when new WAL has been written.
-: 3486: */
1950: 3487: if (lag_tracker->last_lsn == lsn)
branch 0 taken 87% (fallthrough)
branch 1 taken 13%
1692: 3488: return;
258: 3489: lag_tracker->last_lsn = lsn;
-: 3490:
-: 3491: /*
-: 3492: * If advancing the write head of the circular buffer would crash into any
-: 3493: * of the read heads, then the buffer is full. In other words, the
-: 3494: * slowest reader (presumably apply) is the one that controls the release
-: 3495: * of space.
-: 3496: */
258: 3497: new_write_head = (lag_tracker->write_head + 1) % LAG_TRACKER_BUFFER_SIZE;
258: 3498: buffer_full = false;
1032: 3499: for (i = 0; i < NUM_SYNC_REP_WAIT_MODE; ++i)
branch 0 taken 75%
branch 1 taken 25% (fallthrough)
-: 3500: {
774: 3501: if (new_write_head == lag_tracker->read_heads[i])
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3502: buffer_full = true;
-: 3503: }
-: 3504:
-: 3505: /*
-: 3506: * If the buffer is full, for now we just rewind by one slot and overwrite
-: 3507: * the last sample, as a simple (if somewhat uneven) way to lower the
-: 3508: * sampling rate. There may be better adaptive compaction algorithms.
-: 3509: */
258: 3510: if (buffer_full)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 3511: {
#####: 3512: new_write_head = lag_tracker->write_head;
#####: 3513: if (lag_tracker->write_head > 0)
branch 0 never executed
branch 1 never executed
#####: 3514: lag_tracker->write_head--;
-: 3515: else
#####: 3516: lag_tracker->write_head = LAG_TRACKER_BUFFER_SIZE - 1;
-: 3517: }
-: 3518:
-: 3519: /* Store a sample at the current write head position. */
258: 3520: lag_tracker->buffer[lag_tracker->write_head].lsn = lsn;
258: 3521: lag_tracker->buffer[lag_tracker->write_head].time = local_flush_time;
258: 3522: lag_tracker->write_head = new_write_head;
-: 3523:}
-: 3524:
-: 3525:/*
-: 3526: * Find out how much time has elapsed between the moment WAL location 'lsn'
-: 3527: * (or the highest known earlier LSN) was flushed locally and the time 'now'.
-: 3528: * We have a separate read head for each of the reported LSN locations we
-: 3529: * receive in replies from standby; 'head' controls which read head is
-: 3530: * used. Whenever a read head crosses an LSN which was written into the
-: 3531: * lag buffer with LagTrackerWrite, we can use the associated timestamp to
-: 3532: * find out the time this LSN (or an earlier one) was flushed locally, and
-: 3533: * therefore compute the lag.
-: 3534: *
-: 3535: * Return -1 if no new sample data is available, and otherwise the elapsed
-: 3536: * time in microseconds.
-: 3537: */
-: 3538:static TimeOffset
function LagTrackerRead called 4167 returned 100% blocks executed 76%
4167: 3539:LagTrackerRead(int head, XLogRecPtr lsn, TimestampTz now)
-: 3540:{
4167: 3541: TimestampTz time = 0;
-: 3542:
-: 3543: /* Read all unread samples up to this LSN or end of buffer. */
10692: 3544: while (lag_tracker->read_heads[head] != lag_tracker->write_head &&
branch 0 taken 40% (fallthrough)
branch 1 taken 60%
branch 2 taken 27%
branch 3 taken 73% (fallthrough)
1854: 3545: lag_tracker->buffer[lag_tracker->read_heads[head]].lsn <= lsn)
-: 3546: {
504: 3547: time = lag_tracker->buffer[lag_tracker->read_heads[head]].time;
1008: 3548: lag_tracker->last_read[head] =
504: 3549: lag_tracker->buffer[lag_tracker->read_heads[head]];
1008: 3550: lag_tracker->read_heads[head] =
504: 3551: (lag_tracker->read_heads[head] + 1) % LAG_TRACKER_BUFFER_SIZE;
-: 3552: }
-: 3553:
-: 3554: /*
-: 3555: * If the lag tracker is empty, that means the standby has processed
-: 3556: * everything we've ever sent so we should now clear 'last_read'. If we
-: 3557: * didn't do that, we'd risk using a stale and irrelevant sample for
-: 3558: * interpolation at the beginning of the next burst of WAL after a period
-: 3559: * of idleness.
-: 3560: */
4167: 3561: if (lag_tracker->read_heads[head] == lag_tracker->write_head)
branch 0 taken 68% (fallthrough)
branch 1 taken 32%
2817: 3562: lag_tracker->last_read[head].time = 0;
-: 3563:
4167: 3564: if (time > now)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 3565: {
-: 3566: /* If the clock somehow went backwards, treat as not found. */
#####: 3567: return -1;
-: 3568: }
4167: 3569: else if (time == 0)
branch 0 taken 89% (fallthrough)
branch 1 taken 11%
-: 3570: {
-: 3571: /*
-: 3572: * We didn't cross a time. If there is a future sample that we
-: 3573: * haven't reached yet, and we've already reached at least one sample,
-: 3574: * let's interpolate the local flushed time. This is mainly useful
-: 3575: * for reporting a completely stuck apply position as having
-: 3576: * increasing lag, since otherwise we'd have to wait for it to
-: 3577: * eventually start moving again and cross one of our samples before
-: 3578: * we can show the lag increasing.
-: 3579: */
3689: 3580: if (lag_tracker->read_heads[head] == lag_tracker->write_head)
branch 0 taken 64% (fallthrough)
branch 1 taken 36%
-: 3581: {
-: 3582: /* There are no future samples, so we can't interpolate. */
2360: 3583: return -1;
-: 3584: }
1329: 3585: else if (lag_tracker->last_read[head].time != 0)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 3586: {
-: 3587: /* We can interpolate between last_read and the next sample. */
-: 3588: double fraction;
18: 3589: WalTimeSample prev = lag_tracker->last_read[head];
18: 3590: WalTimeSample next = lag_tracker->buffer[lag_tracker->read_heads[head]];
-: 3591:
18: 3592: if (lsn < prev.lsn)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 3593: {
-: 3594: /*
-: 3595: * Reported LSNs shouldn't normally go backwards, but it's
-: 3596: * possible when there is a timeline change. Treat as not
-: 3597: * found.
-: 3598: */
#####: 3599: return -1;
-: 3600: }
-: 3601:
18: 3602: Assert(prev.lsn < next.lsn);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3603:
18: 3604: if (prev.time > next.time)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 3605: {
-: 3606: /* If the clock somehow went backwards, treat as not found. */
#####: 3607: return -1;
-: 3608: }
-: 3609:
-: 3610: /* See how far we are between the previous and next samples. */
18: 3611: fraction =
18: 3612: (double) (lsn - prev.lsn) / (double) (next.lsn - prev.lsn);
-: 3613:
-: 3614: /* Scale the local flush time proportionally. */
18: 3615: time = (TimestampTz)
18: 3616: ((double) prev.time + (next.time - prev.time) * fraction);
-: 3617: }
-: 3618: else
-: 3619: {
-: 3620: /*
-: 3621: * We have only a future sample, implying that we were entirely
-: 3622: * caught up but and now there is a new burst of WAL and the
-: 3623: * standby hasn't processed the first sample yet. Until the
-: 3624: * standby reaches the future sample the best we can do is report
-: 3625: * the hypothetical lag if that sample were to be replayed now.
-: 3626: */
1311: 3627: time = lag_tracker->buffer[lag_tracker->read_heads[head]].time;
-: 3628: }
-: 3629: }
-: 3630:
-: 3631: /* Return the elapsed time since local flush time in microseconds. */
1807: 3632: Assert(time != 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1807: 3633: return now - time;
-: 3634:}
-: 3635:
-: 3636:static void
function UpdateSpillStats called 15 returned 100% blocks executed 88%
15: 3637:UpdateSpillStats(LogicalDecodingContext *ctx)
-: 3638:{
15: 3639: ReorderBuffer *rb = ctx->reorder;
-: 3640:
15: 3641: SpinLockAcquire(&MyWalSnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 3642:
15: 3643: MyWalSnd->spillTxns = rb->spillTxns;
15: 3644: MyWalSnd->spillCount = rb->spillCount;
15: 3645: MyWalSnd->spillBytes = rb->spillBytes;
-: 3646:
15: 3647: elog(WARNING, "UpdateSpillStats: updating stats %p %ld %ld %ld",
call 0 returned 100%
call 1 returned 100%
-: 3648: rb, rb->spillTxns, rb->spillCount, rb->spillBytes);
-: 3649:
15: 3650: SpinLockRelease(&MyWalSnd->mutex);
call 0 returned 100%
15: 3651:}
0001-Add-logical_decoding_work_mem-to-limit-ReorderBuffer-patch_coverage/walreceiver.c.gcov 0000664 0001750 0000000 00000243012 13560210457 031341 0 ustar vignesh root -: 0:Source:walreceiver.c
-: 0:Graph:./walreceiver.gcno
-: 0:Data:./walreceiver.gcda
-: 0:Runs:6533
-: 0:Programs:1
-: 1:/*-------------------------------------------------------------------------
-: 2: *
-: 3: * walreceiver.c
-: 4: *
-: 5: * The WAL receiver process (walreceiver) is new as of Postgres 9.0. It
-: 6: * is the process in the standby server that takes charge of receiving
-: 7: * XLOG records from a primary server during streaming replication.
-: 8: *
-: 9: * When the startup process determines that it's time to start streaming,
-: 10: * it instructs postmaster to start walreceiver. Walreceiver first connects
-: 11: * to the primary server (it will be served by a walsender process
-: 12: * in the primary server), and then keeps receiving XLOG records and
-: 13: * writing them to the disk as long as the connection is alive. As XLOG
-: 14: * records are received and flushed to disk, it updates the
-: 15: * WalRcv->receivedUpto variable in shared memory, to inform the startup
-: 16: * process of how far it can proceed with XLOG replay.
-: 17: *
-: 18: * If the primary server ends streaming, but doesn't disconnect, walreceiver
-: 19: * goes into "waiting" mode, and waits for the startup process to give new
-: 20: * instructions. The startup process will treat that the same as
-: 21: * disconnection, and will rescan the archive/pg_wal directory. But when the
-: 22: * startup process wants to try streaming replication again, it will just
-: 23: * nudge the existing walreceiver process that's waiting, instead of launching
-: 24: * a new one.
-: 25: *
-: 26: * Normal termination is by SIGTERM, which instructs the walreceiver to
-: 27: * exit(0). Emergency termination is by SIGQUIT; like any postmaster child
-: 28: * process, the walreceiver will simply abort and exit on SIGQUIT. A close
-: 29: * of the connection and a FATAL error are treated not as a crash but as
-: 30: * normal operation.
-: 31: *
-: 32: * This file contains the server-facing parts of walreceiver. The libpq-
-: 33: * specific parts are in the libpqwalreceiver module. It's loaded
-: 34: * dynamically to avoid linking the server with libpq.
-: 35: *
-: 36: * Portions Copyright (c) 2010-2019, PostgreSQL Global Development Group
-: 37: *
-: 38: *
-: 39: * IDENTIFICATION
-: 40: * src/backend/replication/walreceiver.c
-: 41: *
-: 42: *-------------------------------------------------------------------------
-: 43: */
-: 44:#include "postgres.h"
-: 45:
-: 46:#include <signal.h>
-: 47:#include <unistd.h>
-: 48:
-: 49:#include "access/htup_details.h"
-: 50:#include "access/timeline.h"
-: 51:#include "access/transam.h"
-: 52:#include "access/xlog_internal.h"
-: 53:#include "catalog/pg_authid.h"
-: 54:#include "catalog/pg_type.h"
-: 55:#include "common/ip.h"
-: 56:#include "funcapi.h"
-: 57:#include "libpq/pqformat.h"
-: 58:#include "libpq/pqsignal.h"
-: 59:#include "miscadmin.h"
-: 60:#include "pgstat.h"
-: 61:#include "replication/walreceiver.h"
-: 62:#include "replication/walsender.h"
-: 63:#include "storage/ipc.h"
-: 64:#include "storage/pmsignal.h"
-: 65:#include "storage/procarray.h"
-: 66:#include "utils/builtins.h"
-: 67:#include "utils/guc.h"
-: 68:#include "utils/pg_lsn.h"
-: 69:#include "utils/ps_status.h"
-: 70:#include "utils/resowner.h"
-: 71:#include "utils/timestamp.h"
-: 72:
-: 73:
-: 74:/* GUC variables */
-: 75:int wal_receiver_status_interval;
-: 76:int wal_receiver_timeout;
-: 77:bool hot_standby_feedback;
-: 78:
-: 79:/* libpqwalreceiver connection */
-: 80:static WalReceiverConn *wrconn = NULL;
-: 81:WalReceiverFunctionsType *WalReceiverFunctions = NULL;
-: 82:
-: 83:#define NAPTIME_PER_CYCLE 100 /* max sleep time between cycles (100ms) */
-: 84:
-: 85:/*
-: 86: * These variables are used similarly to openLogFile/SegNo/Off,
-: 87: * but for walreceiver to write the XLOG. recvFileTLI is the TimeLineID
-: 88: * corresponding the filename of recvFile.
-: 89: */
-: 90:static int recvFile = -1;
-: 91:static TimeLineID recvFileTLI = 0;
-: 92:static XLogSegNo recvSegNo = 0;
-: 93:static uint32 recvOff = 0;
-: 94:
-: 95:/*
-: 96: * Flags set by interrupt handlers of walreceiver for later service in the
-: 97: * main loop.
-: 98: */
-: 99:static volatile sig_atomic_t got_SIGHUP = false;
-: 100:static volatile sig_atomic_t got_SIGTERM = false;
-: 101:
-: 102:/*
-: 103: * LogstreamResult indicates the byte positions that we have already
-: 104: * written/fsynced.
-: 105: */
-: 106:static struct
-: 107:{
-: 108: XLogRecPtr Write; /* last byte + 1 written out in the standby */
-: 109: XLogRecPtr Flush; /* last byte + 1 flushed in the standby */
-: 110:} LogstreamResult;
-: 111:
-: 112:static StringInfoData reply_message;
-: 113:static StringInfoData incoming_message;
-: 114:
-: 115:/* Prototypes for private functions */
-: 116:static void WalRcvFetchTimeLineHistoryFiles(TimeLineID first, TimeLineID last);
-: 117:static void WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI);
-: 118:static void WalRcvDie(int code, Datum arg);
-: 119:static void XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len);
-: 120:static void XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr);
-: 121:static void XLogWalRcvFlush(bool dying);
-: 122:static void XLogWalRcvSendReply(bool force, bool requestReply);
-: 123:static void XLogWalRcvSendHSFeedback(bool immed);
-: 124:static void ProcessWalSndrMessage(XLogRecPtr walEnd, TimestampTz sendTime);
-: 125:
-: 126:/* Signal handlers */
-: 127:static void WalRcvSigHupHandler(SIGNAL_ARGS);
-: 128:static void WalRcvSigUsr1Handler(SIGNAL_ARGS);
-: 129:static void WalRcvShutdownHandler(SIGNAL_ARGS);
-: 130:static void WalRcvQuickDieHandler(SIGNAL_ARGS);
-: 131:
-: 132:
-: 133:/*
-: 134: * Process any interrupts the walreceiver process may have received.
-: 135: * This should be called any time the process's latch has become set.
-: 136: *
-: 137: * Currently, only SIGTERM is of interest. We can't just exit(1) within the
-: 138: * SIGTERM signal handler, because the signal might arrive in the middle of
-: 139: * some critical operation, like while we're holding a spinlock. Instead, the
-: 140: * signal handler sets a flag variable as well as setting the process's latch.
-: 141: * We must check the flag (by calling ProcessWalRcvInterrupts) anytime the
-: 142: * latch has become set. Operations that could block for a long time, such as
-: 143: * reading from a remote server, must pay attention to the latch too; see
-: 144: * libpqrcv_PQgetResult for example.
-: 145: */
-: 146:void
function ProcessWalRcvInterrupts called 1407 returned 99% blocks executed 73%
1407: 147:ProcessWalRcvInterrupts(void)
-: 148:{
-: 149: /*
-: 150: * Although walreceiver interrupt handling doesn't use the same scheme as
-: 151: * regular backends, call CHECK_FOR_INTERRUPTS() to make sure we receive
-: 152: * any incoming signals on Win32.
-: 153: */
1407: 154: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 155:
1407: 156: if (got_SIGTERM)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 157: {
18: 158: ereport(FATAL,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 0%
call 6 never executed
-: 159: (errcode(ERRCODE_ADMIN_SHUTDOWN),
-: 160: errmsg("terminating walreceiver process due to administrator command")));
-: 161: }
1389: 162:}
-: 163:
-: 164:
-: 165:/* Main entry point for walreceiver process */
-: 166:void
function WalReceiverMain called 97 returned 0% blocks executed 71%
97: 167:WalReceiverMain(void)
-: 168:{
-: 169: char conninfo[MAXCONNINFO];
-: 170: char *tmp_conninfo;
-: 171: char slotname[NAMEDATALEN];
-: 172: XLogRecPtr startpoint;
-: 173: TimeLineID startpointTLI;
-: 174: TimeLineID primaryTLI;
-: 175: bool first_stream;
97: 176: WalRcvData *walrcv = WalRcv;
-: 177: TimestampTz last_recv_timestamp;
-: 178: TimestampTz now;
-: 179: bool ping_sent;
-: 180: char *err;
97: 181: char *sender_host = NULL;
97: 182: int sender_port = 0;
-: 183:
-: 184: /*
-: 185: * WalRcv should be set up already (if we are a backend, we inherit this
-: 186: * by fork() or EXEC_BACKEND mechanism from the postmaster).
-: 187: */
97: 188: Assert(walrcv != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 189:
97: 190: now = GetCurrentTimestamp();
call 0 returned 100%
-: 191:
-: 192: /*
-: 193: * Mark walreceiver as running in shared memory.
-: 194: *
-: 195: * Do this as early as possible, so that if we fail later on, we'll set
-: 196: * state to STOPPED. If we die before this, the startup process will keep
-: 197: * waiting for us to start up, until it times out.
-: 198: */
97: 199: SpinLockAcquire(&walrcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
97: 200: Assert(walrcv->pid == 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
97: 201: switch (walrcv->walRcvState)
branch 0 taken 0%
branch 1 taken 1%
branch 2 taken 99%
branch 3 taken 0%
-: 202: {
-: 203: case WALRCV_STOPPING:
-: 204: /* If we've already been requested to stop, don't start up. */
#####: 205: walrcv->walRcvState = WALRCV_STOPPED;
-: 206: /* fall through */
-: 207:
-: 208: case WALRCV_STOPPED:
1: 209: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
1: 210: proc_exit(1);
call 0 returned 0%
-: 211: break;
-: 212:
-: 213: case WALRCV_STARTING:
-: 214: /* The usual case */
96: 215: break;
-: 216:
-: 217: case WALRCV_WAITING:
-: 218: case WALRCV_STREAMING:
-: 219: case WALRCV_RESTARTING:
-: 220: default:
-: 221: /* Shouldn't happen */
#####: 222: SpinLockRelease(&walrcv->mutex);
call 0 never executed
#####: 223: elog(PANIC, "walreceiver still running according to shared memory state");
call 0 never executed
call 1 never executed
call 2 never executed
-: 224: }
-: 225: /* Advertise our PID so that the startup process can kill us */
96: 226: walrcv->pid = MyProcPid;
96: 227: walrcv->walRcvState = WALRCV_STREAMING;
-: 228:
-: 229: /* Fetch information required to start streaming */
96: 230: walrcv->ready_to_display = false;
96: 231: strlcpy(conninfo, (char *) walrcv->conninfo, MAXCONNINFO);
call 0 returned 100%
96: 232: strlcpy(slotname, (char *) walrcv->slotname, NAMEDATALEN);
call 0 returned 100%
96: 233: startpoint = walrcv->receiveStart;
96: 234: startpointTLI = walrcv->receiveStartTLI;
-: 235:
-: 236: /* Initialise to a sanish value */
96: 237: walrcv->lastMsgSendTime =
96: 238: walrcv->lastMsgReceiptTime = walrcv->latestWalEndTime = now;
-: 239:
-: 240: /* Report the latch to use to awaken this process */
96: 241: walrcv->latch = &MyProc->procLatch;
-: 242:
96: 243: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
-: 244:
-: 245: /* Arrange to clean up at walreceiver exit */
96: 246: on_shmem_exit(WalRcvDie, 0);
call 0 returned 100%
-: 247:
-: 248: /* Properly accept or ignore signals the postmaster might send us */
96: 249: pqsignal(SIGHUP, WalRcvSigHupHandler); /* set flag to read config file */
call 0 returned 100%
96: 250: pqsignal(SIGINT, SIG_IGN);
call 0 returned 100%
96: 251: pqsignal(SIGTERM, WalRcvShutdownHandler); /* request shutdown */
call 0 returned 100%
96: 252: pqsignal(SIGQUIT, WalRcvQuickDieHandler); /* hard crash time */
call 0 returned 100%
96: 253: pqsignal(SIGALRM, SIG_IGN);
call 0 returned 100%
96: 254: pqsignal(SIGPIPE, SIG_IGN);
call 0 returned 100%
96: 255: pqsignal(SIGUSR1, WalRcvSigUsr1Handler);
call 0 returned 100%
96: 256: pqsignal(SIGUSR2, SIG_IGN);
call 0 returned 100%
-: 257:
-: 258: /* Reset some signals that are accepted by postmaster but not here */
96: 259: pqsignal(SIGCHLD, SIG_DFL);
call 0 returned 100%
-: 260:
-: 261: /* We allow SIGQUIT (quickdie) at all times */
96: 262: sigdelset(&BlockSig, SIGQUIT);
call 0 returned 100%
-: 263:
-: 264: /* Load the libpq-specific functions */
96: 265: load_file("libpqwalreceiver", false);
call 0 returned 100%
96: 266: if (WalReceiverFunctions == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 267: elog(ERROR, "libpqwalreceiver didn't initialize correctly");
call 0 never executed
call 1 never executed
call 2 never executed
-: 268:
-: 269: /* Unblock signals (they were blocked when the postmaster forked us) */
96: 270: PG_SETMASK(&UnBlockSig);
call 0 returned 100%
-: 271:
-: 272: /* Establish the connection to the primary for XLOG streaming */
96: 273: wrconn = walrcv_connect(conninfo, false, cluster_name[0] ? cluster_name : "walreceiver", &err);
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
call 2 returned 100%
96: 274: if (!wrconn)
branch 0 taken 47% (fallthrough)
branch 1 taken 53%
45: 275: ereport(ERROR,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 0%
call 5 never executed
-: 276: (errmsg("could not connect to the primary server: %s", err)));
-: 277:
-: 278: /*
-: 279: * Save user-visible connection string. This clobbers the original
-: 280: * conninfo, for security. Also save host and port of the sender server
-: 281: * this walreceiver is connected to.
-: 282: */
51: 283: tmp_conninfo = walrcv_get_conninfo(wrconn);
call 0 returned 100%
51: 284: walrcv_get_senderinfo(wrconn, &sender_host, &sender_port);
call 0 returned 100%
51: 285: SpinLockAcquire(&walrcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
51: 286: memset(walrcv->conninfo, 0, MAXCONNINFO);
51: 287: if (tmp_conninfo)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
51: 288: strlcpy((char *) walrcv->conninfo, tmp_conninfo, MAXCONNINFO);
call 0 returned 100%
-: 289:
51: 290: memset(walrcv->sender_host, 0, NI_MAXHOST);
51: 291: if (sender_host)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
51: 292: strlcpy((char *) walrcv->sender_host, sender_host, NI_MAXHOST);
call 0 returned 100%
-: 293:
51: 294: walrcv->sender_port = sender_port;
51: 295: walrcv->ready_to_display = true;
51: 296: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
-: 297:
51: 298: if (tmp_conninfo)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
51: 299: pfree(tmp_conninfo);
call 0 returned 100%
-: 300:
51: 301: if (sender_host)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
51: 302: pfree(sender_host);
call 0 returned 100%
-: 303:
51: 304: first_stream = true;
-: 305: for (;;)
-: 306: {
-: 307: char *primary_sysid;
-: 308: char standby_sysid[32];
-: 309: WalRcvStreamOptions options;
-: 310:
-: 311: /*
-: 312: * Check that we're connected to a valid server using the
-: 313: * IDENTIFY_SYSTEM replication command.
-: 314: */
57: 315: primary_sysid = walrcv_identify_system(wrconn, &primaryTLI);
call 0 returned 100%
-: 316:
57: 317: snprintf(standby_sysid, sizeof(standby_sysid), UINT64_FORMAT,
call 0 returned 100%
call 1 returned 100%
-: 318: GetSystemIdentifier());
57: 319: if (strcmp(primary_sysid, standby_sysid) != 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 320: {
#####: 321: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 322: (errmsg("database system identifier differs between the primary and standby"),
-: 323: errdetail("The primary's identifier is %s, the standby's identifier is %s.",
-: 324: primary_sysid, standby_sysid)));
-: 325: }
-: 326:
-: 327: /*
-: 328: * Confirm that the current timeline of the primary is the same or
-: 329: * ahead of ours.
-: 330: */
57: 331: if (primaryTLI < startpointTLI)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 332: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 333: (errmsg("highest timeline %u of the primary is behind recovery timeline %u",
-: 334: primaryTLI, startpointTLI)));
-: 335:
-: 336: /*
-: 337: * Get any missing history files. We do this always, even when we're
-: 338: * not interested in that timeline, so that if we're promoted to
-: 339: * become the master later on, we don't select the same timeline that
-: 340: * was already used in the current master. This isn't bullet-proof -
-: 341: * you'll need some external software to manage your cluster if you
-: 342: * need to ensure that a unique timeline id is chosen in every case,
-: 343: * but let's avoid the confusion of timeline id collisions where we
-: 344: * can.
-: 345: */
57: 346: WalRcvFetchTimeLineHistoryFiles(startpointTLI, primaryTLI);
call 0 returned 100%
-: 347:
-: 348: /*
-: 349: * Start streaming.
-: 350: *
-: 351: * We'll try to start at the requested starting point and timeline,
-: 352: * even if it's different from the server's latest timeline. In case
-: 353: * we've already reached the end of the old timeline, the server will
-: 354: * finish the streaming immediately, and we will go back to await
-: 355: * orders from the startup process. If recovery_target_timeline is
-: 356: * 'latest', the startup process will scan pg_wal and find the new
-: 357: * history file, bump recovery target timeline, and ask us to restart
-: 358: * on the new timeline.
-: 359: */
57: 360: options.logical = false;
57: 361: options.startpoint = startpoint;
57: 362: options.slotname = slotname[0] != '\0' ? slotname : NULL;
branch 0 taken 5% (fallthrough)
branch 1 taken 95%
57: 363: options.proto.physical.startpointTLI = startpointTLI;
57: 364: ThisTimeLineID = startpointTLI;
57: 365: if (walrcv_startstreaming(wrconn, &options))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
-: 366: {
57: 367: if (first_stream)
branch 0 taken 89% (fallthrough)
branch 1 taken 11%
51: 368: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 369: (errmsg("started streaming WAL from primary at %X/%X on timeline %u",
-: 370: (uint32) (startpoint >> 32), (uint32) startpoint,
-: 371: startpointTLI)));
-: 372: else
6: 373: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 374: (errmsg("restarted WAL streaming at %X/%X on timeline %u",
-: 375: (uint32) (startpoint >> 32), (uint32) startpoint,
-: 376: startpointTLI)));
57: 377: first_stream = false;
-: 378:
-: 379: /* Initialize LogstreamResult and buffers for processing messages */
57: 380: LogstreamResult.Write = LogstreamResult.Flush = GetXLogReplayRecPtr(NULL);
call 0 returned 100%
57: 381: initStringInfo(&reply_message);
call 0 returned 100%
57: 382: initStringInfo(&incoming_message);
call 0 returned 100%
-: 383:
-: 384: /* Initialize the last recv timestamp */
57: 385: last_recv_timestamp = GetCurrentTimestamp();
call 0 returned 100%
57: 386: ping_sent = false;
-: 387:
-: 388: /* Loop until end-of-streaming or error */
-: 389: for (;;)
-: 390: {
-: 391: char *buf;
-: 392: int len;
929: 393: bool endofwal = false;
929: 394: pgsocket wait_fd = PGINVALID_SOCKET;
-: 395: int rc;
-: 396:
-: 397: /*
-: 398: * Exit walreceiver if we're not in recovery. This should not
-: 399: * happen, but cross-check the status here.
-: 400: */
929: 401: if (!RecoveryInProgress())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 402: ereport(FATAL,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 403: (errmsg("cannot continue WAL streaming, recovery has already ended")));
-: 404:
-: 405: /* Process any requests or signals received recently */
929: 406: ProcessWalRcvInterrupts();
call 0 returned 100%
-: 407:
929: 408: if (got_SIGHUP)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 409: {
5: 410: got_SIGHUP = false;
5: 411: ProcessConfigFile(PGC_SIGHUP);
call 0 returned 100%
5: 412: XLogWalRcvSendHSFeedback(true);
call 0 returned 100%
-: 413: }
-: 414:
-: 415: /* See if we can read data immediately */
929: 416: len = walrcv_receive(wrconn, &buf, &wait_fd);
call 0 returned 98%
910: 417: if (len != 0)
branch 0 taken 29% (fallthrough)
branch 1 taken 71%
-: 418: {
-: 419: /*
-: 420: * Process the received data, and any subsequent data we
-: 421: * can read without blocking.
-: 422: */
-: 423: for (;;)
-: 424: {
570: 425: if (len > 0)
branch 0 taken 54% (fallthrough)
branch 1 taken 46%
-: 426: {
-: 427: /*
-: 428: * Something was received from master, so reset
-: 429: * timeout
-: 430: */
309: 431: last_recv_timestamp = GetCurrentTimestamp();
call 0 returned 100%
309: 432: ping_sent = false;
309: 433: XLogWalRcvProcessMsg(buf[0], &buf[1], len - 1);
call 0 returned 100%
-: 434: }
261: 435: else if (len == 0)
branch 0 taken 92% (fallthrough)
branch 1 taken 8%
241: 436: break;
20: 437: else if (len < 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 438: {
20: 439: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 100%
-: 440: (errmsg("replication terminated by primary server"),
-: 441: errdetail("End of WAL reached on timeline %u at %X/%X.",
-: 442: startpointTLI,
-: 443: (uint32) (LogstreamResult.Write >> 32), (uint32) LogstreamResult.Write)));
20: 444: endofwal = true;
20: 445: break;
-: 446: }
309: 447: len = walrcv_receive(wrconn, &buf, &wait_fd);
call 0 returned 100%
309: 448: }
-: 449:
-: 450: /* Let the master know that we received some data. */
261: 451: XLogWalRcvSendReply(false, false);
call 0 returned 100%
-: 452:
-: 453: /*
-: 454: * If we've written some records, flush them to disk and
-: 455: * let the startup process and primary server know about
-: 456: * them.
-: 457: */
261: 458: XLogWalRcvFlush(false);
call 0 returned 100%
-: 459: }
-: 460:
-: 461: /* Check if we need to exit the streaming loop. */
910: 462: if (endofwal)
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
20: 463: break;
-: 464:
-: 465: /*
-: 466: * Ideally we would reuse a WaitEventSet object repeatedly
-: 467: * here to avoid the overheads of WaitLatchOrSocket on epoll
-: 468: * systems, but we can't be sure that libpq (or any other
-: 469: * walreceiver implementation) has the same socket (even if
-: 470: * the fd is the same number, it may have been closed and
-: 471: * reopened since the last time). In future, if there is a
-: 472: * function for removing sockets from WaitEventSet, then we
-: 473: * could add and remove just the socket each time, potentially
-: 474: * avoiding some system calls.
-: 475: */
890: 476: Assert(wait_fd != PGINVALID_SOCKET);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
890: 477: rc = WaitLatchOrSocket(walrcv->latch,
call 0 returned 100%
-: 478: WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
-: 479: WL_TIMEOUT | WL_LATCH_SET,
-: 480: wait_fd,
-: 481: NAPTIME_PER_CYCLE,
-: 482: WAIT_EVENT_WAL_RECEIVER_MAIN);
890: 483: if (rc & WL_LATCH_SET)
branch 0 taken 23% (fallthrough)
branch 1 taken 77%
-: 484: {
208: 485: ResetLatch(walrcv->latch);
call 0 returned 100%
208: 486: ProcessWalRcvInterrupts();
call 0 returned 91%
-: 487:
190: 488: if (walrcv->force_reply)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 489: {
-: 490: /*
-: 491: * The recovery process has asked us to send apply
-: 492: * feedback now. Make sure the flag is really set to
-: 493: * false in shared memory before sending the reply, so
-: 494: * we don't miss a new request for a reply.
-: 495: */
190: 496: walrcv->force_reply = false;
190: 497: pg_memory_barrier();
call 0 returned 100%
190: 498: XLogWalRcvSendReply(true, false);
call 0 returned 100%
-: 499: }
-: 500: }
872: 501: if (rc & WL_TIMEOUT)
branch 0 taken 39% (fallthrough)
branch 1 taken 61%
-: 502: {
-: 503: /*
-: 504: * We didn't receive anything new. If we haven't heard
-: 505: * anything from the server for more than
-: 506: * wal_receiver_timeout / 2, ping the server. Also, if
-: 507: * it's been longer than wal_receiver_status_interval
-: 508: * since the last update we sent, send a status update to
-: 509: * the master anyway, to report any progress in applying
-: 510: * WAL.
-: 511: */
341: 512: bool requestReply = false;
-: 513:
-: 514: /*
-: 515: * Check if time since last receive from standby has
-: 516: * reached the configured limit.
-: 517: */
341: 518: if (wal_receiver_timeout > 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 519: {
341: 520: TimestampTz now = GetCurrentTimestamp();
call 0 returned 100%
-: 521: TimestampTz timeout;
-: 522:
341: 523: timeout =
341: 524: TimestampTzPlusMilliseconds(last_recv_timestamp,
-: 525: wal_receiver_timeout);
-: 526:
341: 527: if (now >= timeout)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 528: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 529: (errmsg("terminating walreceiver due to timeout")));
-: 530:
-: 531: /*
-: 532: * We didn't receive anything new, for half of
-: 533: * receiver replication timeout. Ping the server.
-: 534: */
341: 535: if (!ping_sent)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 536: {
341: 537: timeout = TimestampTzPlusMilliseconds(last_recv_timestamp,
-: 538: (wal_receiver_timeout / 2));
341: 539: if (now >= timeout)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 540: {
#####: 541: requestReply = true;
#####: 542: ping_sent = true;
-: 543: }
-: 544: }
-: 545: }
-: 546:
341: 547: XLogWalRcvSendReply(requestReply, requestReply);
call 0 returned 100%
341: 548: XLogWalRcvSendHSFeedback(false);
call 0 returned 100%
-: 549: }
872: 550: }
-: 551:
-: 552: /*
-: 553: * The backend finished streaming. Exit streaming COPY-mode from
-: 554: * our side, too.
-: 555: */
20: 556: walrcv_endstreaming(wrconn, &primaryTLI);
call 0 returned 30%
-: 557:
-: 558: /*
-: 559: * If the server had switched to a new timeline that we didn't
-: 560: * know about when we began streaming, fetch its timeline history
-: 561: * file now.
-: 562: */
6: 563: WalRcvFetchTimeLineHistoryFiles(startpointTLI, primaryTLI);
call 0 returned 100%
-: 564: }
-: 565: else
#####: 566: ereport(LOG,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 567: (errmsg("primary server contains no more WAL on requested timeline %u",
-: 568: startpointTLI)));
-: 569:
-: 570: /*
-: 571: * End of WAL reached on the requested timeline. Close the last
-: 572: * segment, and await for new orders from the startup process.
-: 573: */
6: 574: if (recvFile >= 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 575: {
-: 576: char xlogfname[MAXFNAMELEN];
-: 577:
6: 578: XLogWalRcvFlush(false);
call 0 returned 100%
6: 579: if (close(recvFile) != 0)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 580: ereport(PANIC,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 581: (errcode_for_file_access(),
-: 582: errmsg("could not close log segment %s: %m",
-: 583: XLogFileNameP(recvFileTLI, recvSegNo))));
-: 584:
-: 585: /*
-: 586: * Create .done file forcibly to prevent the streamed segment from
-: 587: * being archived later.
-: 588: */
6: 589: XLogFileName(xlogfname, recvFileTLI, recvSegNo, wal_segment_size);
call 0 returned 100%
6: 590: if (XLogArchiveMode != ARCHIVE_MODE_ALWAYS)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
6: 591: XLogArchiveForceDone(xlogfname);
call 0 returned 100%
-: 592: else
#####: 593: XLogArchiveNotify(xlogfname);
call 0 never executed
-: 594: }
6: 595: recvFile = -1;
-: 596:
6: 597: elog(DEBUG1, "walreceiver ended streaming and awaits new instructions");
call 0 returned 100%
call 1 returned 100%
6: 598: WalRcvWaitForStartPosition(&startpoint, &startpointTLI);
call 0 returned 100%
6: 599: }
-: 600: /* not reached */
-: 601:}
-: 602:
-: 603:/*
-: 604: * Wait for startup process to set receiveStart and receiveStartTLI.
-: 605: */
-: 606:static void
function WalRcvWaitForStartPosition called 6 returned 100% blocks executed 69%
6: 607:WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
-: 608:{
6: 609: WalRcvData *walrcv = WalRcv;
-: 610: int state;
-: 611:
6: 612: SpinLockAcquire(&walrcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
6: 613: state = walrcv->walRcvState;
6: 614: if (state != WALRCV_STREAMING)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 615: {
#####: 616: SpinLockRelease(&walrcv->mutex);
call 0 never executed
#####: 617: if (state == WALRCV_STOPPING)
branch 0 never executed
branch 1 never executed
#####: 618: proc_exit(0);
call 0 never executed
-: 619: else
#####: 620: elog(FATAL, "unexpected walreceiver state");
call 0 never executed
call 1 never executed
call 2 never executed
-: 621: }
6: 622: walrcv->walRcvState = WALRCV_WAITING;
6: 623: walrcv->receiveStart = InvalidXLogRecPtr;
6: 624: walrcv->receiveStartTLI = 0;
6: 625: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
-: 626:
6: 627: if (update_process_title)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
6: 628: set_ps_display("idle", false);
call 0 returned 100%
-: 629:
-: 630: /*
-: 631: * nudge startup process to notice that we've stopped streaming and are
-: 632: * now waiting for instructions.
-: 633: */
6: 634: WakeupRecovery();
call 0 returned 100%
-: 635: for (;;)
-: 636: {
12: 637: ResetLatch(walrcv->latch);
call 0 returned 100%
-: 638:
12: 639: ProcessWalRcvInterrupts();
call 0 returned 100%
-: 640:
12: 641: SpinLockAcquire(&walrcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
12: 642: Assert(walrcv->walRcvState == WALRCV_RESTARTING ||
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
branch 4 never executed
branch 5 never executed
call 6 never executed
-: 643: walrcv->walRcvState == WALRCV_WAITING ||
-: 644: walrcv->walRcvState == WALRCV_STOPPING);
12: 645: if (walrcv->walRcvState == WALRCV_RESTARTING)
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
-: 646: {
-: 647: /* we don't expect primary_conninfo to change */
6: 648: *startpoint = walrcv->receiveStart;
6: 649: *startpointTLI = walrcv->receiveStartTLI;
6: 650: walrcv->walRcvState = WALRCV_STREAMING;
6: 651: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
6: 652: break;
-: 653: }
6: 654: if (walrcv->walRcvState == WALRCV_STOPPING)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 655: {
-: 656: /*
-: 657: * We should've received SIGTERM if the startup process wants us
-: 658: * to die, but might as well check it here too.
-: 659: */
#####: 660: SpinLockRelease(&walrcv->mutex);
call 0 never executed
#####: 661: exit(1);
call 0 never executed
-: 662: }
6: 663: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
-: 664:
6: 665: (void) WaitLatch(walrcv->latch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
call 0 returned 100%
-: 666: WAIT_EVENT_WAL_RECEIVER_WAIT_START);
6: 667: }
-: 668:
6: 669: if (update_process_title)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 670: {
-: 671: char activitymsg[50];
-: 672:
12: 673: snprintf(activitymsg, sizeof(activitymsg), "restarting at %X/%X",
call 0 returned 100%
6: 674: (uint32) (*startpoint >> 32),
6: 675: (uint32) *startpoint);
6: 676: set_ps_display(activitymsg, false);
call 0 returned 100%
-: 677: }
6: 678:}
-: 679:
-: 680:/*
-: 681: * Fetch any missing timeline history files between 'first' and 'last'
-: 682: * (inclusive) from the server.
-: 683: */
-: 684:static void
function WalRcvFetchTimeLineHistoryFiles called 63 returned 100% blocks executed 73%
63: 685:WalRcvFetchTimeLineHistoryFiles(TimeLineID first, TimeLineID last)
-: 686:{
-: 687: TimeLineID tli;
-: 688:
138: 689: for (tli = first; tli <= last; tli++)
branch 0 taken 54%
branch 1 taken 46% (fallthrough)
-: 690: {
-: 691: /* there's no history file for timeline 1 */
75: 692: if (tli != 1 && !existsTimeLineHistory(tli))
branch 0 taken 33% (fallthrough)
branch 1 taken 67%
call 2 returned 100%
branch 3 taken 24% (fallthrough)
branch 4 taken 76%
-: 693: {
-: 694: char *fname;
-: 695: char *content;
-: 696: int len;
-: 697: char expectedfname[MAXFNAMELEN];
-: 698:
6: 699: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 700: (errmsg("fetching timeline history file for timeline %u from primary server",
-: 701: tli)));
-: 702:
6: 703: walrcv_readtimelinehistoryfile(wrconn, tli, &fname, &content, &len);
call 0 returned 100%
-: 704:
-: 705: /*
-: 706: * Check that the filename on the master matches what we
-: 707: * calculated ourselves. This is just a sanity check, it should
-: 708: * always match.
-: 709: */
6: 710: TLHistoryFileName(expectedfname, tli);
call 0 returned 100%
6: 711: if (strcmp(fname, expectedfname) != 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 712: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 713: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 714: errmsg_internal("primary reported unexpected file name for timeline history file of timeline %u",
-: 715: tli)));
-: 716:
-: 717: /*
-: 718: * Write the file to pg_wal.
-: 719: */
6: 720: writeTimeLineHistoryFile(tli, content, len);
call 0 returned 100%
-: 721:
6: 722: pfree(fname);
call 0 returned 100%
6: 723: pfree(content);
call 0 returned 100%
-: 724: }
-: 725: }
63: 726:}
-: 727:
-: 728:/*
-: 729: * Mark us as STOPPED in shared memory at exit.
-: 730: */
-: 731:static void
function WalRcvDie called 96 returned 100% blocks executed 83%
96: 732:WalRcvDie(int code, Datum arg)
-: 733:{
96: 734: WalRcvData *walrcv = WalRcv;
-: 735:
-: 736: /* Ensure that all WAL records received are flushed to disk */
96: 737: XLogWalRcvFlush(true);
call 0 returned 100%
-: 738:
-: 739: /* Mark ourselves inactive in shared memory */
96: 740: SpinLockAcquire(&walrcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
96: 741: Assert(walrcv->walRcvState == WALRCV_STREAMING ||
branch 0 taken 13% (fallthrough)
branch 1 taken 88%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 100% (fallthrough)
branch 5 taken 0%
branch 6 taken 100% (fallthrough)
branch 7 taken 0%
branch 8 taken 0% (fallthrough)
branch 9 taken 100%
call 10 never executed
-: 742: walrcv->walRcvState == WALRCV_RESTARTING ||
-: 743: walrcv->walRcvState == WALRCV_STARTING ||
-: 744: walrcv->walRcvState == WALRCV_WAITING ||
-: 745: walrcv->walRcvState == WALRCV_STOPPING);
96: 746: Assert(walrcv->pid == MyProcPid);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
96: 747: walrcv->walRcvState = WALRCV_STOPPED;
96: 748: walrcv->pid = 0;
96: 749: walrcv->ready_to_display = false;
96: 750: walrcv->latch = NULL;
96: 751: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
-: 752:
-: 753: /* Terminate the connection gracefully. */
96: 754: if (wrconn != NULL)
branch 0 taken 53% (fallthrough)
branch 1 taken 47%
51: 755: walrcv_disconnect(wrconn);
call 0 returned 100%
-: 756:
-: 757: /* Wake up the startup process to notice promptly that we're gone */
96: 758: WakeupRecovery();
call 0 returned 100%
96: 759:}
-: 760:
-: 761:/* SIGHUP: set flag to re-read config file at next convenient time */
-: 762:static void
function WalRcvSigHupHandler called 5 returned 100% blocks executed 100%
5: 763:WalRcvSigHupHandler(SIGNAL_ARGS)
-: 764:{
5: 765: got_SIGHUP = true;
5: 766:}
-: 767:
-: 768:
-: 769:/* SIGUSR1: used by latch mechanism */
-: 770:static void
function WalRcvSigUsr1Handler called 196 returned 100% blocks executed 100%
196: 771:WalRcvSigUsr1Handler(SIGNAL_ARGS)
-: 772:{
196: 773: int save_errno = errno;
call 0 returned 100%
-: 774:
196: 775: latch_sigusr1_handler();
call 0 returned 100%
-: 776:
196: 777: errno = save_errno;
call 0 returned 100%
196: 778:}
-: 779:
-: 780:/* SIGTERM: set flag for ProcessWalRcvInterrupts */
-: 781:static void
function WalRcvShutdownHandler called 20 returned 100% blocks executed 100%
20: 782:WalRcvShutdownHandler(SIGNAL_ARGS)
-: 783:{
20: 784: int save_errno = errno;
call 0 returned 100%
-: 785:
20: 786: got_SIGTERM = true;
-: 787:
20: 788: if (WalRcv->latch)
branch 0 taken 90% (fallthrough)
branch 1 taken 10%
18: 789: SetLatch(WalRcv->latch);
call 0 returned 100%
-: 790:
20: 791: errno = save_errno;
call 0 returned 100%
20: 792:}
-: 793:
-: 794:/*
-: 795: * WalRcvQuickDieHandler() occurs when signalled SIGQUIT by the postmaster.
-: 796: *
-: 797: * Some backend has bought the farm, so we need to stop what we're doing and
-: 798: * exit.
-: 799: */
-: 800:static void
function WalRcvQuickDieHandler called 0 returned 0% blocks executed 0%
#####: 801:WalRcvQuickDieHandler(SIGNAL_ARGS)
-: 802:{
-: 803: /*
-: 804: * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
-: 805: * because shared memory may be corrupted, so we don't want to try to
-: 806: * clean up our transaction. Just nail the windows shut and get out of
-: 807: * town. The callbacks wouldn't be safe to run from a signal handler,
-: 808: * anyway.
-: 809: *
-: 810: * Note we use _exit(2) not _exit(0). This is to force the postmaster
-: 811: * into a system reset cycle if someone sends a manual SIGQUIT to a random
-: 812: * backend. This is necessary precisely because we don't clean up our
-: 813: * shared memory state. (The "dead man switch" mechanism in pmsignal.c
-: 814: * should ensure the postmaster sees this as a crash, too, but no harm in
-: 815: * being doubly sure.)
-: 816: */
#####: 817: _exit(2);
-: 818:}
-: 819:
-: 820:/*
-: 821: * Accept the message from XLOG stream, and process it.
-: 822: */
-: 823:static void
function XLogWalRcvProcessMsg called 309 returned 100% blocks executed 27%
309: 824:XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len)
-: 825:{
-: 826: int hdrlen;
-: 827: XLogRecPtr dataStart;
-: 828: XLogRecPtr walEnd;
-: 829: TimestampTz sendTime;
-: 830: bool replyRequested;
-: 831:
309: 832: resetStringInfo(&incoming_message);
call 0 returned 100%
-: 833:
309: 834: switch (type)
branch 0 taken 100%
branch 1 taken 0%
branch 2 taken 0%
-: 835: {
-: 836: case 'w': /* WAL records */
-: 837: {
-: 838: /* copy message to StringInfo */
309: 839: hdrlen = sizeof(int64) + sizeof(int64) + sizeof(int64);
309: 840: if (len < hdrlen)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 841: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 842: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 843: errmsg_internal("invalid WAL message received from primary")));
309: 844: appendBinaryStringInfo(&incoming_message, buf, hdrlen);
call 0 returned 100%
-: 845:
-: 846: /* read the fields */
309: 847: dataStart = pq_getmsgint64(&incoming_message);
call 0 returned 100%
309: 848: walEnd = pq_getmsgint64(&incoming_message);
call 0 returned 100%
309: 849: sendTime = pq_getmsgint64(&incoming_message);
call 0 returned 100%
309: 850: ProcessWalSndrMessage(walEnd, sendTime);
call 0 returned 100%
-: 851:
309: 852: buf += hdrlen;
309: 853: len -= hdrlen;
309: 854: XLogWalRcvWrite(buf, len, dataStart);
call 0 returned 100%
309: 855: break;
-: 856: }
-: 857: case 'k': /* Keepalive */
-: 858: {
-: 859: /* copy message to StringInfo */
#####: 860: hdrlen = sizeof(int64) + sizeof(int64) + sizeof(char);
#####: 861: if (len != hdrlen)
branch 0 never executed
branch 1 never executed
#####: 862: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 863: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 864: errmsg_internal("invalid keepalive message received from primary")));
#####: 865: appendBinaryStringInfo(&incoming_message, buf, hdrlen);
call 0 never executed
-: 866:
-: 867: /* read the fields */
#####: 868: walEnd = pq_getmsgint64(&incoming_message);
call 0 never executed
#####: 869: sendTime = pq_getmsgint64(&incoming_message);
call 0 never executed
#####: 870: replyRequested = pq_getmsgbyte(&incoming_message);
call 0 never executed
-: 871:
#####: 872: ProcessWalSndrMessage(walEnd, sendTime);
call 0 never executed
-: 873:
-: 874: /* If the primary requested a reply, send one immediately */
#####: 875: if (replyRequested)
branch 0 never executed
branch 1 never executed
#####: 876: XLogWalRcvSendReply(true, false);
call 0 never executed
#####: 877: break;
-: 878: }
-: 879: default:
#####: 880: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 881: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 882: errmsg_internal("invalid replication message type %d",
-: 883: type)));
-: 884: }
309: 885:}
-: 886:
-: 887:/*
-: 888: * Write XLOG data to disk.
-: 889: */
-: 890:static void
function XLogWalRcvWrite called 309 returned 100% blocks executed 27%
309: 891:XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr)
-: 892:{
-: 893: int startoff;
-: 894: int byteswritten;
-: 895:
927: 896: while (nbytes > 0)
branch 0 taken 50%
branch 1 taken 50% (fallthrough)
-: 897: {
-: 898: int segbytes;
-: 899:
309: 900: if (recvFile < 0 || !XLByteInSeg(recptr, recvSegNo, wal_segment_size))
branch 0 taken 86% (fallthrough)
branch 1 taken 14%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
-: 901: {
-: 902: bool use_existent;
-: 903:
-: 904: /*
-: 905: * fsync() and close current file before we switch to next one. We
-: 906: * would otherwise have to reopen this file to fsync it later
-: 907: */
44: 908: if (recvFile >= 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 909: {
-: 910: char xlogfname[MAXFNAMELEN];
-: 911:
#####: 912: XLogWalRcvFlush(false);
call 0 never executed
-: 913:
-: 914: /*
-: 915: * XLOG segment files will be re-read by recovery in startup
-: 916: * process soon, so we don't advise the OS to release cache
-: 917: * pages associated with the file like XLogFileClose() does.
-: 918: */
#####: 919: if (close(recvFile) != 0)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 920: ereport(PANIC,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 921: (errcode_for_file_access(),
-: 922: errmsg("could not close log segment %s: %m",
-: 923: XLogFileNameP(recvFileTLI, recvSegNo))));
-: 924:
-: 925: /*
-: 926: * Create .done file forcibly to prevent the streamed segment
-: 927: * from being archived later.
-: 928: */
#####: 929: XLogFileName(xlogfname, recvFileTLI, recvSegNo, wal_segment_size);
call 0 never executed
#####: 930: if (XLogArchiveMode != ARCHIVE_MODE_ALWAYS)
branch 0 never executed
branch 1 never executed
#####: 931: XLogArchiveForceDone(xlogfname);
call 0 never executed
-: 932: else
#####: 933: XLogArchiveNotify(xlogfname);
call 0 never executed
-: 934: }
44: 935: recvFile = -1;
-: 936:
-: 937: /* Create/use new log file */
44: 938: XLByteToSeg(recptr, recvSegNo, wal_segment_size);
44: 939: use_existent = true;
44: 940: recvFile = XLogFileInit(recvSegNo, &use_existent, true);
call 0 returned 100%
44: 941: recvFileTLI = ThisTimeLineID;
44: 942: recvOff = 0;
-: 943: }
-: 944:
-: 945: /* Calculate the start offset of the received logs */
309: 946: startoff = XLogSegmentOffset(recptr, wal_segment_size);
-: 947:
309: 948: if (startoff + nbytes > wal_segment_size)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 949: segbytes = wal_segment_size - startoff;
-: 950: else
309: 951: segbytes = nbytes;
-: 952:
-: 953: /* Need to seek in the file? */
309: 954: if (recvOff != startoff)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 955: {
#####: 956: if (lseek(recvFile, (off_t) startoff, SEEK_SET) < 0)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 957: ereport(PANIC,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 958: (errcode_for_file_access(),
-: 959: errmsg("could not seek in log segment %s to offset %u: %m",
-: 960: XLogFileNameP(recvFileTLI, recvSegNo),
-: 961: startoff)));
#####: 962: recvOff = startoff;
-: 963: }
-: 964:
-: 965: /* OK to write the logs */
309: 966: errno = 0;
call 0 returned 100%
-: 967:
309: 968: byteswritten = write(recvFile, buf, segbytes);
call 0 returned 100%
309: 969: if (byteswritten <= 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 970: {
-: 971: /* if write didn't set errno, assume no disk space */
#####: 972: if (errno == 0)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 973: errno = ENOSPC;
call 0 never executed
#####: 974: ereport(PANIC,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 975: (errcode_for_file_access(),
-: 976: errmsg("could not write to log segment %s "
-: 977: "at offset %u, length %lu: %m",
-: 978: XLogFileNameP(recvFileTLI, recvSegNo),
-: 979: recvOff, (unsigned long) segbytes)));
-: 980: }
-: 981:
-: 982: /* Update state for write */
309: 983: recptr += byteswritten;
-: 984:
309: 985: recvOff += byteswritten;
309: 986: nbytes -= byteswritten;
309: 987: buf += byteswritten;
-: 988:
309: 989: LogstreamResult.Write = recptr;
-: 990: }
309: 991:}
-: 992:
-: 993:/*
-: 994: * Flush the log to disk.
-: 995: *
-: 996: * If we're in the midst of dying, it's unwise to do anything that might throw
-: 997: * an error, so we skip sending a reply in that case.
-: 998: */
-: 999:static void
function XLogWalRcvFlush called 363 returned 100% blocks executed 95%
363: 1000:XLogWalRcvFlush(bool dying)
-: 1001:{
363: 1002: if (LogstreamResult.Flush < LogstreamResult.Write)
branch 0 taken 65% (fallthrough)
branch 1 taken 35%
-: 1003: {
235: 1004: WalRcvData *walrcv = WalRcv;
-: 1005:
235: 1006: issue_xlog_fsync(recvFile, recvSegNo);
call 0 returned 100%
-: 1007:
235: 1008: LogstreamResult.Flush = LogstreamResult.Write;
-: 1009:
-: 1010: /* Update shared-memory status */
235: 1011: SpinLockAcquire(&walrcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
235: 1012: if (walrcv->receivedUpto < LogstreamResult.Flush)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1013: {
235: 1014: walrcv->latestChunkStart = walrcv->receivedUpto;
235: 1015: walrcv->receivedUpto = LogstreamResult.Flush;
235: 1016: walrcv->receivedTLI = ThisTimeLineID;
-: 1017: }
235: 1018: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
-: 1019:
-: 1020: /* Signal the startup process and walsender that new WAL has arrived */
235: 1021: WakeupRecovery();
call 0 returned 100%
235: 1022: if (AllowCascadeReplication())
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
235: 1023: WalSndWakeup();
call 0 returned 100%
-: 1024:
-: 1025: /* Report XLOG streaming progress in PS display */
235: 1026: if (update_process_title)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1027: {
-: 1028: char activitymsg[50];
-: 1029:
470: 1030: snprintf(activitymsg, sizeof(activitymsg), "streaming %X/%X",
call 0 returned 100%
235: 1031: (uint32) (LogstreamResult.Write >> 32),
235: 1032: (uint32) LogstreamResult.Write);
235: 1033: set_ps_display(activitymsg, false);
call 0 returned 100%
-: 1034: }
-: 1035:
-: 1036: /* Also let the master know that we made some progress */
235: 1037: if (!dying)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1038: {
235: 1039: XLogWalRcvSendReply(false, false);
call 0 returned 100%
235: 1040: XLogWalRcvSendHSFeedback(false);
call 0 returned 100%
-: 1041: }
-: 1042: }
363: 1043:}
-: 1044:
-: 1045:/*
-: 1046: * Send reply message to primary, indicating our current WAL locations, oldest
-: 1047: * xmin and the current time.
-: 1048: *
-: 1049: * If 'force' is not set, the message is only sent if enough time has
-: 1050: * passed since last status update to reach wal_receiver_status_interval.
-: 1051: * If wal_receiver_status_interval is disabled altogether and 'force' is
-: 1052: * false, this is a no-op.
-: 1053: *
-: 1054: * If 'requestReply' is true, requests the server to reply immediately upon
-: 1055: * receiving this message. This is used for heartbeats, when approaching
-: 1056: * wal_receiver_timeout.
-: 1057: */
-: 1058:static void
function XLogWalRcvSendReply called 1027 returned 100% blocks executed 92%
1027: 1059:XLogWalRcvSendReply(bool force, bool requestReply)
-: 1060:{
-: 1061: static XLogRecPtr writePtr = 0;
-: 1062: static XLogRecPtr flushPtr = 0;
-: 1063: XLogRecPtr applyPtr;
-: 1064: static TimestampTz sendTime = 0;
-: 1065: TimestampTz now;
-: 1066:
-: 1067: /*
-: 1068: * If the user doesn't want status to be reported to the master, be sure
-: 1069: * to exit before doing anything at all.
-: 1070: */
1027: 1071: if (!force && wal_receiver_status_interval <= 0)
branch 0 taken 81% (fallthrough)
branch 1 taken 19%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 1072: return;
-: 1073:
-: 1074: /* Get current timestamp. */
1027: 1075: now = GetCurrentTimestamp();
call 0 returned 100%
-: 1076:
-: 1077: /*
-: 1078: * We can compare the write and flush positions to the last message we
-: 1079: * sent without taking any lock, but the apply position requires a spin
-: 1080: * lock, so we don't check that unless something else has changed or 10
-: 1081: * seconds have passed. This means that the apply WAL location will
-: 1082: * appear, from the master's point of view, to lag slightly, but since
-: 1083: * this is only for reporting purposes and only on idle systems, that's
-: 1084: * probably OK.
-: 1085: */
1027: 1086: if (!force
branch 0 taken 81% (fallthrough)
branch 1 taken 19%
837: 1087: && writePtr == LogstreamResult.Write
branch 0 taken 68% (fallthrough)
branch 1 taken 32%
572: 1088: && flushPtr == LogstreamResult.Flush
branch 0 taken 59% (fallthrough)
branch 1 taken 41%
337: 1089: && !TimestampDifferenceExceeds(sendTime, now,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
-: 1090: wal_receiver_status_interval * 1000))
337: 1091: return;
690: 1092: sendTime = now;
-: 1093:
-: 1094: /* Construct a new message */
690: 1095: writePtr = LogstreamResult.Write;
690: 1096: flushPtr = LogstreamResult.Flush;
690: 1097: applyPtr = GetXLogReplayRecPtr(NULL);
call 0 returned 100%
-: 1098:
690: 1099: resetStringInfo(&reply_message);
call 0 returned 100%
690: 1100: pq_sendbyte(&reply_message, 'r');
call 0 returned 100%
690: 1101: pq_sendint64(&reply_message, writePtr);
call 0 returned 100%
690: 1102: pq_sendint64(&reply_message, flushPtr);
call 0 returned 100%
690: 1103: pq_sendint64(&reply_message, applyPtr);
call 0 returned 100%
690: 1104: pq_sendint64(&reply_message, GetCurrentTimestamp());
call 0 returned 100%
call 1 returned 100%
690: 1105: pq_sendbyte(&reply_message, requestReply ? 1 : 0);
call 0 returned 100%
-: 1106:
-: 1107: /* Send it */
690: 1108: elog(DEBUG2, "sending write %X/%X flush %X/%X apply %X/%X%s",
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 returned 100%
-: 1109: (uint32) (writePtr >> 32), (uint32) writePtr,
-: 1110: (uint32) (flushPtr >> 32), (uint32) flushPtr,
-: 1111: (uint32) (applyPtr >> 32), (uint32) applyPtr,
-: 1112: requestReply ? " (reply requested)" : "");
-: 1113:
690: 1114: walrcv_send(wrconn, reply_message.data, reply_message.len);
call 0 returned 100%
-: 1115:}
-: 1116:
-: 1117:/*
-: 1118: * Send hot standby feedback message to primary, plus the current time,
-: 1119: * in case they don't have a watch.
-: 1120: *
-: 1121: * If the user disables feedback, send one final message to tell sender
-: 1122: * to forget about the xmin on this standby. We also send this message
-: 1123: * on first connect because a previous connection might have set xmin
-: 1124: * on a replication slot. (If we're not using a slot it's harmless to
-: 1125: * send a feedback message explicitly setting InvalidTransactionId).
-: 1126: */
-: 1127:static void
function XLogWalRcvSendHSFeedback called 581 returned 100% blocks executed 91%
581: 1128:XLogWalRcvSendHSFeedback(bool immed)
-: 1129:{
-: 1130: TimestampTz now;
-: 1131: FullTransactionId nextFullXid;
-: 1132: TransactionId nextXid;
-: 1133: uint32 xmin_epoch,
-: 1134: catalog_xmin_epoch;
-: 1135: TransactionId xmin,
-: 1136: catalog_xmin;
-: 1137: static TimestampTz sendTime = 0;
-: 1138:
-: 1139: /* initially true so we always send at least one feedback message */
-: 1140: static bool master_has_standby_xmin = true;
-: 1141:
-: 1142: /*
-: 1143: * If the user doesn't want status to be reported to the master, be sure
-: 1144: * to exit before doing anything at all.
-: 1145: */
1113: 1146: if ((wal_receiver_status_interval <= 0 || !hot_standby_feedback) &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 92% (fallthrough)
branch 3 taken 8%
branch 4 taken 90% (fallthrough)
branch 5 taken 10%
532: 1147: !master_has_standby_xmin)
1005: 1148: return;
-: 1149:
-: 1150: /* Get current timestamp. */
100: 1151: now = GetCurrentTimestamp();
call 0 returned 100%
-: 1152:
100: 1153: if (!immed)
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
-: 1154: {
-: 1155: /*
-: 1156: * Send feedback at most once per wal_receiver_status_interval.
-: 1157: */
95: 1158: if (!TimestampDifferenceExceeds(sendTime, now,
call 0 returned 100%
branch 1 taken 45% (fallthrough)
branch 2 taken 55%
-: 1159: wal_receiver_status_interval * 1000))
43: 1160: return;
52: 1161: sendTime = now;
-: 1162: }
-: 1163:
-: 1164: /*
-: 1165: * If Hot Standby is not yet accepting connections there is nothing to
-: 1166: * send. Check this after the interval has expired to reduce number of
-: 1167: * calls.
-: 1168: *
-: 1169: * Bailing out here also ensures that we don't send feedback until we've
-: 1170: * read our own replication slot state, so we don't tell the master to
-: 1171: * discard needed xmin or catalog_xmin from any slots that may exist on
-: 1172: * this replica.
-: 1173: */
57: 1174: if (!HotStandbyActive())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 1175: return;
-: 1176:
-: 1177: /*
-: 1178: * Make the expensive call to get the oldest xmin once we are certain
-: 1179: * everything else has been checked.
-: 1180: */
57: 1181: if (hot_standby_feedback)
branch 0 taken 11% (fallthrough)
branch 1 taken 89%
-: 1182: {
-: 1183: TransactionId slot_xmin;
-: 1184:
-: 1185: /*
-: 1186: * Usually GetOldestXmin() would include both global replication slot
-: 1187: * xmin and catalog_xmin in its calculations, but we want to derive
-: 1188: * separate values for each of those. So we ask for an xmin that
-: 1189: * excludes the catalog_xmin.
-: 1190: */
6: 1191: xmin = GetOldestXmin(NULL,
call 0 returned 100%
-: 1192: PROCARRAY_FLAGS_DEFAULT | PROCARRAY_SLOTS_XMIN);
-: 1193:
6: 1194: ProcArrayGetReplicationSlotXmin(&slot_xmin, &catalog_xmin);
call 0 returned 100%
-: 1195:
7: 1196: if (TransactionIdIsValid(slot_xmin) &&
branch 0 taken 17% (fallthrough)
branch 1 taken 83%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
1: 1197: TransactionIdPrecedes(slot_xmin, xmin))
call 0 returned 100%
#####: 1198: xmin = slot_xmin;
-: 1199: }
-: 1200: else
-: 1201: {
51: 1202: xmin = InvalidTransactionId;
51: 1203: catalog_xmin = InvalidTransactionId;
-: 1204: }
-: 1205:
-: 1206: /*
-: 1207: * Get epoch and adjust if nextXid and oldestXmin are different sides of
-: 1208: * the epoch boundary.
-: 1209: */
57: 1210: nextFullXid = ReadNextFullTransactionId();
call 0 returned 100%
57: 1211: nextXid = XidFromFullTransactionId(nextFullXid);
57: 1212: xmin_epoch = EpochFromFullTransactionId(nextFullXid);
57: 1213: catalog_xmin_epoch = xmin_epoch;
57: 1214: if (nextXid < xmin)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1215: xmin_epoch--;
57: 1216: if (nextXid < catalog_xmin)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1217: catalog_xmin_epoch--;
-: 1218:
57: 1219: elog(DEBUG2, "sending hot standby feedback xmin %u epoch %u catalog_xmin %u catalog_xmin_epoch %u",
call 0 returned 100%
call 1 returned 100%
-: 1220: xmin, xmin_epoch, catalog_xmin, catalog_xmin_epoch);
-: 1221:
-: 1222: /* Construct the message and send it. */
57: 1223: resetStringInfo(&reply_message);
call 0 returned 100%
57: 1224: pq_sendbyte(&reply_message, 'h');
call 0 returned 100%
57: 1225: pq_sendint64(&reply_message, GetCurrentTimestamp());
call 0 returned 100%
call 1 returned 100%
57: 1226: pq_sendint32(&reply_message, xmin);
call 0 returned 100%
57: 1227: pq_sendint32(&reply_message, xmin_epoch);
call 0 returned 100%
57: 1228: pq_sendint32(&reply_message, catalog_xmin);
call 0 returned 100%
57: 1229: pq_sendint32(&reply_message, catalog_xmin_epoch);
call 0 returned 100%
57: 1230: walrcv_send(wrconn, reply_message.data, reply_message.len);
call 0 returned 100%
57: 1231: if (TransactionIdIsValid(xmin) || TransactionIdIsValid(catalog_xmin))
branch 0 taken 89% (fallthrough)
branch 1 taken 11%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
6: 1232: master_has_standby_xmin = true;
-: 1233: else
51: 1234: master_has_standby_xmin = false;
-: 1235:}
-: 1236:
-: 1237:/*
-: 1238: * Update shared memory status upon receiving a message from primary.
-: 1239: *
-: 1240: * 'walEnd' and 'sendTime' are the end-of-WAL and timestamp of the latest
-: 1241: * message, reported by primary.
-: 1242: */
-: 1243:static void
function ProcessWalSndrMessage called 309 returned 100% blocks executed 80%
309: 1244:ProcessWalSndrMessage(XLogRecPtr walEnd, TimestampTz sendTime)
-: 1245:{
309: 1246: WalRcvData *walrcv = WalRcv;
-: 1247:
309: 1248: TimestampTz lastMsgReceiptTime = GetCurrentTimestamp();
call 0 returned 100%
-: 1249:
-: 1250: /* Update shared-memory status */
309: 1251: SpinLockAcquire(&walrcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
309: 1252: if (walrcv->latestWalEnd < walEnd)
branch 0 taken 78% (fallthrough)
branch 1 taken 22%
241: 1253: walrcv->latestWalEndTime = sendTime;
309: 1254: walrcv->latestWalEnd = walEnd;
309: 1255: walrcv->lastMsgSendTime = sendTime;
309: 1256: walrcv->lastMsgReceiptTime = lastMsgReceiptTime;
309: 1257: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
-: 1258:
309: 1259: if (log_min_messages <= DEBUG2)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
-: 1260: {
-: 1261: char *sendtime;
-: 1262: char *receipttime;
-: 1263: int applyDelay;
-: 1264:
-: 1265: /* Copy because timestamptz_to_str returns a static buffer */
8: 1266: sendtime = pstrdup(timestamptz_to_str(sendTime));
call 0 returned 100%
call 1 returned 100%
8: 1267: receipttime = pstrdup(timestamptz_to_str(lastMsgReceiptTime));
call 0 returned 100%
call 1 returned 100%
8: 1268: applyDelay = GetReplicationApplyDelay();
call 0 returned 100%
-: 1269:
-: 1270: /* apply delay is not available */
8: 1271: if (applyDelay == -1)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1272: elog(DEBUG2, "sendtime %s receipttime %s replication apply delay (N/A) transfer latency %d ms",
call 0 never executed
call 1 never executed
call 2 never executed
-: 1273: sendtime,
-: 1274: receipttime,
-: 1275: GetReplicationTransferLatency());
-: 1276: else
8: 1277: elog(DEBUG2, "sendtime %s receipttime %s replication apply delay %d ms transfer latency %d ms",
call 0 returned 100%
call 1 returned 100%
call 2 returned 100%
-: 1278: sendtime,
-: 1279: receipttime,
-: 1280: applyDelay,
-: 1281: GetReplicationTransferLatency());
-: 1282:
8: 1283: pfree(sendtime);
call 0 returned 100%
8: 1284: pfree(receipttime);
call 0 returned 100%
-: 1285: }
309: 1286:}
-: 1287:
-: 1288:/*
-: 1289: * Wake up the walreceiver main loop.
-: 1290: *
-: 1291: * This is called by the startup process whenever interesting xlog records
-: 1292: * are applied, so that walreceiver can check if it needs to send an apply
-: 1293: * notification back to the master which may be waiting in a COMMIT with
-: 1294: * synchronous_commit = remote_apply.
-: 1295: */
-: 1296:void
function WalRcvForceReply called 200 returned 100% blocks executed 88%
200: 1297:WalRcvForceReply(void)
-: 1298:{
-: 1299: Latch *latch;
-: 1300:
200: 1301: WalRcv->force_reply = true;
-: 1302: /* fetching the latch pointer might not be atomic, so use spinlock */
200: 1303: SpinLockAcquire(&WalRcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
200: 1304: latch = WalRcv->latch;
200: 1305: SpinLockRelease(&WalRcv->mutex);
call 0 returned 100%
200: 1306: if (latch)
branch 0 taken 70% (fallthrough)
branch 1 taken 31%
139: 1307: SetLatch(latch);
call 0 returned 100%
200: 1308:}
-: 1309:
-: 1310:/*
-: 1311: * Return a string constant representing the state. This is used
-: 1312: * in system functions and views, and should *not* be translated.
-: 1313: */
-: 1314:static const char *
function WalRcvGetStateString called 0 returned 0% blocks executed 0%
#####: 1315:WalRcvGetStateString(WalRcvState state)
-: 1316:{
#####: 1317: switch (state)
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
branch 4 never executed
branch 5 never executed
branch 6 never executed
-: 1318: {
-: 1319: case WALRCV_STOPPED:
#####: 1320: return "stopped";
-: 1321: case WALRCV_STARTING:
#####: 1322: return "starting";
-: 1323: case WALRCV_STREAMING:
#####: 1324: return "streaming";
-: 1325: case WALRCV_WAITING:
#####: 1326: return "waiting";
-: 1327: case WALRCV_RESTARTING:
#####: 1328: return "restarting";
-: 1329: case WALRCV_STOPPING:
#####: 1330: return "stopping";
-: 1331: }
#####: 1332: return "UNKNOWN";
-: 1333:}
-: 1334:
-: 1335:/*
-: 1336: * Returns activity of WAL receiver, including pid, state and xlog locations
-: 1337: * received from the WAL sender of another server.
-: 1338: */
-: 1339:Datum
function pg_stat_get_wal_receiver called 0 returned 0% blocks executed 0%
#####: 1340:pg_stat_get_wal_receiver(PG_FUNCTION_ARGS)
-: 1341:{
-: 1342: TupleDesc tupdesc;
-: 1343: Datum *values;
-: 1344: bool *nulls;
-: 1345: int pid;
-: 1346: bool ready_to_display;
-: 1347: WalRcvState state;
-: 1348: XLogRecPtr receive_start_lsn;
-: 1349: TimeLineID receive_start_tli;
-: 1350: XLogRecPtr received_lsn;
-: 1351: TimeLineID received_tli;
-: 1352: TimestampTz last_send_time;
-: 1353: TimestampTz last_receipt_time;
-: 1354: XLogRecPtr latest_end_lsn;
-: 1355: TimestampTz latest_end_time;
-: 1356: char sender_host[NI_MAXHOST];
#####: 1357: int sender_port = 0;
-: 1358: char slotname[NAMEDATALEN];
-: 1359: char conninfo[MAXCONNINFO];
-: 1360:
-: 1361: /* Take a lock to ensure value consistency */
#####: 1362: SpinLockAcquire(&WalRcv->mutex);
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
#####: 1363: pid = (int) WalRcv->pid;
#####: 1364: ready_to_display = WalRcv->ready_to_display;
#####: 1365: state = WalRcv->walRcvState;
#####: 1366: receive_start_lsn = WalRcv->receiveStart;
#####: 1367: receive_start_tli = WalRcv->receiveStartTLI;
#####: 1368: received_lsn = WalRcv->receivedUpto;
#####: 1369: received_tli = WalRcv->receivedTLI;
#####: 1370: last_send_time = WalRcv->lastMsgSendTime;
#####: 1371: last_receipt_time = WalRcv->lastMsgReceiptTime;
#####: 1372: latest_end_lsn = WalRcv->latestWalEnd;
#####: 1373: latest_end_time = WalRcv->latestWalEndTime;
#####: 1374: strlcpy(slotname, (char *) WalRcv->slotname, sizeof(slotname));
call 0 never executed
#####: 1375: strlcpy(sender_host, (char *) WalRcv->sender_host, sizeof(sender_host));
call 0 never executed
#####: 1376: sender_port = WalRcv->sender_port;
#####: 1377: strlcpy(conninfo, (char *) WalRcv->conninfo, sizeof(conninfo));
call 0 never executed
#####: 1378: SpinLockRelease(&WalRcv->mutex);
call 0 never executed
-: 1379:
-: 1380: /*
-: 1381: * No WAL receiver (or not ready yet), just return a tuple with NULL
-: 1382: * values
-: 1383: */
#####: 1384: if (pid == 0 || !ready_to_display)
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
#####: 1385: PG_RETURN_NULL();
-: 1386:
-: 1387: /* determine result type */
#####: 1388: if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 1389: elog(ERROR, "return type must be a row type");
call 0 never executed
call 1 never executed
call 2 never executed
-: 1390:
#####: 1391: values = palloc0(sizeof(Datum) * tupdesc->natts);
call 0 never executed
#####: 1392: nulls = palloc0(sizeof(bool) * tupdesc->natts);
call 0 never executed
-: 1393:
-: 1394: /* Fetch values */
#####: 1395: values[0] = Int32GetDatum(pid);
-: 1396:
#####: 1397: if (!is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_STATS))
call 0 never executed
call 1 never executed
branch 2 never executed
branch 3 never executed
-: 1398: {
-: 1399: /*
-: 1400: * Only superusers and members of pg_read_all_stats can see details.
-: 1401: * Other users only get the pid value to know whether it is a WAL
-: 1402: * receiver, but no details.
-: 1403: */
#####: 1404: MemSet(&nulls[1], true, sizeof(bool) * (tupdesc->natts - 1));
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
branch 4 never executed
branch 5 never executed
branch 6 never executed
branch 7 never executed
branch 8 never executed
branch 9 never executed
-: 1405: }
-: 1406: else
-: 1407: {
#####: 1408: values[1] = CStringGetTextDatum(WalRcvGetStateString(state));
call 0 never executed
call 1 never executed
-: 1409:
#####: 1410: if (XLogRecPtrIsInvalid(receive_start_lsn))
branch 0 never executed
branch 1 never executed
#####: 1411: nulls[2] = true;
-: 1412: else
#####: 1413: values[2] = LSNGetDatum(receive_start_lsn);
#####: 1414: values[3] = Int32GetDatum(receive_start_tli);
#####: 1415: if (XLogRecPtrIsInvalid(received_lsn))
branch 0 never executed
branch 1 never executed
#####: 1416: nulls[4] = true;
-: 1417: else
#####: 1418: values[4] = LSNGetDatum(received_lsn);
#####: 1419: values[5] = Int32GetDatum(received_tli);
#####: 1420: if (last_send_time == 0)
branch 0 never executed
branch 1 never executed
#####: 1421: nulls[6] = true;
-: 1422: else
#####: 1423: values[6] = TimestampTzGetDatum(last_send_time);
#####: 1424: if (last_receipt_time == 0)
branch 0 never executed
branch 1 never executed
#####: 1425: nulls[7] = true;
-: 1426: else
#####: 1427: values[7] = TimestampTzGetDatum(last_receipt_time);
#####: 1428: if (XLogRecPtrIsInvalid(latest_end_lsn))
branch 0 never executed
branch 1 never executed
#####: 1429: nulls[8] = true;
-: 1430: else
#####: 1431: values[8] = LSNGetDatum(latest_end_lsn);
#####: 1432: if (latest_end_time == 0)
branch 0 never executed
branch 1 never executed
#####: 1433: nulls[9] = true;
-: 1434: else
#####: 1435: values[9] = TimestampTzGetDatum(latest_end_time);
#####: 1436: if (*slotname == '\0')
branch 0 never executed
branch 1 never executed
#####: 1437: nulls[10] = true;
-: 1438: else
#####: 1439: values[10] = CStringGetTextDatum(slotname);
call 0 never executed
#####: 1440: if (*sender_host == '\0')
branch 0 never executed
branch 1 never executed
#####: 1441: nulls[11] = true;
-: 1442: else
#####: 1443: values[11] = CStringGetTextDatum(sender_host);
call 0 never executed
#####: 1444: if (sender_port == 0)
branch 0 never executed
branch 1 never executed
#####: 1445: nulls[12] = true;
-: 1446: else
#####: 1447: values[12] = Int32GetDatum(sender_port);
#####: 1448: if (*conninfo == '\0')
branch 0 never executed
branch 1 never executed
#####: 1449: nulls[13] = true;
-: 1450: else
#####: 1451: values[13] = CStringGetTextDatum(conninfo);
call 0 never executed
-: 1452: }
-: 1453:
-: 1454: /* Returns the record as Datum */
#####: 1455: PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
call 0 never executed
call 1 never executed
-: 1456:}
0001-Add-logical_decoding_work_mem-to-limit-ReorderBuffer-patch_coverage/decode.c.gcov 0000664 0001750 0000000 00000157726 13560210457 030274 0 ustar vignesh root -: 0:Source:decode.c
-: 0:Graph:./decode.gcno
-: 0:Data:./decode.gcda
-: 0:Runs:6531
-: 0:Programs:1
-: 1:/* -------------------------------------------------------------------------
-: 2: *
-: 3: * decode.c
-: 4: * This module decodes WAL records read using xlogreader.h's APIs for the
-: 5: * purpose of logical decoding by passing information to the
-: 6: * reorderbuffer module (containing the actual changes) and to the
-: 7: * snapbuild module to build a fitting catalog snapshot (to be able to
-: 8: * properly decode the changes in the reorderbuffer).
-: 9: *
-: 10: * NOTE:
-: 11: * This basically tries to handle all low level xlog stuff for
-: 12: * reorderbuffer.c and snapbuild.c. There's some minor leakage where a
-: 13: * specific record's struct is used to pass data along, but those just
-: 14: * happen to contain the right amount of data in a convenient
-: 15: * format. There isn't and shouldn't be much intelligence about the
-: 16: * contents of records in here except turning them into a more usable
-: 17: * format.
-: 18: *
-: 19: * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
-: 20: * Portions Copyright (c) 1994, Regents of the University of California
-: 21: *
-: 22: * IDENTIFICATION
-: 23: * src/backend/replication/logical/decode.c
-: 24: *
-: 25: * -------------------------------------------------------------------------
-: 26: */
-: 27:#include "postgres.h"
-: 28:
-: 29:#include "access/heapam.h"
-: 30:#include "access/heapam_xlog.h"
-: 31:#include "access/transam.h"
-: 32:#include "access/xact.h"
-: 33:#include "access/xlog_internal.h"
-: 34:#include "access/xlogutils.h"
-: 35:#include "access/xlogreader.h"
-: 36:#include "access/xlogrecord.h"
-: 37:
-: 38:#include "catalog/pg_control.h"
-: 39:
-: 40:#include "replication/decode.h"
-: 41:#include "replication/logical.h"
-: 42:#include "replication/message.h"
-: 43:#include "replication/reorderbuffer.h"
-: 44:#include "replication/origin.h"
-: 45:#include "replication/snapbuild.h"
-: 46:
-: 47:#include "storage/standby.h"
-: 48:
-: 49:typedef struct XLogRecordBuffer
-: 50:{
-: 51: XLogRecPtr origptr;
-: 52: XLogRecPtr endptr;
-: 53: XLogReaderState *record;
-: 54:} XLogRecordBuffer;
-: 55:
-: 56:/* RMGR Handlers */
-: 57:static void DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 58:static void DecodeHeapOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 59:static void DecodeHeap2Op(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 60:static void DecodeXactOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 61:static void DecodeStandbyOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 62:static void DecodeLogicalMsgOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 63:
-: 64:/* individual record(group)'s handlers */
-: 65:static void DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 66:static void DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 67:static void DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 68:static void DecodeTruncate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 69:static void DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 70:static void DecodeSpecConfirm(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 71:
-: 72:static void DecodeCommit(LogicalDecodingContext *ctx, XLogRecordBuffer *buf,
-: 73: xl_xact_parsed_commit *parsed, TransactionId xid);
-: 74:static void DecodeAbort(LogicalDecodingContext *ctx, XLogRecordBuffer *buf,
-: 75: xl_xact_parsed_abort *parsed, TransactionId xid);
-: 76:
-: 77:/* common function to decode tuples */
-: 78:static void DecodeXLogTuple(char *data, Size len, ReorderBufferTupleBuf *tup);
-: 79:
-: 80:/*
-: 81: * Take every XLogReadRecord()ed record and perform the actions required to
-: 82: * decode it using the output plugin already setup in the logical decoding
-: 83: * context.
-: 84: *
-: 85: * NB: Note that every record's xid needs to be processed by reorderbuffer
-: 86: * (xids contained in the content of records are not relevant for this rule).
-: 87: * That means that for records which'd otherwise not go through the
-: 88: * reorderbuffer ReorderBufferProcessXid() has to be called. We don't want to
-: 89: * call ReorderBufferProcessXid for each record type by default, because
-: 90: * e.g. empty xacts can be handled more efficiently if there's no previous
-: 91: * state for them.
-: 92: *
-: 93: * We also support the ability to fast forward thru records, skipping some
-: 94: * record types completely - see individual record types for details.
-: 95: */
-: 96:void
function LogicalDecodingProcessRecord called 1595259 returned 100% blocks executed 84%
1595259: 97:LogicalDecodingProcessRecord(LogicalDecodingContext *ctx, XLogReaderState *record)
-: 98:{
-: 99: XLogRecordBuffer buf;
-: 100:
1595259: 101: buf.origptr = ctx->reader->ReadRecPtr;
1595259: 102: buf.endptr = ctx->reader->EndRecPtr;
1595259: 103: buf.record = record;
-: 104:
-: 105: /* cast so we get a warning when new rmgrs are added */
1595259: 106: switch ((RmgrIds) XLogRecGetRmid(record))
branch 0 taken 1%
branch 1 taken 1%
branch 2 taken 1%
branch 3 taken 1%
branch 4 taken 82%
branch 5 taken 1%
branch 6 taken 17%
branch 7 taken 0%
branch 8 taken 0%
-: 107: {
-: 108: /*
-: 109: * Rmgrs we care about for logical decoding. Add new rmgrs in
-: 110: * rmgrlist.h's order.
-: 111: */
-: 112: case RM_XLOG_ID:
1135: 113: DecodeXLogOp(ctx, &buf);
call 0 returned 100%
1135: 114: break;
-: 115:
-: 116: case RM_XACT_ID:
1396: 117: DecodeXactOp(ctx, &buf);
call 0 returned 100%
1396: 118: break;
-: 119:
-: 120: case RM_STANDBY_ID:
1447: 121: DecodeStandbyOp(ctx, &buf);
call 0 returned 100%
1447: 122: break;
-: 123:
-: 124: case RM_HEAP2_ID:
14068: 125: DecodeHeap2Op(ctx, &buf);
call 0 returned 100%
14068: 126: break;
-: 127:
-: 128: case RM_HEAP_ID:
1301408: 129: DecodeHeapOp(ctx, &buf);
call 0 returned 100%
1301408: 130: break;
-: 131:
-: 132: case RM_LOGICALMSG_ID:
32: 133: DecodeLogicalMsgOp(ctx, &buf);
call 0 returned 100%
32: 134: break;
-: 135:
-: 136: /*
-: 137: * Rmgrs irrelevant for logical decoding; they describe stuff not
-: 138: * represented in logical decoding. Add new rmgrs in rmgrlist.h's
-: 139: * order.
-: 140: */
-: 141: case RM_SMGR_ID:
-: 142: case RM_CLOG_ID:
-: 143: case RM_DBASE_ID:
-: 144: case RM_TBLSPC_ID:
-: 145: case RM_MULTIXACT_ID:
-: 146: case RM_RELMAP_ID:
-: 147: case RM_BTREE_ID:
-: 148: case RM_HASH_ID:
-: 149: case RM_GIN_ID:
-: 150: case RM_GIST_ID:
-: 151: case RM_SEQ_ID:
-: 152: case RM_SPGIST_ID:
-: 153: case RM_BRIN_ID:
-: 154: case RM_COMMIT_TS_ID:
-: 155: case RM_REPLORIGIN_ID:
-: 156: case RM_GENERIC_ID:
-: 157: /* just deal with xid, and done */
275773: 158: ReorderBufferProcessXid(ctx->reorder, XLogRecGetXid(record),
call 0 returned 100%
-: 159: buf.origptr);
275773: 160: break;
-: 161: case RM_NEXT_ID:
#####: 162: elog(ERROR, "unexpected RM_NEXT_ID rmgr_id: %u", (RmgrIds) XLogRecGetRmid(buf.record));
call 0 never executed
call 1 never executed
call 2 never executed
-: 163: }
1595259: 164:}
-: 165:
-: 166:/*
-: 167: * Handle rmgr XLOG_ID records for DecodeRecordIntoReorderBuffer().
-: 168: */
-: 169:static void
function DecodeXLogOp called 1135 returned 100% blocks executed 70%
1135: 170:DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 171:{
1135: 172: SnapBuild *builder = ctx->snapshot_builder;
1135: 173: uint8 info = XLogRecGetInfo(buf->record) & ~XLR_INFO_MASK;
-: 174:
1135: 175: ReorderBufferProcessXid(ctx->reorder, XLogRecGetXid(buf->record),
call 0 returned 100%
-: 176: buf->origptr);
-: 177:
1135: 178: switch (info)
branch 0 taken 1%
branch 1 taken 2%
branch 2 taken 97%
branch 3 taken 0%
-: 179: {
-: 180: /* this is also used in END_OF_RECOVERY checkpoints */
-: 181: case XLOG_CHECKPOINT_SHUTDOWN:
-: 182: case XLOG_END_OF_RECOVERY:
9: 183: SnapBuildSerializationPoint(builder, buf->origptr);
call 0 returned 100%
-: 184:
9: 185: break;
-: 186: case XLOG_CHECKPOINT_ONLINE:
-: 187:
-: 188: /*
-: 189: * a RUNNING_XACTS record will have been logged near to this, we
-: 190: * can restart from there.
-: 191: */
23: 192: break;
-: 193: case XLOG_NOOP:
-: 194: case XLOG_NEXTOID:
-: 195: case XLOG_SWITCH:
-: 196: case XLOG_BACKUP_END:
-: 197: case XLOG_PARAMETER_CHANGE:
-: 198: case XLOG_RESTORE_POINT:
-: 199: case XLOG_FPW_CHANGE:
-: 200: case XLOG_FPI_FOR_HINT:
-: 201: case XLOG_FPI:
1103: 202: break;
-: 203: default:
#####: 204: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info);
call 0 never executed
call 1 never executed
call 2 never executed
-: 205: }
1135: 206:}
-: 207:
-: 208:/*
-: 209: * Handle rmgr XACT_ID records for DecodeRecordIntoReorderBuffer().
-: 210: */
-: 211:static void
function DecodeXactOp called 1396 returned 100% blocks executed 90%
1396: 212:DecodeXactOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 213:{
1396: 214: SnapBuild *builder = ctx->snapshot_builder;
1396: 215: ReorderBuffer *reorder = ctx->reorder;
1396: 216: XLogReaderState *r = buf->record;
1396: 217: uint8 info = XLogRecGetInfo(r) & XLOG_XACT_OPMASK;
-: 218:
-: 219: /*
-: 220: * If the snapshot isn't yet fully built, we cannot decode anything, so
-: 221: * bail out.
-: 222: *
-: 223: * However, it's critical to process XLOG_XACT_ASSIGNMENT records even
-: 224: * when the snapshot is being built: it is possible to get later records
-: 225: * that require subxids to be properly assigned.
-: 226: */
1396: 227: if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT &&
call 0 returned 100%
branch 1 taken 1% (fallthrough)
branch 2 taken 99%
branch 3 taken 100% (fallthrough)
branch 4 taken 0%
-: 228: info != XLOG_XACT_ASSIGNMENT)
1401: 229: return;
-: 230:
1391: 231: switch (info)
branch 0 taken 90%
branch 1 taken 2%
branch 2 taken 8%
branch 3 taken 1%
branch 4 taken 0%
-: 232: {
-: 233: case XLOG_XACT_COMMIT:
-: 234: case XLOG_XACT_COMMIT_PREPARED:
-: 235: {
-: 236: xl_xact_commit *xlrec;
-: 237: xl_xact_parsed_commit parsed;
-: 238: TransactionId xid;
-: 239:
1256: 240: xlrec = (xl_xact_commit *) XLogRecGetData(r);
1256: 241: ParseCommitRecord(XLogRecGetInfo(buf->record), xlrec, &parsed);
call 0 returned 100%
-: 242:
1256: 243: if (!TransactionIdIsValid(parsed.twophase_xid))
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
1254: 244: xid = XLogRecGetXid(r);
-: 245: else
2: 246: xid = parsed.twophase_xid;
-: 247:
1256: 248: DecodeCommit(ctx, buf, &parsed, xid);
call 0 returned 100%
1256: 249: break;
-: 250: }
-: 251: case XLOG_XACT_ABORT:
-: 252: case XLOG_XACT_ABORT_PREPARED:
-: 253: {
-: 254: xl_xact_abort *xlrec;
-: 255: xl_xact_parsed_abort parsed;
-: 256: TransactionId xid;
-: 257:
23: 258: xlrec = (xl_xact_abort *) XLogRecGetData(r);
23: 259: ParseAbortRecord(XLogRecGetInfo(buf->record), xlrec, &parsed);
call 0 returned 100%
-: 260:
23: 261: if (!TransactionIdIsValid(parsed.twophase_xid))
branch 0 taken 96% (fallthrough)
branch 1 taken 4%
22: 262: xid = XLogRecGetXid(r);
-: 263: else
1: 264: xid = parsed.twophase_xid;
-: 265:
23: 266: DecodeAbort(ctx, buf, &parsed, xid);
call 0 returned 100%
23: 267: break;
-: 268: }
-: 269: case XLOG_XACT_ASSIGNMENT:
-: 270: {
-: 271: xl_xact_assignment *xlrec;
-: 272: int i;
-: 273: TransactionId *sub_xid;
-: 274:
109: 275: xlrec = (xl_xact_assignment *) XLogRecGetData(r);
-: 276:
109: 277: sub_xid = &xlrec->xsub[0];
-: 278:
722: 279: for (i = 0; i < xlrec->nsubxacts; i++)
branch 0 taken 85%
branch 1 taken 15% (fallthrough)
-: 280: {
1226: 281: ReorderBufferAssignChild(reorder, xlrec->xtop,
call 0 returned 100%
613: 282: *(sub_xid++), buf->origptr);
-: 283: }
109: 284: break;
-: 285: }
-: 286: case XLOG_XACT_PREPARE:
-: 287:
-: 288: /*
-: 289: * Currently decoding ignores PREPARE TRANSACTION and will just
-: 290: * decode the transaction when the COMMIT PREPARED is sent or
-: 291: * throw away the transaction's contents when a ROLLBACK PREPARED
-: 292: * is received. In the future we could add code to expose prepared
-: 293: * transactions in the changestream allowing for a kind of
-: 294: * distributed 2PC.
-: 295: */
3: 296: ReorderBufferProcessXid(reorder, XLogRecGetXid(r), buf->origptr);
call 0 returned 100%
3: 297: break;
-: 298: default:
#####: 299: elog(ERROR, "unexpected RM_XACT_ID record type: %u", info);
call 0 never executed
call 1 never executed
call 2 never executed
-: 300: }
-: 301:}
-: 302:
-: 303:/*
-: 304: * Handle rmgr STANDBY_ID records for DecodeRecordIntoReorderBuffer().
-: 305: */
-: 306:static void
function DecodeStandbyOp called 1447 returned 100% blocks executed 54%
1447: 307:DecodeStandbyOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 308:{
1447: 309: SnapBuild *builder = ctx->snapshot_builder;
1447: 310: XLogReaderState *r = buf->record;
1447: 311: uint8 info = XLogRecGetInfo(r) & ~XLR_INFO_MASK;
-: 312:
1447: 313: ReorderBufferProcessXid(ctx->reorder, XLogRecGetXid(r), buf->origptr);
call 0 returned 100%
-: 314:
1447: 315: switch (info)
branch 0 taken 24%
branch 1 taken 76%
branch 2 taken 0%
branch 3 taken 0%
-: 316: {
-: 317: case XLOG_RUNNING_XACTS:
-: 318: {
350: 319: xl_running_xacts *running = (xl_running_xacts *) XLogRecGetData(r);
-: 320:
350: 321: SnapBuildProcessRunningXacts(builder, buf->origptr, running);
call 0 returned 100%
-: 322:
-: 323: /*
-: 324: * Abort all transactions that we keep track of, that are
-: 325: * older than the record's oldestRunningXid. This is the most
-: 326: * convenient spot for doing so since, in contrast to shutdown
-: 327: * or end-of-recovery checkpoints, we have information about
-: 328: * all running transactions which includes prepared ones,
-: 329: * while shutdown checkpoints just know that no non-prepared
-: 330: * transactions are in progress.
-: 331: */
350: 332: ReorderBufferAbortOld(ctx->reorder, running->oldestRunningXid);
call 0 returned 100%
-: 333: }
350: 334: break;
-: 335: case XLOG_STANDBY_LOCK:
1097: 336: break;
-: 337: case XLOG_INVALIDATIONS:
-: 338: {
#####: 339: xl_invalidations *invalidations =
-: 340: (xl_invalidations *) XLogRecGetData(r);
-: 341:
#####: 342: if (!ctx->fast_forward)
branch 0 never executed
branch 1 never executed
#####: 343: ReorderBufferImmediateInvalidation(ctx->reorder,
call 0 never executed
#####: 344: invalidations->nmsgs,
#####: 345: invalidations->msgs);
-: 346: }
#####: 347: break;
-: 348: default:
#####: 349: elog(ERROR, "unexpected RM_STANDBY_ID record type: %u", info);
call 0 never executed
call 1 never executed
call 2 never executed
-: 350: }
1447: 351:}
-: 352:
-: 353:/*
-: 354: * Handle rmgr HEAP2_ID records for DecodeRecordIntoReorderBuffer().
-: 355: */
-: 356:static void
function DecodeHeap2Op called 14068 returned 100% blocks executed 84%
14068: 357:DecodeHeap2Op(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 358:{
14068: 359: uint8 info = XLogRecGetInfo(buf->record) & XLOG_HEAP_OPMASK;
14068: 360: TransactionId xid = XLogRecGetXid(buf->record);
14068: 361: SnapBuild *builder = ctx->snapshot_builder;
-: 362:
14068: 363: ReorderBufferProcessXid(ctx->reorder, xid, buf->origptr);
call 0 returned 100%
-: 364:
-: 365: /*
-: 366: * If we don't have snapshot or we are just fast-forwarding, there is no
-: 367: * point in decoding changes.
-: 368: */
28128: 369: if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT ||
call 0 returned 100%
branch 1 taken 99% (fallthrough)
branch 2 taken 1%
branch 3 taken 0% (fallthrough)
branch 4 taken 100%
14060: 370: ctx->fast_forward)
14076: 371: return;
-: 372:
14060: 373: switch (info)
branch 0 taken 1%
branch 1 taken 97%
branch 2 taken 1%
branch 3 taken 2%
branch 4 taken 0%
-: 374: {
-: 375: case XLOG_HEAP2_MULTI_INSERT:
16: 376: if (!ctx->fast_forward &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
8: 377: SnapBuildProcessChange(builder, xid, buf->origptr))
call 0 returned 100%
8: 378: DecodeMultiInsert(ctx, buf);
call 0 returned 100%
8: 379: break;
-: 380: case XLOG_HEAP2_NEW_CID:
-: 381: {
-: 382: xl_heap_new_cid *xlrec;
-: 383:
13630: 384: xlrec = (xl_heap_new_cid *) XLogRecGetData(buf->record);
13630: 385: SnapBuildProcessNewCid(builder, xid, buf->origptr, xlrec);
call 0 returned 100%
-: 386:
13630: 387: break;
-: 388: }
-: 389: case XLOG_HEAP2_REWRITE:
-: 390:
-: 391: /*
-: 392: * Although these records only exist to serve the needs of logical
-: 393: * decoding, all the work happens as part of crash or archive
-: 394: * recovery, so we don't need to do anything here.
-: 395: */
89: 396: break;
-: 397:
-: 398: /*
-: 399: * Everything else here is just low level physical stuff we're not
-: 400: * interested in.
-: 401: */
-: 402: case XLOG_HEAP2_FREEZE_PAGE:
-: 403: case XLOG_HEAP2_CLEAN:
-: 404: case XLOG_HEAP2_CLEANUP_INFO:
-: 405: case XLOG_HEAP2_VISIBLE:
-: 406: case XLOG_HEAP2_LOCK_UPDATED:
333: 407: break;
-: 408: default:
#####: 409: elog(ERROR, "unexpected RM_HEAP2_ID record type: %u", info);
call 0 never executed
call 1 never executed
call 2 never executed
-: 410: }
-: 411:}
-: 412:
-: 413:/*
-: 414: * Handle rmgr HEAP_ID records for DecodeRecordIntoReorderBuffer().
-: 415: */
-: 416:static void
function DecodeHeapOp called 1301408 returned 100% blocks executed 92%
1301408: 417:DecodeHeapOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 418:{
1301408: 419: uint8 info = XLogRecGetInfo(buf->record) & XLOG_HEAP_OPMASK;
1301408: 420: TransactionId xid = XLogRecGetXid(buf->record);
1301408: 421: SnapBuild *builder = ctx->snapshot_builder;
-: 422:
1301408: 423: ReorderBufferProcessXid(ctx->reorder, xid, buf->origptr);
call 0 returned 100%
-: 424:
-: 425: /*
-: 426: * If we don't have snapshot or we are just fast-forwarding, there is no
-: 427: * point in decoding data changes.
-: 428: */
2602812: 429: if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT ||
call 0 returned 100%
branch 1 taken 99% (fallthrough)
branch 2 taken 1%
branch 3 taken 1% (fallthrough)
branch 4 taken 99%
1301404: 430: ctx->fast_forward)
1301419: 431: return;
-: 432:
1301397: 433: switch (info)
branch 0 taken 74%
branch 1 taken 7%
branch 2 taken 9%
branch 3 taken 1%
branch 4 taken 1%
branch 5 taken 1%
branch 6 taken 8%
branch 7 taken 0%
-: 434: {
-: 435: case XLOG_HEAP_INSERT:
962824: 436: if (SnapBuildProcessChange(builder, xid, buf->origptr))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
962824: 437: DecodeInsert(ctx, buf);
call 0 returned 100%
962824: 438: break;
-: 439:
-: 440: /*
-: 441: * Treat HOT update as normal updates. There is no useful
-: 442: * information in the fact that we could make it a HOT update
-: 443: * locally and the WAL layout is compatible.
-: 444: */
-: 445: case XLOG_HEAP_HOT_UPDATE:
-: 446: case XLOG_HEAP_UPDATE:
87059: 447: if (SnapBuildProcessChange(builder, xid, buf->origptr))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
87059: 448: DecodeUpdate(ctx, buf);
call 0 returned 100%
87059: 449: break;
-: 450:
-: 451: case XLOG_HEAP_DELETE:
123546: 452: if (SnapBuildProcessChange(builder, xid, buf->origptr))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
123546: 453: DecodeDelete(ctx, buf);
call 0 returned 100%
123546: 454: break;
-: 455:
-: 456: case XLOG_HEAP_TRUNCATE:
8: 457: if (SnapBuildProcessChange(builder, xid, buf->origptr))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
8: 458: DecodeTruncate(ctx, buf);
call 0 returned 100%
8: 459: break;
-: 460:
-: 461: case XLOG_HEAP_INPLACE:
-: 462:
-: 463: /*
-: 464: * Inplace updates are only ever performed on catalog tuples and
-: 465: * can, per definition, not change tuple visibility. Since we
-: 466: * don't decode catalog tuples, we're not interested in the
-: 467: * record's contents.
-: 468: *
-: 469: * In-place updates can be used either by XID-bearing transactions
-: 470: * (e.g. in CREATE INDEX CONCURRENTLY) or by XID-less
-: 471: * transactions (e.g. VACUUM). In the former case, the commit
-: 472: * record will include cache invalidations, so we mark the
-: 473: * transaction as catalog modifying here. Currently that's
-: 474: * redundant because the commit will do that as well, but once we
-: 475: * support decoding in-progress relations, this will be important.
-: 476: */
601: 477: if (!TransactionIdIsValid(xid))
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
1: 478: break;
-: 479:
600: 480: SnapBuildProcessChange(builder, xid, buf->origptr);
call 0 returned 100%
600: 481: ReorderBufferXidSetCatalogChanges(ctx->reorder, xid, buf->origptr);
call 0 returned 100%
600: 482: break;
-: 483:
-: 484: case XLOG_HEAP_CONFIRM:
17900: 485: if (SnapBuildProcessChange(builder, xid, buf->origptr))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
17900: 486: DecodeSpecConfirm(ctx, buf);
call 0 returned 100%
17900: 487: break;
-: 488:
-: 489: case XLOG_HEAP_LOCK:
-: 490: /* we don't care about row level locks for now */
109459: 491: break;
-: 492:
-: 493: default:
#####: 494: elog(ERROR, "unexpected RM_HEAP_ID record type: %u", info);
call 0 never executed
call 1 never executed
call 2 never executed
-: 495: break;
-: 496: }
-: 497:}
-: 498:
-: 499:static inline bool
function FilterByOrigin called 1182743 returned 100% blocks executed 75%
1182743: 500:FilterByOrigin(LogicalDecodingContext *ctx, RepOriginId origin_id)
-: 501:{
1182743: 502: if (ctx->callbacks.filter_by_origin_cb == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 503: return false;
-: 504:
1182743: 505: return filter_by_origin_cb_wrapper(ctx, origin_id);
call 0 returned 100%
-: 506:}
-: 507:
-: 508:/*
-: 509: * Handle rmgr LOGICALMSG_ID records for DecodeRecordIntoReorderBuffer().
-: 510: */
-: 511:static void
function DecodeLogicalMsgOp called 32 returned 100% blocks executed 81%
32: 512:DecodeLogicalMsgOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 513:{
32: 514: SnapBuild *builder = ctx->snapshot_builder;
32: 515: XLogReaderState *r = buf->record;
32: 516: TransactionId xid = XLogRecGetXid(r);
32: 517: uint8 info = XLogRecGetInfo(r) & ~XLR_INFO_MASK;
32: 518: RepOriginId origin_id = XLogRecGetOrigin(r);
-: 519: Snapshot snapshot;
-: 520: xl_logical_message *message;
-: 521:
32: 522: if (info != XLOG_LOGICAL_MESSAGE)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 523: elog(ERROR, "unexpected RM_LOGICALMSG_ID record type: %u", info);
call 0 never executed
call 1 never executed
call 2 never executed
-: 524:
32: 525: ReorderBufferProcessXid(ctx->reorder, XLogRecGetXid(r), buf->origptr);
call 0 returned 100%
-: 526:
-: 527: /*
-: 528: * If we don't have snapshot or we are just fast-forwarding, there is no
-: 529: * point in decoding messages.
-: 530: */
64: 531: if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT ||
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
branch 3 taken 0% (fallthrough)
branch 4 taken 100%
32: 532: ctx->fast_forward)
#####: 533: return;
-: 534:
32: 535: message = (xl_logical_message *) XLogRecGetData(r);
-: 536:
62: 537: if (message->dbId != ctx->slot->data.database ||
branch 0 taken 94% (fallthrough)
branch 1 taken 6%
branch 2 taken 7% (fallthrough)
branch 3 taken 93%
30: 538: FilterByOrigin(ctx, origin_id))
call 0 returned 100%
4: 539: return;
-: 540:
50: 541: if (message->transactional &&
branch 0 taken 79% (fallthrough)
branch 1 taken 21%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
22: 542: !SnapBuildProcessChange(builder, xid, buf->origptr))
call 0 returned 100%
#####: 543: return;
34: 544: else if (!message->transactional &&
branch 0 taken 21% (fallthrough)
branch 1 taken 79%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
12: 545: (SnapBuildCurrentState(builder) != SNAPBUILD_CONSISTENT ||
call 0 returned 100%
branch 1 taken 50% (fallthrough)
branch 2 taken 50%
6: 546: SnapBuildXactNeedsSkip(builder, buf->origptr)))
call 0 returned 100%
3: 547: return;
-: 548:
25: 549: snapshot = SnapBuildGetOrBuildSnapshot(builder, xid);
call 0 returned 100%
50: 550: ReorderBufferQueueMessage(ctx->reorder, xid, snapshot, buf->endptr,
call 0 returned 100%
25: 551: message->transactional,
25: 552: message->message, /* first part of message is
-: 553: * prefix */
-: 554: message->message_size,
25: 555: message->message + message->prefix_size);
-: 556:}
-: 557:
-: 558:/*
-: 559: * Consolidated commit record handling between the different form of commit
-: 560: * records.
-: 561: */
-: 562:static void
function DecodeCommit called 1256 returned 100% blocks executed 100%
1256: 563:DecodeCommit(LogicalDecodingContext *ctx, XLogRecordBuffer *buf,
-: 564: xl_xact_parsed_commit *parsed, TransactionId xid)
-: 565:{
1256: 566: XLogRecPtr origin_lsn = InvalidXLogRecPtr;
1256: 567: TimestampTz commit_time = parsed->xact_time;
1256: 568: RepOriginId origin_id = XLogRecGetOrigin(buf->record);
-: 569: int i;
-: 570:
1256: 571: if (parsed->xinfo & XACT_XINFO_HAS_ORIGIN)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 572: {
2: 573: origin_lsn = parsed->origin_lsn;
2: 574: commit_time = parsed->origin_timestamp;
-: 575: }
-: 576:
-: 577: /*
-: 578: * Process invalidation messages, even if we're not interested in the
-: 579: * transaction's contents, since the various caches need to always be
-: 580: * consistent.
-: 581: */
1256: 582: if (parsed->nmsgs > 0)
branch 0 taken 36% (fallthrough)
branch 1 taken 64%
-: 583: {
451: 584: if (!ctx->fast_forward)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
902: 585: ReorderBufferAddInvalidations(ctx->reorder, xid, buf->origptr,
call 0 returned 100%
451: 586: parsed->nmsgs, parsed->msgs);
451: 587: ReorderBufferXidSetCatalogChanges(ctx->reorder, xid, buf->origptr);
call 0 returned 100%
-: 588: }
-: 589:
1256: 590: SnapBuildCommitTxn(ctx->snapshot_builder, buf->origptr, xid,
call 0 returned 100%
-: 591: parsed->nsubxacts, parsed->subxacts);
-: 592:
-: 593: /* ----
-: 594: * Check whether we are interested in this specific transaction, and tell
-: 595: * the reorderbuffer to forget the content of the (sub-)transactions
-: 596: * if not.
-: 597: *
-: 598: * There can be several reasons we might not be interested in this
-: 599: * transaction:
-: 600: * 1) We might not be interested in decoding transactions up to this
-: 601: * LSN. This can happen because we previously decoded it and now just
-: 602: * are restarting or if we haven't assembled a consistent snapshot yet.
-: 603: * 2) The transaction happened in another database.
-: 604: * 3) The output plugin is not interested in the origin.
-: 605: * 4) We are doing fast-forwarding
-: 606: *
-: 607: * We can't just use ReorderBufferAbort() here, because we need to execute
-: 608: * the transaction's invalidations. This currently won't be needed if
-: 609: * we're just skipping over the transaction because currently we only do
-: 610: * so during startup, to get to the first transaction the client needs. As
-: 611: * we have reset the catalog caches before starting to read WAL, and we
-: 612: * haven't yet touched any catalogs, there can't be anything to invalidate.
-: 613: * But if we're "forgetting" this commit because it's it happened in
-: 614: * another database, the invalidations might be important, because they
-: 615: * could be for shared catalogs and we might have loaded data into the
-: 616: * relevant syscaches.
-: 617: * ---
-: 618: */
1710: 619: if (SnapBuildXactNeedsSkip(ctx->snapshot_builder, buf->origptr) ||
call 0 returned 100%
branch 1 taken 36% (fallthrough)
branch 2 taken 64%
branch 3 taken 100% (fallthrough)
branch 4 taken 0%
1361: 620: (parsed->dbId != InvalidOid && parsed->dbId != ctx->slot->data.database) ||
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 99% (fallthrough)
branch 3 taken 1%
901: 621: ctx->fast_forward || FilterByOrigin(ctx, origin_id))
call 0 returned 100%
branch 1 taken 1% (fallthrough)
branch 2 taken 99%
-: 622: {
1519: 623: for (i = 0; i < parsed->nsubxacts; i++)
branch 0 taken 47%
branch 1 taken 53% (fallthrough)
-: 624: {
710: 625: ReorderBufferForget(ctx->reorder, parsed->subxacts[i], buf->origptr);
call 0 returned 100%
-: 626: }
809: 627: ReorderBufferForget(ctx->reorder, xid, buf->origptr);
call 0 returned 100%
-: 628:
2065: 629: return;
-: 630: }
-: 631:
-: 632: /* tell the reorderbuffer about the surviving subtransactions */
561: 633: for (i = 0; i < parsed->nsubxacts; i++)
branch 0 taken 20%
branch 1 taken 80% (fallthrough)
-: 634: {
114: 635: ReorderBufferCommitChild(ctx->reorder, xid, parsed->subxacts[i],
call 0 returned 100%
-: 636: buf->origptr, buf->endptr);
-: 637: }
-: 638:
-: 639: /* replay actions of all transaction + subtransactions in order */
447: 640: ReorderBufferCommit(ctx->reorder, xid, buf->origptr, buf->endptr,
call 0 returned 100%
-: 641: commit_time, origin_id, origin_lsn);
-: 642:}
-: 643:
-: 644:/*
-: 645: * Get the data from the various forms of abort records and pass it on to
-: 646: * snapbuild.c and reorderbuffer.c
-: 647: */
-: 648:static void
function DecodeAbort called 23 returned 100% blocks executed 67%
23: 649:DecodeAbort(LogicalDecodingContext *ctx, XLogRecordBuffer *buf,
-: 650: xl_xact_parsed_abort *parsed, TransactionId xid)
-: 651:{
-: 652: int i;
-: 653:
23: 654: for (i = 0; i < parsed->nsubxacts; i++)
branch 0 taken 0%
branch 1 taken 100% (fallthrough)
-: 655: {
#####: 656: ReorderBufferAbort(ctx->reorder, parsed->subxacts[i],
call 0 never executed
#####: 657: buf->record->EndRecPtr);
-: 658: }
-: 659:
23: 660: ReorderBufferAbort(ctx->reorder, xid, buf->record->EndRecPtr);
call 0 returned 100%
23: 661:}
-: 662:
-: 663:/*
-: 664: * Parse XLOG_HEAP_INSERT (not MULTI_INSERT!) records into tuplebufs.
-: 665: *
-: 666: * Deletes can contain the new tuple.
-: 667: */
-: 668:static void
function DecodeInsert called 962824 returned 100% blocks executed 95%
962824: 669:DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 670:{
-: 671: Size datalen;
-: 672: char *tupledata;
-: 673: Size tuplelen;
962824: 674: XLogReaderState *r = buf->record;
-: 675: xl_heap_insert *xlrec;
-: 676: ReorderBufferChange *change;
-: 677: RelFileNode target_node;
-: 678:
962824: 679: xlrec = (xl_heap_insert *) XLogRecGetData(r);
-: 680:
-: 681: /*
-: 682: * Ignore insert records without new tuples (this does happen when
-: 683: * raw_heap_insert marks the TOAST record as HEAP_INSERT_NO_LOGICAL).
-: 684: */
962824: 685: if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE))
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
18162: 686: return;
-: 687:
-: 688: /* only interested in our database */
953746: 689: XLogRecGetBlockTag(r, 0, &target_node, NULL, NULL);
call 0 returned 100%
953746: 690: if (target_node.dbNode != ctx->slot->data.database)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 691: return;
-: 692:
-: 693: /* output plugin doesn't look for this origin, no need to queue */
953746: 694: if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
call 0 returned 100%
branch 1 taken 1% (fallthrough)
branch 2 taken 99%
6: 695: return;
-: 696:
953740: 697: change = ReorderBufferGetChange(ctx->reorder);
call 0 returned 100%
953740: 698: if (!(xlrec->flags & XLH_INSERT_IS_SPECULATIVE))
branch 0 taken 98% (fallthrough)
branch 1 taken 2%
935840: 699: change->action = REORDER_BUFFER_CHANGE_INSERT;
-: 700: else
17900: 701: change->action = REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT;
953740: 702: change->origin_id = XLogRecGetOrigin(r);
-: 703:
953740: 704: memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode));
-: 705:
953740: 706: tupledata = XLogRecGetBlockData(r, 0, &datalen);
call 0 returned 100%
953740: 707: tuplelen = datalen - SizeOfHeapHeader;
-: 708:
953740: 709: change->data.tp.newtuple =
953740: 710: ReorderBufferGetTupleBuf(ctx->reorder, tuplelen);
call 0 returned 100%
-: 711:
953740: 712: DecodeXLogTuple(tupledata, datalen, change->data.tp.newtuple);
call 0 returned 100%
-: 713:
953740: 714: change->data.tp.clear_toast_afterwards = true;
-: 715:
953740: 716: ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r), buf->origptr, change);
call 0 returned 100%
-: 717:}
-: 718:
-: 719:/*
-: 720: * Parse XLOG_HEAP_UPDATE and XLOG_HEAP_HOT_UPDATE, which have the same layout
-: 721: * in the record, from wal into proper tuplebufs.
-: 722: *
-: 723: * Updates can possibly contain a new tuple and the old primary key.
-: 724: */
-: 725:static void
function DecodeUpdate called 87059 returned 100% blocks executed 84%
87059: 726:DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 727:{
87059: 728: XLogReaderState *r = buf->record;
-: 729: xl_heap_update *xlrec;
-: 730: ReorderBufferChange *change;
-: 731: char *data;
-: 732: RelFileNode target_node;
-: 733:
87059: 734: xlrec = (xl_heap_update *) XLogRecGetData(r);
-: 735:
-: 736: /* only interested in our database */
87059: 737: XLogRecGetBlockTag(r, 0, &target_node, NULL, NULL);
call 0 returned 100%
87059: 738: if (target_node.dbNode != ctx->slot->data.database)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 739: return;
-: 740:
-: 741: /* output plugin doesn't look for this origin, no need to queue */
87059: 742: if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 743: return;
-: 744:
87059: 745: change = ReorderBufferGetChange(ctx->reorder);
call 0 returned 100%
87059: 746: change->action = REORDER_BUFFER_CHANGE_UPDATE;
87059: 747: change->origin_id = XLogRecGetOrigin(r);
87059: 748: memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode));
-: 749:
87059: 750: if (xlrec->flags & XLH_UPDATE_CONTAINS_NEW_TUPLE)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
-: 751: {
-: 752: Size datalen;
-: 753: Size tuplelen;
-: 754:
86054: 755: data = XLogRecGetBlockData(r, 0, &datalen);
call 0 returned 100%
-: 756:
86054: 757: tuplelen = datalen - SizeOfHeapHeader;
-: 758:
86054: 759: change->data.tp.newtuple =
86054: 760: ReorderBufferGetTupleBuf(ctx->reorder, tuplelen);
call 0 returned 100%
-: 761:
86054: 762: DecodeXLogTuple(data, datalen, change->data.tp.newtuple);
call 0 returned 100%
-: 763: }
-: 764:
87059: 765: if (xlrec->flags & XLH_UPDATE_CONTAINS_OLD)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 766: {
-: 767: Size datalen;
-: 768: Size tuplelen;
-: 769:
-: 770: /* caution, remaining data in record is not aligned */
382: 771: data = XLogRecGetData(r) + SizeOfHeapUpdate;
382: 772: datalen = XLogRecGetDataLen(r) - SizeOfHeapUpdate;
382: 773: tuplelen = datalen - SizeOfHeapHeader;
-: 774:
382: 775: change->data.tp.oldtuple =
382: 776: ReorderBufferGetTupleBuf(ctx->reorder, tuplelen);
call 0 returned 100%
-: 777:
382: 778: DecodeXLogTuple(data, datalen, change->data.tp.oldtuple);
call 0 returned 100%
-: 779: }
-: 780:
87059: 781: change->data.tp.clear_toast_afterwards = true;
-: 782:
87059: 783: ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r), buf->origptr, change);
call 0 returned 100%
-: 784:}
-: 785:
-: 786:/*
-: 787: * Parse XLOG_HEAP_DELETE from wal into proper tuplebufs.
-: 788: *
-: 789: * Deletes can possibly contain the old primary key.
-: 790: */
-: 791:static void
function DecodeDelete called 123546 returned 100% blocks executed 83%
123546: 792:DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 793:{
123546: 794: XLogReaderState *r = buf->record;
-: 795: xl_heap_delete *xlrec;
-: 796: ReorderBufferChange *change;
-: 797: RelFileNode target_node;
-: 798:
123546: 799: xlrec = (xl_heap_delete *) XLogRecGetData(r);
-: 800:
-: 801: /* only interested in our database */
123546: 802: XLogRecGetBlockTag(r, 0, &target_node, NULL, NULL);
call 0 returned 100%
123546: 803: if (target_node.dbNode != ctx->slot->data.database)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
4: 804: return;
-: 805:
-: 806: /*
-: 807: * Super deletions are irrelevant for logical decoding, it's driven by the
-: 808: * confirmation records.
-: 809: */
123544: 810: if (xlrec->flags & XLH_DELETE_IS_SUPER)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 811: return;
-: 812:
-: 813: /* output plugin doesn't look for this origin, no need to queue */
123544: 814: if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 815: return;
-: 816:
123544: 817: change = ReorderBufferGetChange(ctx->reorder);
call 0 returned 100%
123544: 818: change->action = REORDER_BUFFER_CHANGE_DELETE;
123544: 819: change->origin_id = XLogRecGetOrigin(r);
-: 820:
123544: 821: memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode));
-: 822:
-: 823: /* old primary key stored */
123544: 824: if (xlrec->flags & XLH_DELETE_CONTAINS_OLD)
branch 0 taken 49% (fallthrough)
branch 1 taken 51%
-: 825: {
60629: 826: Size datalen = XLogRecGetDataLen(r) - SizeOfHeapDelete;
60629: 827: Size tuplelen = datalen - SizeOfHeapHeader;
-: 828:
60629: 829: Assert(XLogRecGetDataLen(r) > (SizeOfHeapDelete + SizeOfHeapHeader));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 830:
60629: 831: change->data.tp.oldtuple =
60629: 832: ReorderBufferGetTupleBuf(ctx->reorder, tuplelen);
call 0 returned 100%
-: 833:
60629: 834: DecodeXLogTuple((char *) xlrec + SizeOfHeapDelete,
call 0 returned 100%
-: 835: datalen, change->data.tp.oldtuple);
-: 836: }
-: 837:
123544: 838: change->data.tp.clear_toast_afterwards = true;
-: 839:
123544: 840: ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r), buf->origptr, change);
call 0 returned 100%
-: 841:}
-: 842:
-: 843:/*
-: 844: * Parse XLOG_HEAP_TRUNCATE from wal
-: 845: */
-: 846:static void
function DecodeTruncate called 8 returned 100% blocks executed 85%
8: 847:DecodeTruncate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 848:{
8: 849: XLogReaderState *r = buf->record;
-: 850: xl_heap_truncate *xlrec;
-: 851: ReorderBufferChange *change;
-: 852:
8: 853: xlrec = (xl_heap_truncate *) XLogRecGetData(r);
-: 854:
-: 855: /* only interested in our database */
8: 856: if (xlrec->dbId != ctx->slot->data.database)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 857: return;
-: 858:
-: 859: /* output plugin doesn't look for this origin, no need to queue */
8: 860: if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 861: return;
-: 862:
8: 863: change = ReorderBufferGetChange(ctx->reorder);
call 0 returned 100%
8: 864: change->action = REORDER_BUFFER_CHANGE_TRUNCATE;
8: 865: change->origin_id = XLogRecGetOrigin(r);
8: 866: if (xlrec->flags & XLH_TRUNCATE_CASCADE)
branch 0 taken 13% (fallthrough)
branch 1 taken 88%
1: 867: change->data.truncate.cascade = true;
8: 868: if (xlrec->flags & XLH_TRUNCATE_RESTART_SEQS)
branch 0 taken 25% (fallthrough)
branch 1 taken 75%
2: 869: change->data.truncate.restart_seqs = true;
8: 870: change->data.truncate.nrelids = xlrec->nrelids;
8: 871: change->data.truncate.relids = ReorderBufferGetRelids(ctx->reorder,
call 0 returned 100%
8: 872: xlrec->nrelids);
8: 873: memcpy(change->data.truncate.relids, xlrec->relids,
8: 874: xlrec->nrelids * sizeof(Oid));
8: 875: ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r),
call 0 returned 100%
-: 876: buf->origptr, change);
-: 877:}
-: 878:
-: 879:/*
-: 880: * Decode XLOG_HEAP2_MULTI_INSERT_insert record into multiple tuplebufs.
-: 881: *
-: 882: * Currently MULTI_INSERT will always contain the full tuples.
-: 883: */
-: 884:static void
function DecodeMultiInsert called 8 returned 100% blocks executed 77%
8: 885:DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 886:{
8: 887: XLogReaderState *r = buf->record;
-: 888: xl_heap_multi_insert *xlrec;
-: 889: int i;
-: 890: char *data;
-: 891: char *tupledata;
-: 892: Size tuplelen;
-: 893: RelFileNode rnode;
-: 894:
8: 895: xlrec = (xl_heap_multi_insert *) XLogRecGetData(r);
-: 896:
-: 897: /* only interested in our database */
8: 898: XLogRecGetBlockTag(r, 0, &rnode, NULL, NULL);
call 0 returned 100%
8: 899: if (rnode.dbNode != ctx->slot->data.database)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 900: return;
-: 901:
-: 902: /* output plugin doesn't look for this origin, no need to queue */
8: 903: if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 904: return;
-: 905:
-: 906: /*
-: 907: * As multi_insert is not used for catalogs yet, the block should always
-: 908: * have data even if a full-page write of it is taken.
-: 909: */
8: 910: tupledata = XLogRecGetBlockData(r, 0, &tuplelen);
call 0 returned 100%
8: 911: Assert(tupledata != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 912:
8: 913: data = tupledata;
820: 914: for (i = 0; i < xlrec->ntuples; i++)
branch 0 taken 99%
branch 1 taken 1% (fallthrough)
-: 915: {
-: 916: ReorderBufferChange *change;
-: 917: xl_multi_insert_tuple *xlhdr;
-: 918: int datalen;
-: 919: ReorderBufferTupleBuf *tuple;
-: 920:
812: 921: change = ReorderBufferGetChange(ctx->reorder);
call 0 returned 100%
812: 922: change->action = REORDER_BUFFER_CHANGE_INSERT;
812: 923: change->origin_id = XLogRecGetOrigin(r);
-: 924:
812: 925: memcpy(&change->data.tp.relnode, &rnode, sizeof(RelFileNode));
-: 926:
812: 927: xlhdr = (xl_multi_insert_tuple *) SHORTALIGN(data);
812: 928: data = ((char *) xlhdr) + SizeOfMultiInsertTuple;
812: 929: datalen = xlhdr->datalen;
-: 930:
-: 931: /*
-: 932: * CONTAINS_NEW_TUPLE will always be set currently as multi_insert
-: 933: * isn't used for catalogs, but better be future proof.
-: 934: *
-: 935: * We decode the tuple in pretty much the same way as DecodeXLogTuple,
-: 936: * but since the layout is slightly different, we can't use it here.
-: 937: */
812: 938: if (xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 939: {
-: 940: HeapTupleHeader header;
-: 941:
812: 942: change->data.tp.newtuple =
812: 943: ReorderBufferGetTupleBuf(ctx->reorder, datalen);
call 0 returned 100%
-: 944:
812: 945: tuple = change->data.tp.newtuple;
812: 946: header = tuple->tuple.t_data;
-: 947:
-: 948: /* not a disk based tuple */
812: 949: ItemPointerSetInvalid(&tuple->tuple.t_self);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
branch 3 taken 0% (fallthrough)
branch 4 taken 100%
call 5 never executed
-: 950:
-: 951: /*
-: 952: * We can only figure this out after reassembling the
-: 953: * transactions.
-: 954: */
812: 955: tuple->tuple.t_tableOid = InvalidOid;
-: 956:
812: 957: tuple->tuple.t_len = datalen + SizeofHeapTupleHeader;
-: 958:
812: 959: memset(header, 0, SizeofHeapTupleHeader);
-: 960:
812: 961: memcpy((char *) tuple->tuple.t_data + SizeofHeapTupleHeader,
-: 962: (char *) data,
-: 963: datalen);
812: 964: header->t_infomask = xlhdr->t_infomask;
812: 965: header->t_infomask2 = xlhdr->t_infomask2;
812: 966: header->t_hoff = xlhdr->t_hoff;
-: 967: }
-: 968:
-: 969: /*
-: 970: * Reset toast reassembly state only after the last row in the last
-: 971: * xl_multi_insert_tuple record emitted by one heap_multi_insert()
-: 972: * call.
-: 973: */
936: 974: if (xlrec->flags & XLH_INSERT_LAST_IN_MULTI &&
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
branch 2 taken 3% (fallthrough)
branch 3 taken 97%
124: 975: (i + 1) == xlrec->ntuples)
4: 976: change->data.tp.clear_toast_afterwards = true;
-: 977: else
808: 978: change->data.tp.clear_toast_afterwards = false;
-: 979:
812: 980: ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r),
call 0 returned 100%
-: 981: buf->origptr, change);
-: 982:
-: 983: /* move to the next xl_multi_insert_tuple entry */
812: 984: data += datalen;
-: 985: }
8: 986: Assert(data == tupledata + tuplelen);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 987:}
-: 988:
-: 989:/*
-: 990: * Parse XLOG_HEAP_CONFIRM from wal into a confirmation change.
-: 991: *
-: 992: * This is pretty trivial, all the state essentially already setup by the
-: 993: * speculative insertion.
-: 994: */
-: 995:static void
function DecodeSpecConfirm called 17900 returned 100% blocks executed 73%
17900: 996:DecodeSpecConfirm(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 997:{
17900: 998: XLogReaderState *r = buf->record;
-: 999: ReorderBufferChange *change;
-: 1000: RelFileNode target_node;
-: 1001:
-: 1002: /* only interested in our database */
17900: 1003: XLogRecGetBlockTag(r, 0, &target_node, NULL, NULL);
call 0 returned 100%
17900: 1004: if (target_node.dbNode != ctx->slot->data.database)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1005: return;
-: 1006:
-: 1007: /* output plugin doesn't look for this origin, no need to queue */
17900: 1008: if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 1009: return;
-: 1010:
17900: 1011: change = ReorderBufferGetChange(ctx->reorder);
call 0 returned 100%
17900: 1012: change->action = REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM;
17900: 1013: change->origin_id = XLogRecGetOrigin(r);
-: 1014:
17900: 1015: memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode));
-: 1016:
17900: 1017: change->data.tp.clear_toast_afterwards = true;
-: 1018:
17900: 1019: ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r), buf->origptr, change);
call 0 returned 100%
-: 1020:}
-: 1021:
-: 1022:
-: 1023:/*
-: 1024: * Read a HeapTuple as WAL logged by heap_insert, heap_update and heap_delete
-: 1025: * (but not by heap_multi_insert) into a tuplebuf.
-: 1026: *
-: 1027: * The size 'len' and the pointer 'data' in the record need to be
-: 1028: * computed outside as they are record specific.
-: 1029: */
-: 1030:static void
function DecodeXLogTuple called 1100805 returned 100% blocks executed 57%
1100805: 1031:DecodeXLogTuple(char *data, Size len, ReorderBufferTupleBuf *tuple)
-: 1032:{
-: 1033: xl_heap_header xlhdr;
1100805: 1034: int datalen = len - SizeOfHeapHeader;
-: 1035: HeapTupleHeader header;
-: 1036:
1100805: 1037: Assert(datalen >= 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1038:
1100805: 1039: tuple->tuple.t_len = datalen + SizeofHeapTupleHeader;
1100805: 1040: header = tuple->tuple.t_data;
-: 1041:
-: 1042: /* not a disk based tuple */
1100805: 1043: ItemPointerSetInvalid(&tuple->tuple.t_self);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
branch 3 taken 0% (fallthrough)
branch 4 taken 100%
call 5 never executed
-: 1044:
-: 1045: /* we can only figure this out after reassembling the transactions */
1100805: 1046: tuple->tuple.t_tableOid = InvalidOid;
-: 1047:
-: 1048: /* data is not stored aligned, copy to aligned storage */
1100805: 1049: memcpy((char *) &xlhdr,
-: 1050: data,
-: 1051: SizeOfHeapHeader);
-: 1052:
1100805: 1053: memset(header, 0, SizeofHeapTupleHeader);
-: 1054:
2201610: 1055: memcpy(((char *) tuple->tuple.t_data) + SizeofHeapTupleHeader,
1100805: 1056: data + SizeOfHeapHeader,
-: 1057: datalen);
-: 1058:
1100805: 1059: header->t_infomask = xlhdr.t_infomask;
1100805: 1060: header->t_infomask2 = xlhdr.t_infomask2;
1100805: 1061: header->t_hoff = xlhdr.t_hoff;
1100805: 1062:}
0001-Add-logical_decoding_work_mem-to-limit-ReorderBuffer-patch_coverage/worker.c.gcov 0000664 0001750 0000000 00000262413 13560210457 030350 0 ustar vignesh root -: 0:Source:worker.c
-: 0:Graph:./worker.gcno
-: 0:Data:./worker.gcda
-: 0:Runs:6533
-: 0:Programs:1
-: 1:/*-------------------------------------------------------------------------
-: 2: * worker.c
-: 3: * PostgreSQL logical replication worker (apply)
-: 4: *
-: 5: * Copyright (c) 2016-2019, PostgreSQL Global Development Group
-: 6: *
-: 7: * IDENTIFICATION
-: 8: * src/backend/replication/logical/worker.c
-: 9: *
-: 10: * NOTES
-: 11: * This file contains the worker which applies logical changes as they come
-: 12: * from remote logical replication stream.
-: 13: *
-: 14: * The main worker (apply) is started by logical replication worker
-: 15: * launcher for every enabled subscription in a database. It uses
-: 16: * walsender protocol to communicate with publisher.
-: 17: *
-: 18: * This module includes server facing code and shares libpqwalreceiver
-: 19: * module with walreceiver for providing the libpq specific functionality.
-: 20: *
-: 21: *-------------------------------------------------------------------------
-: 22: */
-: 23:
-: 24:#include "postgres.h"
-: 25:
-: 26:#include "access/table.h"
-: 27:#include "access/tableam.h"
-: 28:#include "access/xact.h"
-: 29:#include "access/xlog_internal.h"
-: 30:#include "catalog/catalog.h"
-: 31:#include "catalog/namespace.h"
-: 32:#include "catalog/pg_subscription.h"
-: 33:#include "catalog/pg_subscription_rel.h"
-: 34:#include "commands/tablecmds.h"
-: 35:#include "commands/trigger.h"
-: 36:#include "executor/executor.h"
-: 37:#include "executor/nodeModifyTable.h"
-: 38:#include "funcapi.h"
-: 39:#include "libpq/pqformat.h"
-: 40:#include "libpq/pqsignal.h"
-: 41:#include "mb/pg_wchar.h"
-: 42:#include "miscadmin.h"
-: 43:#include "nodes/makefuncs.h"
-: 44:#include "optimizer/optimizer.h"
-: 45:#include "parser/parse_relation.h"
-: 46:#include "pgstat.h"
-: 47:#include "postmaster/bgworker.h"
-: 48:#include "postmaster/postmaster.h"
-: 49:#include "postmaster/walwriter.h"
-: 50:#include "replication/decode.h"
-: 51:#include "replication/logical.h"
-: 52:#include "replication/logicalproto.h"
-: 53:#include "replication/logicalrelation.h"
-: 54:#include "replication/logicalworker.h"
-: 55:#include "replication/origin.h"
-: 56:#include "replication/reorderbuffer.h"
-: 57:#include "replication/snapbuild.h"
-: 58:#include "replication/walreceiver.h"
-: 59:#include "replication/worker_internal.h"
-: 60:#include "rewrite/rewriteHandler.h"
-: 61:#include "storage/bufmgr.h"
-: 62:#include "storage/ipc.h"
-: 63:#include "storage/lmgr.h"
-: 64:#include "storage/proc.h"
-: 65:#include "storage/procarray.h"
-: 66:#include "tcop/tcopprot.h"
-: 67:#include "utils/builtins.h"
-: 68:#include "utils/catcache.h"
-: 69:#include "utils/datum.h"
-: 70:#include "utils/fmgroids.h"
-: 71:#include "utils/guc.h"
-: 72:#include "utils/inval.h"
-: 73:#include "utils/lsyscache.h"
-: 74:#include "utils/memutils.h"
-: 75:#include "utils/rel.h"
-: 76:#include "utils/syscache.h"
-: 77:#include "utils/timeout.h"
-: 78:
-: 79:#define NAPTIME_PER_CYCLE 1000 /* max sleep time between cycles (1s) */
-: 80:
-: 81:typedef struct FlushPosition
-: 82:{
-: 83: dlist_node node;
-: 84: XLogRecPtr local_end;
-: 85: XLogRecPtr remote_end;
-: 86:} FlushPosition;
-: 87:
-: 88:static dlist_head lsn_mapping = DLIST_STATIC_INIT(lsn_mapping);
-: 89:
-: 90:typedef struct SlotErrCallbackArg
-: 91:{
-: 92: LogicalRepRelMapEntry *rel;
-: 93: int local_attnum;
-: 94: int remote_attnum;
-: 95:} SlotErrCallbackArg;
-: 96:
-: 97:static MemoryContext ApplyMessageContext = NULL;
-: 98:MemoryContext ApplyContext = NULL;
-: 99:
-: 100:WalReceiverConn *wrconn = NULL;
-: 101:
-: 102:Subscription *MySubscription = NULL;
-: 103:bool MySubscriptionValid = false;
-: 104:
-: 105:bool in_remote_transaction = false;
-: 106:static XLogRecPtr remote_final_lsn = InvalidXLogRecPtr;
-: 107:
-: 108:static void send_feedback(XLogRecPtr recvpos, bool force, bool requestReply);
-: 109:
-: 110:static void store_flush_position(XLogRecPtr remote_lsn);
-: 111:
-: 112:static void maybe_reread_subscription(void);
-: 113:
-: 114:/* Flags set by signal handlers */
-: 115:static volatile sig_atomic_t got_SIGHUP = false;
-: 116:
-: 117:/*
-: 118: * Should this worker apply changes for given relation.
-: 119: *
-: 120: * This is mainly needed for initial relation data sync as that runs in
-: 121: * separate worker process running in parallel and we need some way to skip
-: 122: * changes coming to the main apply worker during the sync of a table.
-: 123: *
-: 124: * Note we need to do smaller or equals comparison for SYNCDONE state because
-: 125: * it might hold position of end of initial slot consistent point WAL
-: 126: * record + 1 (ie start of next record) and next record can be COMMIT of
-: 127: * transaction we are now processing (which is what we set remote_final_lsn
-: 128: * to in apply_handle_begin).
-: 129: */
-: 130:static bool
function should_apply_changes_for_rel called 667 returned 100% blocks executed 80%
667: 131:should_apply_changes_for_rel(LogicalRepRelMapEntry *rel)
-: 132:{
667: 133: if (am_tablesync_worker())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 134: return MyLogicalRepWorker->relid == rel->localreloid;
-: 135: else
1334: 136: return (rel->state == SUBREL_STATE_READY ||
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
12: 137: (rel->state == SUBREL_STATE_SYNCDONE &&
branch 0 never executed
branch 1 never executed
#####: 138: rel->statelsn <= remote_final_lsn));
-: 139:}
-: 140:
-: 141:/*
-: 142: * Make sure that we started local transaction.
-: 143: *
-: 144: * Also switches to ApplyMessageContext as necessary.
-: 145: */
-: 146:static bool
function ensure_transaction called 667 returned 100% blocks executed 92%
667: 147:ensure_transaction(void)
-: 148:{
667: 149: if (IsTransactionState())
call 0 returned 100%
branch 1 taken 84% (fallthrough)
branch 2 taken 16%
-: 150: {
562: 151: SetCurrentStatementStartTimestamp();
call 0 returned 100%
-: 152:
562: 153: if (CurrentMemoryContext != ApplyMessageContext)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 154: MemoryContextSwitchTo(ApplyMessageContext);
call 0 never executed
-: 155:
562: 156: return false;
-: 157: }
-: 158:
105: 159: SetCurrentStatementStartTimestamp();
call 0 returned 100%
105: 160: StartTransactionCommand();
call 0 returned 100%
-: 161:
105: 162: maybe_reread_subscription();
call 0 returned 100%
-: 163:
105: 164: MemoryContextSwitchTo(ApplyMessageContext);
call 0 returned 100%
105: 165: return true;
-: 166:}
-: 167:
-: 168:
-: 169:/*
-: 170: * Executor state preparation for evaluation of constraint expressions,
-: 171: * indexes and triggers.
-: 172: *
-: 173: * This is based on similar code in copy.c
-: 174: */
-: 175:static EState *
function create_estate_for_relation called 649 returned 100% blocks executed 100%
649: 176:create_estate_for_relation(LogicalRepRelMapEntry *rel)
-: 177:{
-: 178: EState *estate;
-: 179: ResultRelInfo *resultRelInfo;
-: 180: RangeTblEntry *rte;
-: 181:
649: 182: estate = CreateExecutorState();
call 0 returned 100%
-: 183:
649: 184: rte = makeNode(RangeTblEntry);
call 0 returned 100%
649: 185: rte->rtekind = RTE_RELATION;
649: 186: rte->relid = RelationGetRelid(rel->localrel);
649: 187: rte->relkind = rel->localrel->rd_rel->relkind;
649: 188: rte->rellockmode = AccessShareLock;
649: 189: ExecInitRangeTable(estate, list_make1(rte));
call 0 returned 100%
call 1 returned 100%
-: 190:
649: 191: resultRelInfo = makeNode(ResultRelInfo);
call 0 returned 100%
649: 192: InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
call 0 returned 100%
-: 193:
649: 194: estate->es_result_relations = resultRelInfo;
649: 195: estate->es_num_result_relations = 1;
649: 196: estate->es_result_relation_info = resultRelInfo;
-: 197:
649: 198: estate->es_output_cid = GetCurrentCommandId(true);
call 0 returned 100%
-: 199:
-: 200: /* Prepare to catch AFTER triggers. */
649: 201: AfterTriggerBeginQuery();
call 0 returned 100%
-: 202:
649: 203: return estate;
-: 204:}
-: 205:
-: 206:/*
-: 207: * Executes default values for columns for which we can't map to remote
-: 208: * relation columns.
-: 209: *
-: 210: * This allows us to support tables which have more columns on the downstream
-: 211: * than on the upstream.
-: 212: */
-: 213:static void
function slot_fill_defaults called 351 returned 100% blocks executed 96%
351: 214:slot_fill_defaults(LogicalRepRelMapEntry *rel, EState *estate,
-: 215: TupleTableSlot *slot)
-: 216:{
351: 217: TupleDesc desc = RelationGetDescr(rel->localrel);
351: 218: int num_phys_attrs = desc->natts;
-: 219: int i;
-: 220: int attnum,
351: 221: num_defaults = 0;
-: 222: int *defmap;
-: 223: ExprState **defexprs;
-: 224: ExprContext *econtext;
-: 225:
351: 226: econtext = GetPerTupleExprContext(estate);
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
call 2 never executed
-: 227:
-: 228: /* We got all the data via replication, no need to evaluate anything. */
351: 229: if (num_phys_attrs == rel->remoterel.natts)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
698: 230: return;
-: 231:
4: 232: defmap = (int *) palloc(num_phys_attrs * sizeof(int));
call 0 returned 100%
4: 233: defexprs = (ExprState **) palloc(num_phys_attrs * sizeof(ExprState *));
call 0 returned 100%
-: 234:
16: 235: for (attnum = 0; attnum < num_phys_attrs; attnum++)
branch 0 taken 75%
branch 1 taken 25% (fallthrough)
-: 236: {
-: 237: Expr *defexpr;
-: 238:
12: 239: if (TupleDescAttr(desc, attnum)->attisdropped || TupleDescAttr(desc, attnum)->attgenerated)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 17% (fallthrough)
branch 3 taken 83%
2: 240: continue;
-: 241:
10: 242: if (rel->attrmap[attnum] >= 0)
branch 0 taken 60% (fallthrough)
branch 1 taken 40%
6: 243: continue;
-: 244:
4: 245: defexpr = (Expr *) build_column_default(rel->localrel, attnum + 1);
call 0 returned 100%
-: 246:
4: 247: if (defexpr != NULL)
branch 0 taken 75% (fallthrough)
branch 1 taken 25%
-: 248: {
-: 249: /* Run the expression through planner */
3: 250: defexpr = expression_planner(defexpr);
call 0 returned 100%
-: 251:
-: 252: /* Initialize executable expression in copycontext */
3: 253: defexprs[num_defaults] = ExecInitExpr(defexpr, NULL);
call 0 returned 100%
3: 254: defmap[num_defaults] = attnum;
3: 255: num_defaults++;
-: 256: }
-: 257:
-: 258: }
-: 259:
7: 260: for (i = 0; i < num_defaults; i++)
branch 0 taken 43%
branch 1 taken 57% (fallthrough)
6: 261: slot->tts_values[defmap[i]] =
3: 262: ExecEvalExpr(defexprs[i], econtext, &slot->tts_isnull[defmap[i]]);
call 0 returned 100%
-: 263:}
-: 264:
-: 265:/*
-: 266: * Error callback to give more context info about type conversion failure.
-: 267: */
-: 268:static void
function slot_store_error_callback called 0 returned 0% blocks executed 0%
#####: 269:slot_store_error_callback(void *arg)
-: 270:{
#####: 271: SlotErrCallbackArg *errarg = (SlotErrCallbackArg *) arg;
-: 272: LogicalRepRelMapEntry *rel;
-: 273: char *remotetypname;
-: 274: Oid remotetypoid,
-: 275: localtypoid;
-: 276:
-: 277: /* Nothing to do if remote attribute number is not set */
#####: 278: if (errarg->remote_attnum < 0)
branch 0 never executed
branch 1 never executed
#####: 279: return;
-: 280:
#####: 281: rel = errarg->rel;
#####: 282: remotetypoid = rel->remoterel.atttyps[errarg->remote_attnum];
-: 283:
-: 284: /* Fetch remote type name from the LogicalRepTypMap cache */
#####: 285: remotetypname = logicalrep_typmap_gettypname(remotetypoid);
call 0 never executed
-: 286:
-: 287: /* Fetch local type OID from the local sys cache */
#####: 288: localtypoid = get_atttype(rel->localreloid, errarg->local_attnum + 1);
call 0 never executed
-: 289:
#####: 290: errcontext("processing remote data for replication target relation \"%s.%s\" column \"%s\", "
call 0 never executed
call 1 never executed
call 2 never executed
-: 291: "remote type %s, local type %s",
-: 292: rel->remoterel.nspname, rel->remoterel.relname,
#####: 293: rel->remoterel.attnames[errarg->remote_attnum],
-: 294: remotetypname,
-: 295: format_type_be(localtypoid));
-: 296:}
-: 297:
-: 298:/*
-: 299: * Store data in C string form into slot.
-: 300: * This is similar to BuildTupleFromCStrings but TupleTableSlot fits our
-: 301: * use better.
-: 302: */
-: 303:static void
function slot_store_cstrings called 649 returned 100% blocks executed 100%
649: 304:slot_store_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
-: 305: char **values)
-: 306:{
649: 307: int natts = slot->tts_tupleDescriptor->natts;
-: 308: int i;
-: 309: SlotErrCallbackArg errarg;
-: 310: ErrorContextCallback errcallback;
-: 311:
649: 312: ExecClearTuple(slot);
call 0 returned 100%
-: 313:
-: 314: /* Push callback + info on the error context stack */
649: 315: errarg.rel = rel;
649: 316: errarg.local_attnum = -1;
649: 317: errarg.remote_attnum = -1;
649: 318: errcallback.callback = slot_store_error_callback;
649: 319: errcallback.arg = (void *) &errarg;
649: 320: errcallback.previous = error_context_stack;
649: 321: error_context_stack = &errcallback;
-: 322:
-: 323: /* Call the "in" function for each non-dropped attribute */
1599: 324: for (i = 0; i < natts; i++)
branch 0 taken 59%
branch 1 taken 41% (fallthrough)
-: 325: {
950: 326: Form_pg_attribute att = TupleDescAttr(slot->tts_tupleDescriptor, i);
950: 327: int remoteattnum = rel->attrmap[i];
-: 328:
1881: 329: if (!att->attisdropped && remoteattnum >= 0 &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 98% (fallthrough)
branch 3 taken 2%
branch 4 taken 85% (fallthrough)
branch 5 taken 15%
931: 330: values[remoteattnum] != NULL)
788: 331: {
-: 332: Oid typinput;
-: 333: Oid typioparam;
-: 334:
788: 335: errarg.local_attnum = i;
788: 336: errarg.remote_attnum = remoteattnum;
-: 337:
788: 338: getTypeInputInfo(att->atttypid, &typinput, &typioparam);
call 0 returned 100%
1576: 339: slot->tts_values[i] =
788: 340: OidInputFunctionCall(typinput, values[remoteattnum],
call 0 returned 100%
-: 341: typioparam, att->atttypmod);
788: 342: slot->tts_isnull[i] = false;
-: 343:
788: 344: errarg.local_attnum = -1;
788: 345: errarg.remote_attnum = -1;
-: 346: }
-: 347: else
-: 348: {
-: 349: /*
-: 350: * We assign NULL to dropped attributes, NULL values, and missing
-: 351: * values (missing values should be later filled using
-: 352: * slot_fill_defaults).
-: 353: */
162: 354: slot->tts_values[i] = (Datum) 0;
162: 355: slot->tts_isnull[i] = true;
-: 356: }
-: 357: }
-: 358:
-: 359: /* Pop the error context stack */
649: 360: error_context_stack = errcallback.previous;
-: 361:
649: 362: ExecStoreVirtualTuple(slot);
call 0 returned 100%
649: 363:}
-: 364:
-: 365:/*
-: 366: * Modify slot with user data provided as C strings.
-: 367: * This is somewhat similar to heap_modify_tuple but also calls the type
-: 368: * input function on the user data as the input is the text representation
-: 369: * of the types.
-: 370: */
-: 371:static void
function slot_modify_cstrings called 107 returned 100% blocks executed 94%
107: 372:slot_modify_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
-: 373: char **values, bool *replaces)
-: 374:{
107: 375: int natts = slot->tts_tupleDescriptor->natts;
-: 376: int i;
-: 377: SlotErrCallbackArg errarg;
-: 378: ErrorContextCallback errcallback;
-: 379:
107: 380: slot_getallattrs(slot);
call 0 returned 100%
107: 381: ExecClearTuple(slot);
call 0 returned 100%
-: 382:
-: 383: /* Push callback + info on the error context stack */
107: 384: errarg.rel = rel;
107: 385: errarg.local_attnum = -1;
107: 386: errarg.remote_attnum = -1;
107: 387: errcallback.callback = slot_store_error_callback;
107: 388: errcallback.arg = (void *) &errarg;
107: 389: errcallback.previous = error_context_stack;
107: 390: error_context_stack = &errcallback;
-: 391:
-: 392: /* Call the "in" function for each replaced attribute */
300: 393: for (i = 0; i < natts; i++)
branch 0 taken 64%
branch 1 taken 36% (fallthrough)
-: 394: {
193: 395: Form_pg_attribute att = TupleDescAttr(slot->tts_tupleDescriptor, i);
193: 396: int remoteattnum = rel->attrmap[i];
-: 397:
193: 398: if (remoteattnum < 0)
branch 0 taken 7% (fallthrough)
branch 1 taken 93%
13: 399: continue;
-: 400:
180: 401: if (!replaces[remoteattnum])
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 402: continue;
-: 403:
180: 404: if (values[remoteattnum] != NULL)
branch 0 taken 88% (fallthrough)
branch 1 taken 12%
-: 405: {
-: 406: Oid typinput;
-: 407: Oid typioparam;
-: 408:
158: 409: errarg.local_attnum = i;
158: 410: errarg.remote_attnum = remoteattnum;
-: 411:
158: 412: getTypeInputInfo(att->atttypid, &typinput, &typioparam);
call 0 returned 100%
316: 413: slot->tts_values[i] =
158: 414: OidInputFunctionCall(typinput, values[remoteattnum],
call 0 returned 100%
-: 415: typioparam, att->atttypmod);
158: 416: slot->tts_isnull[i] = false;
-: 417:
158: 418: errarg.local_attnum = -1;
158: 419: errarg.remote_attnum = -1;
-: 420: }
-: 421: else
-: 422: {
22: 423: slot->tts_values[i] = (Datum) 0;
22: 424: slot->tts_isnull[i] = true;
-: 425: }
-: 426: }
-: 427:
-: 428: /* Pop the error context stack */
107: 429: error_context_stack = errcallback.previous;
-: 430:
107: 431: ExecStoreVirtualTuple(slot);
call 0 returned 100%
107: 432:}
-: 433:
-: 434:/*
-: 435: * Handle BEGIN message.
-: 436: */
-: 437:static void
function apply_handle_begin called 135 returned 100% blocks executed 100%
135: 438:apply_handle_begin(StringInfo s)
-: 439:{
-: 440: LogicalRepBeginData begin_data;
-: 441:
135: 442: logicalrep_read_begin(s, &begin_data);
call 0 returned 100%
-: 443:
135: 444: remote_final_lsn = begin_data.final_lsn;
-: 445:
135: 446: in_remote_transaction = true;
-: 447:
135: 448: pgstat_report_activity(STATE_RUNNING, NULL);
call 0 returned 100%
135: 449:}
-: 450:
-: 451:/*
-: 452: * Handle COMMIT message.
-: 453: *
-: 454: * TODO, support tracking of multiple origins
-: 455: */
-: 456:static void
function apply_handle_commit called 134 returned 100% blocks executed 93%
134: 457:apply_handle_commit(StringInfo s)
-: 458:{
-: 459: LogicalRepCommitData commit_data;
-: 460:
134: 461: logicalrep_read_commit(s, &commit_data);
call 0 returned 100%
-: 462:
134: 463: Assert(commit_data.commit_lsn == remote_final_lsn);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 464:
-: 465: /* The synchronization worker runs in single transaction. */
134: 466: if (IsTransactionState() && !am_tablesync_worker())
call 0 returned 100%
branch 1 taken 78% (fallthrough)
branch 2 taken 22%
call 3 returned 100%
branch 4 taken 100% (fallthrough)
branch 5 taken 0%
-: 467: {
-: 468: /*
-: 469: * Update origin state so we can restart streaming from correct
-: 470: * position in case of crash.
-: 471: */
104: 472: replorigin_session_origin_lsn = commit_data.end_lsn;
104: 473: replorigin_session_origin_timestamp = commit_data.committime;
-: 474:
104: 475: CommitTransactionCommand();
call 0 returned 100%
104: 476: pgstat_report_stat(false);
call 0 returned 100%
-: 477:
104: 478: store_flush_position(commit_data.end_lsn);
call 0 returned 100%
-: 479: }
-: 480: else
-: 481: {
-: 482: /* Process any invalidation messages that might have accumulated. */
30: 483: AcceptInvalidationMessages();
call 0 returned 100%
30: 484: maybe_reread_subscription();
call 0 returned 100%
-: 485: }
-: 486:
134: 487: in_remote_transaction = false;
-: 488:
-: 489: /* Process any tables that are being synchronized in parallel. */
134: 490: process_syncing_tables(commit_data.end_lsn);
call 0 returned 100%
-: 491:
134: 492: pgstat_report_activity(STATE_IDLE, NULL);
call 0 returned 100%
134: 493:}
-: 494:
-: 495:/*
-: 496: * Handle ORIGIN message.
-: 497: *
-: 498: * TODO, support tracking of multiple origins
-: 499: */
-: 500:static void
function apply_handle_origin called 0 returned 0% blocks executed 0%
#####: 501:apply_handle_origin(StringInfo s)
-: 502:{
-: 503: /*
-: 504: * ORIGIN message can only come inside remote transaction and before any
-: 505: * actual writes.
-: 506: */
#####: 507: if (!in_remote_transaction ||
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
#####: 508: (IsTransactionState() && !am_tablesync_worker()))
call 0 never executed
call 1 never executed
branch 2 never executed
branch 3 never executed
#####: 509: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 510: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 511: errmsg("ORIGIN message sent out of order")));
#####: 512:}
-: 513:
-: 514:/*
-: 515: * Handle RELATION message.
-: 516: *
-: 517: * Note we don't do validation against local schema here. The validation
-: 518: * against local schema is postponed until first change for given relation
-: 519: * comes as we only care about it when applying changes for it anyway and we
-: 520: * do less locking this way.
-: 521: */
-: 522:static void
function apply_handle_relation called 44 returned 100% blocks executed 100%
44: 523:apply_handle_relation(StringInfo s)
-: 524:{
-: 525: LogicalRepRelation *rel;
-: 526:
44: 527: rel = logicalrep_read_rel(s);
call 0 returned 100%
44: 528: logicalrep_relmap_update(rel);
call 0 returned 100%
44: 529:}
-: 530:
-: 531:/*
-: 532: * Handle TYPE message.
-: 533: *
-: 534: * Note we don't do local mapping here, that's done when the type is
-: 535: * actually used.
-: 536: */
-: 537:static void
function apply_handle_type called 16 returned 100% blocks executed 100%
16: 538:apply_handle_type(StringInfo s)
-: 539:{
-: 540: LogicalRepTyp typ;
-: 541:
16: 542: logicalrep_read_typ(s, &typ);
call 0 returned 100%
16: 543: logicalrep_typmap_update(&typ);
call 0 returned 100%
16: 544:}
-: 545:
-: 546:/*
-: 547: * Get replica identity index or if it is not defined a primary key.
-: 548: *
-: 549: * If neither is defined, returns InvalidOid
-: 550: */
-: 551:static Oid
function GetRelationIdentityOrPK called 298 returned 100% blocks executed 100%
298: 552:GetRelationIdentityOrPK(Relation rel)
-: 553:{
-: 554: Oid idxoid;
-: 555:
298: 556: idxoid = RelationGetReplicaIndex(rel);
call 0 returned 100%
-: 557:
298: 558: if (!OidIsValid(idxoid))
branch 0 taken 41% (fallthrough)
branch 1 taken 59%
122: 559: idxoid = RelationGetPrimaryKeyIndex(rel);
call 0 returned 100%
-: 560:
298: 561: return idxoid;
-: 562:}
-: 563:
-: 564:/*
-: 565: * Handle INSERT message.
-: 566: */
-: 567:static void
function apply_handle_insert called 364 returned 99% blocks executed 97%
364: 568:apply_handle_insert(StringInfo s)
-: 569:{
-: 570: LogicalRepRelMapEntry *rel;
-: 571: LogicalRepTupleData newtup;
-: 572: LogicalRepRelId relid;
-: 573: EState *estate;
-: 574: TupleTableSlot *remoteslot;
-: 575: MemoryContext oldctx;
-: 576:
364: 577: ensure_transaction();
call 0 returned 100%
-: 578:
364: 579: relid = logicalrep_read_insert(s, &newtup);
call 0 returned 100%
364: 580: rel = logicalrep_rel_open(relid, RowExclusiveLock);
call 0 returned 99%
363: 581: if (!should_apply_changes_for_rel(rel))
call 0 returned 100%
branch 1 taken 3% (fallthrough)
branch 2 taken 97%
-: 582: {
-: 583: /*
-: 584: * The relation can't become interesting in the middle of the
-: 585: * transaction so it's safe to unlock it.
-: 586: */
12: 587: logicalrep_rel_close(rel, RowExclusiveLock);
call 0 returned 100%
375: 588: return;
-: 589: }
-: 590:
-: 591: /* Initialize the executor state. */
351: 592: estate = create_estate_for_relation(rel);
call 0 returned 100%
351: 593: remoteslot = ExecInitExtraTupleSlot(estate,
call 0 returned 100%
351: 594: RelationGetDescr(rel->localrel),
-: 595: &TTSOpsVirtual);
-: 596:
-: 597: /* Input functions may need an active snapshot, so get one */
351: 598: PushActiveSnapshot(GetTransactionSnapshot());
call 0 returned 100%
call 1 returned 100%
-: 599:
-: 600: /* Process and store remote tuple in the slot */
351: 601: oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 returned 100%
call 3 returned 100%
351: 602: slot_store_cstrings(remoteslot, rel, newtup.values);
call 0 returned 100%
351: 603: slot_fill_defaults(rel, estate, remoteslot);
call 0 returned 100%
351: 604: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 605:
351: 606: ExecOpenIndices(estate->es_result_relation_info, false);
call 0 returned 100%
-: 607:
-: 608: /* Do the insert. */
351: 609: ExecSimpleRelationInsert(estate, remoteslot);
call 0 returned 100%
-: 610:
-: 611: /* Cleanup. */
351: 612: ExecCloseIndices(estate->es_result_relation_info);
call 0 returned 100%
351: 613: PopActiveSnapshot();
call 0 returned 100%
-: 614:
-: 615: /* Handle queued AFTER triggers. */
351: 616: AfterTriggerEndQuery(estate);
call 0 returned 100%
-: 617:
351: 618: ExecResetTupleTable(estate->es_tupleTable, false);
call 0 returned 100%
351: 619: FreeExecutorState(estate);
call 0 returned 100%
-: 620:
351: 621: logicalrep_rel_close(rel, NoLock);
call 0 returned 100%
-: 622:
351: 623: CommandCounterIncrement();
call 0 returned 100%
-: 624:}
-: 625:
-: 626:/*
-: 627: * Check if the logical replication relation is updatable and throw
-: 628: * appropriate error if it isn't.
-: 629: */
-: 630:static void
function check_relation_updatable called 298 returned 100% blocks executed 16%
298: 631:check_relation_updatable(LogicalRepRelMapEntry *rel)
-: 632:{
-: 633: /* Updatable, no error. */
298: 634: if (rel->updatable)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
596: 635: return;
-: 636:
-: 637: /*
-: 638: * We are in error mode so it's fine this is somewhat slow. It's better to
-: 639: * give user correct error.
-: 640: */
#####: 641: if (OidIsValid(GetRelationIdentityOrPK(rel->localrel)))
call 0 never executed
branch 1 never executed
branch 2 never executed
-: 642: {
#####: 643: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 644: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 645: errmsg("publisher did not send replica identity column "
-: 646: "expected by the logical replication target relation \"%s.%s\"",
-: 647: rel->remoterel.nspname, rel->remoterel.relname)));
-: 648: }
-: 649:
#####: 650: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 651: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 652: errmsg("logical replication target relation \"%s.%s\" has "
-: 653: "neither REPLICA IDENTITY index nor PRIMARY "
-: 654: "KEY and published relation does not have "
-: 655: "REPLICA IDENTITY FULL",
-: 656: rel->remoterel.nspname, rel->remoterel.relname)));
-: 657:}
-: 658:
-: 659:/*
-: 660: * Handle UPDATE message.
-: 661: *
-: 662: * TODO: FDW support
-: 663: */
-: 664:static void
function apply_handle_update called 107 returned 100% blocks executed 87%
107: 665:apply_handle_update(StringInfo s)
-: 666:{
-: 667: LogicalRepRelMapEntry *rel;
-: 668: LogicalRepRelId relid;
-: 669: Oid idxoid;
-: 670: EState *estate;
-: 671: EPQState epqstate;
-: 672: LogicalRepTupleData oldtup;
-: 673: LogicalRepTupleData newtup;
-: 674: bool has_oldtup;
-: 675: TupleTableSlot *localslot;
-: 676: TupleTableSlot *remoteslot;
-: 677: bool found;
-: 678: MemoryContext oldctx;
-: 679:
107: 680: ensure_transaction();
call 0 returned 100%
-: 681:
107: 682: relid = logicalrep_read_update(s, &has_oldtup, &oldtup,
call 0 returned 100%
-: 683: &newtup);
107: 684: rel = logicalrep_rel_open(relid, RowExclusiveLock);
call 0 returned 100%
107: 685: if (!should_apply_changes_for_rel(rel))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 686: {
-: 687: /*
-: 688: * The relation can't become interesting in the middle of the
-: 689: * transaction so it's safe to unlock it.
-: 690: */
#####: 691: logicalrep_rel_close(rel, RowExclusiveLock);
call 0 never executed
107: 692: return;
-: 693: }
-: 694:
-: 695: /* Check if we can do the update. */
107: 696: check_relation_updatable(rel);
call 0 returned 100%
-: 697:
-: 698: /* Initialize the executor state. */
107: 699: estate = create_estate_for_relation(rel);
call 0 returned 100%
107: 700: remoteslot = ExecInitExtraTupleSlot(estate,
call 0 returned 100%
107: 701: RelationGetDescr(rel->localrel),
-: 702: &TTSOpsVirtual);
107: 703: localslot = table_slot_create(rel->localrel,
call 0 returned 100%
-: 704: &estate->es_tupleTable);
107: 705: EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1);
call 0 returned 100%
-: 706:
107: 707: PushActiveSnapshot(GetTransactionSnapshot());
call 0 returned 100%
call 1 returned 100%
107: 708: ExecOpenIndices(estate->es_result_relation_info, false);
call 0 returned 100%
-: 709:
-: 710: /* Build the search tuple. */
107: 711: oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 returned 100%
call 3 returned 100%
107: 712: slot_store_cstrings(remoteslot, rel,
branch 0 taken 59% (fallthrough)
branch 1 taken 41%
call 2 returned 100%
-: 713: has_oldtup ? oldtup.values : newtup.values);
107: 714: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 715:
-: 716: /*
-: 717: * Try to find tuple using either replica identity index, primary key or
-: 718: * if needed, sequential scan.
-: 719: */
107: 720: idxoid = GetRelationIdentityOrPK(rel->localrel);
call 0 returned 100%
107: 721: Assert(OidIsValid(idxoid) ||
branch 0 taken 21% (fallthrough)
branch 1 taken 79%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 0% (fallthrough)
branch 5 taken 100%
call 6 never executed
-: 722: (rel->remoterel.replident == REPLICA_IDENTITY_FULL && has_oldtup));
-: 723:
107: 724: if (OidIsValid(idxoid))
branch 0 taken 79% (fallthrough)
branch 1 taken 21%
85: 725: found = RelationFindReplTupleByIndex(rel->localrel, idxoid,
call 0 returned 100%
-: 726: LockTupleExclusive,
-: 727: remoteslot, localslot);
-: 728: else
22: 729: found = RelationFindReplTupleSeq(rel->localrel, LockTupleExclusive,
call 0 returned 100%
-: 730: remoteslot, localslot);
-: 731:
107: 732: ExecClearTuple(remoteslot);
call 0 returned 100%
-: 733:
-: 734: /*
-: 735: * Tuple found.
-: 736: *
-: 737: * Note this will fail if there are other conflicting unique indexes.
-: 738: */
107: 739: if (found)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 740: {
-: 741: /* Process and store remote tuple in the slot */
107: 742: oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
call 2 never executed
call 3 returned 100%
107: 743: ExecCopySlot(remoteslot, localslot);
call 0 returned 100%
107: 744: slot_modify_cstrings(remoteslot, rel, newtup.values, newtup.changed);
call 0 returned 100%
107: 745: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 746:
107: 747: EvalPlanQualSetSlot(&epqstate, remoteslot);
-: 748:
-: 749: /* Do the actual update. */
107: 750: ExecSimpleRelationUpdate(estate, &epqstate, localslot, remoteslot);
call 0 returned 100%
-: 751: }
-: 752: else
-: 753: {
-: 754: /*
-: 755: * The tuple to be updated could not be found.
-: 756: *
-: 757: * TODO what to do here, change the log level to LOG perhaps?
-: 758: */
#####: 759: elog(DEBUG1,
call 0 never executed
call 1 never executed
-: 760: "logical replication did not find row for update "
-: 761: "in replication target relation \"%s\"",
-: 762: RelationGetRelationName(rel->localrel));
-: 763: }
-: 764:
-: 765: /* Cleanup. */
107: 766: ExecCloseIndices(estate->es_result_relation_info);
call 0 returned 100%
107: 767: PopActiveSnapshot();
call 0 returned 100%
-: 768:
-: 769: /* Handle queued AFTER triggers. */
107: 770: AfterTriggerEndQuery(estate);
call 0 returned 100%
-: 771:
107: 772: EvalPlanQualEnd(&epqstate);
call 0 returned 100%
107: 773: ExecResetTupleTable(estate->es_tupleTable, false);
call 0 returned 100%
107: 774: FreeExecutorState(estate);
call 0 returned 100%
-: 775:
107: 776: logicalrep_rel_close(rel, NoLock);
call 0 returned 100%
-: 777:
107: 778: CommandCounterIncrement();
call 0 returned 100%
-: 779:}
-: 780:
-: 781:/*
-: 782: * Handle DELETE message.
-: 783: *
-: 784: * TODO: FDW support
-: 785: */
-: 786:static void
function apply_handle_delete called 191 returned 100% blocks executed 86%
191: 787:apply_handle_delete(StringInfo s)
-: 788:{
-: 789: LogicalRepRelMapEntry *rel;
-: 790: LogicalRepTupleData oldtup;
-: 791: LogicalRepRelId relid;
-: 792: Oid idxoid;
-: 793: EState *estate;
-: 794: EPQState epqstate;
-: 795: TupleTableSlot *remoteslot;
-: 796: TupleTableSlot *localslot;
-: 797: bool found;
-: 798: MemoryContext oldctx;
-: 799:
191: 800: ensure_transaction();
call 0 returned 100%
-: 801:
191: 802: relid = logicalrep_read_delete(s, &oldtup);
call 0 returned 100%
191: 803: rel = logicalrep_rel_open(relid, RowExclusiveLock);
call 0 returned 100%
191: 804: if (!should_apply_changes_for_rel(rel))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 805: {
-: 806: /*
-: 807: * The relation can't become interesting in the middle of the
-: 808: * transaction so it's safe to unlock it.
-: 809: */
#####: 810: logicalrep_rel_close(rel, RowExclusiveLock);
call 0 never executed
191: 811: return;
-: 812: }
-: 813:
-: 814: /* Check if we can do the delete. */
191: 815: check_relation_updatable(rel);
call 0 returned 100%
-: 816:
-: 817: /* Initialize the executor state. */
191: 818: estate = create_estate_for_relation(rel);
call 0 returned 100%
191: 819: remoteslot = ExecInitExtraTupleSlot(estate,
call 0 returned 100%
191: 820: RelationGetDescr(rel->localrel),
-: 821: &TTSOpsVirtual);
191: 822: localslot = table_slot_create(rel->localrel,
call 0 returned 100%
-: 823: &estate->es_tupleTable);
191: 824: EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1);
call 0 returned 100%
-: 825:
191: 826: PushActiveSnapshot(GetTransactionSnapshot());
call 0 returned 100%
call 1 returned 100%
191: 827: ExecOpenIndices(estate->es_result_relation_info, false);
call 0 returned 100%
-: 828:
-: 829: /* Find the tuple using the replica identity index. */
191: 830: oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 returned 100%
call 3 returned 100%
191: 831: slot_store_cstrings(remoteslot, rel, oldtup.values);
call 0 returned 100%
191: 832: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 833:
-: 834: /*
-: 835: * Try to find tuple using either replica identity index, primary key or
-: 836: * if needed, sequential scan.
-: 837: */
191: 838: idxoid = GetRelationIdentityOrPK(rel->localrel);
call 0 returned 100%
191: 839: Assert(OidIsValid(idxoid) ||
branch 0 taken 52% (fallthrough)
branch 1 taken 48%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
call 4 never executed
-: 840: (rel->remoterel.replident == REPLICA_IDENTITY_FULL));
-: 841:
191: 842: if (OidIsValid(idxoid))
branch 0 taken 48% (fallthrough)
branch 1 taken 52%
91: 843: found = RelationFindReplTupleByIndex(rel->localrel, idxoid,
call 0 returned 100%
-: 844: LockTupleExclusive,
-: 845: remoteslot, localslot);
-: 846: else
100: 847: found = RelationFindReplTupleSeq(rel->localrel, LockTupleExclusive,
call 0 returned 100%
-: 848: remoteslot, localslot);
-: 849: /* If found delete it. */
191: 850: if (found)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 851: {
191: 852: EvalPlanQualSetSlot(&epqstate, localslot);
-: 853:
-: 854: /* Do the actual delete. */
191: 855: ExecSimpleRelationDelete(estate, &epqstate, localslot);
call 0 returned 100%
-: 856: }
-: 857: else
-: 858: {
-: 859: /* The tuple to be deleted could not be found. */
#####: 860: elog(DEBUG1,
call 0 never executed
call 1 never executed
-: 861: "logical replication could not find row for delete "
-: 862: "in replication target relation \"%s\"",
-: 863: RelationGetRelationName(rel->localrel));
-: 864: }
-: 865:
-: 866: /* Cleanup. */
191: 867: ExecCloseIndices(estate->es_result_relation_info);
call 0 returned 100%
191: 868: PopActiveSnapshot();
call 0 returned 100%
-: 869:
-: 870: /* Handle queued AFTER triggers. */
191: 871: AfterTriggerEndQuery(estate);
call 0 returned 100%
-: 872:
191: 873: EvalPlanQualEnd(&epqstate);
call 0 returned 100%
191: 874: ExecResetTupleTable(estate->es_tupleTable, false);
call 0 returned 100%
191: 875: FreeExecutorState(estate);
call 0 returned 100%
-: 876:
191: 877: logicalrep_rel_close(rel, NoLock);
call 0 returned 100%
-: 878:
191: 879: CommandCounterIncrement();
call 0 returned 100%
-: 880:}
-: 881:
-: 882:/*
-: 883: * Handle TRUNCATE message.
-: 884: *
-: 885: * TODO: FDW support
-: 886: */
-: 887:static void
function apply_handle_truncate called 5 returned 100% blocks executed 94%
5: 888:apply_handle_truncate(StringInfo s)
-: 889:{
5: 890: bool cascade = false;
5: 891: bool restart_seqs = false;
5: 892: List *remote_relids = NIL;
5: 893: List *remote_rels = NIL;
5: 894: List *rels = NIL;
5: 895: List *relids = NIL;
5: 896: List *relids_logged = NIL;
-: 897: ListCell *lc;
-: 898:
5: 899: ensure_transaction();
call 0 returned 100%
-: 900:
5: 901: remote_relids = logicalrep_read_truncate(s, &cascade, &restart_seqs);
call 0 returned 100%
-: 902:
11: 903: foreach(lc, remote_relids)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 55% (fallthrough)
branch 3 taken 45%
branch 4 taken 55%
branch 5 taken 45% (fallthrough)
-: 904: {
6: 905: LogicalRepRelId relid = lfirst_oid(lc);
-: 906: LogicalRepRelMapEntry *rel;
-: 907:
6: 908: rel = logicalrep_rel_open(relid, RowExclusiveLock);
call 0 returned 100%
6: 909: if (!should_apply_changes_for_rel(rel))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 910: {
-: 911: /*
-: 912: * The relation can't become interesting in the middle of the
-: 913: * transaction so it's safe to unlock it.
-: 914: */
#####: 915: logicalrep_rel_close(rel, RowExclusiveLock);
call 0 never executed
#####: 916: continue;
-: 917: }
-: 918:
6: 919: remote_rels = lappend(remote_rels, rel);
call 0 returned 100%
6: 920: rels = lappend(rels, rel->localrel);
call 0 returned 100%
6: 921: relids = lappend_oid(relids, rel->localreloid);
call 0 returned 100%
6: 922: if (RelationIsLogicallyLogged(rel->localrel))
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
call 4 returned 100%
branch 5 taken 100% (fallthrough)
branch 6 taken 0%
6: 923: relids_logged = lappend_oid(relids_logged, rel->localreloid);
call 0 returned 100%
-: 924: }
-: 925:
-: 926: /*
-: 927: * Even if we used CASCADE on the upstream master we explicitly default to
-: 928: * replaying changes without further cascading. This might be later
-: 929: * changeable with a user specified option.
-: 930: */
5: 931: ExecuteTruncateGuts(rels, relids, relids_logged, DROP_RESTRICT, restart_seqs);
call 0 returned 100%
-: 932:
11: 933: foreach(lc, remote_rels)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 55% (fallthrough)
branch 3 taken 45%
branch 4 taken 55%
branch 5 taken 45% (fallthrough)
-: 934: {
6: 935: LogicalRepRelMapEntry *rel = lfirst(lc);
-: 936:
6: 937: logicalrep_rel_close(rel, NoLock);
call 0 returned 100%
-: 938: }
-: 939:
5: 940: CommandCounterIncrement();
call 0 returned 100%
5: 941:}
-: 942:
-: 943:
-: 944:/*
-: 945: * Logical replication protocol message dispatcher.
-: 946: */
-: 947:static void
function apply_dispatch called 996 returned 99% blocks executed 68%
996: 948:apply_dispatch(StringInfo s)
-: 949:{
996: 950: char action = pq_getmsgbyte(s);
call 0 returned 100%
-: 951:
996: 952: switch (action)
branch 0 taken 14%
branch 1 taken 13%
branch 2 taken 37%
branch 3 taken 11%
branch 4 taken 19%
branch 5 taken 1%
branch 6 taken 4%
branch 7 taken 2%
branch 8 taken 0%
branch 9 taken 0%
-: 953: {
-: 954: /* BEGIN */
-: 955: case 'B':
135: 956: apply_handle_begin(s);
call 0 returned 100%
135: 957: break;
-: 958: /* COMMIT */
-: 959: case 'C':
134: 960: apply_handle_commit(s);
call 0 returned 100%
134: 961: break;
-: 962: /* INSERT */
-: 963: case 'I':
364: 964: apply_handle_insert(s);
call 0 returned 99%
363: 965: break;
-: 966: /* UPDATE */
-: 967: case 'U':
107: 968: apply_handle_update(s);
call 0 returned 100%
107: 969: break;
-: 970: /* DELETE */
-: 971: case 'D':
191: 972: apply_handle_delete(s);
call 0 returned 100%
191: 973: break;
-: 974: /* TRUNCATE */
-: 975: case 'T':
5: 976: apply_handle_truncate(s);
call 0 returned 100%
5: 977: break;
-: 978: /* RELATION */
-: 979: case 'R':
44: 980: apply_handle_relation(s);
call 0 returned 100%
44: 981: break;
-: 982: /* TYPE */
-: 983: case 'Y':
16: 984: apply_handle_type(s);
call 0 returned 100%
16: 985: break;
-: 986: /* ORIGIN */
-: 987: case 'O':
#####: 988: apply_handle_origin(s);
call 0 never executed
#####: 989: break;
-: 990: default:
#####: 991: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 992: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 993: errmsg("invalid logical replication message type \"%c\"", action)));
-: 994: }
995: 995:}
-: 996:
-: 997:/*
-: 998: * Figure out which write/flush positions to report to the walsender process.
-: 999: *
-: 1000: * We can't simply report back the last LSN the walsender sent us because the
-: 1001: * local transaction might not yet be flushed to disk locally. Instead we
-: 1002: * build a list that associates local with remote LSNs for every commit. When
-: 1003: * reporting back the flush position to the sender we iterate that list and
-: 1004: * check which entries on it are already locally flushed. Those we can report
-: 1005: * as having been flushed.
-: 1006: *
-: 1007: * The have_pending_txes is true if there are outstanding transactions that
-: 1008: * need to be flushed.
-: 1009: */
-: 1010:static void
function get_flush_position called 1900 returned 100% blocks executed 94%
1900: 1011:get_flush_position(XLogRecPtr *write, XLogRecPtr *flush,
-: 1012: bool *have_pending_txes)
-: 1013:{
-: 1014: dlist_mutable_iter iter;
1900: 1015: XLogRecPtr local_flush = GetFlushRecPtr();
call 0 returned 100%
-: 1016:
1900: 1017: *write = InvalidXLogRecPtr;
1900: 1018: *flush = InvalidXLogRecPtr;
-: 1019:
1999: 1020: dlist_foreach_modify(iter, &lsn_mapping)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 86%
branch 3 taken 14% (fallthrough)
-: 1021: {
1710: 1022: FlushPosition *pos =
1710: 1023: dlist_container(FlushPosition, node, iter.cur);
-: 1024:
1710: 1025: *write = pos->remote_end;
-: 1026:
1710: 1027: if (pos->local_end <= local_flush)
branch 0 taken 6% (fallthrough)
branch 1 taken 94%
-: 1028: {
99: 1029: *flush = pos->remote_end;
99: 1030: dlist_delete(iter.cur);
call 0 returned 100%
99: 1031: pfree(pos);
call 0 returned 100%
-: 1032: }
-: 1033: else
-: 1034: {
-: 1035: /*
-: 1036: * Don't want to uselessly iterate over the rest of the list which
-: 1037: * could potentially be long. Instead get the last element and
-: 1038: * grab the write position from there.
-: 1039: */
1611: 1040: pos = dlist_tail_element(FlushPosition, node,
call 0 returned 100%
-: 1041: &lsn_mapping);
1611: 1042: *write = pos->remote_end;
1611: 1043: *have_pending_txes = true;
3222: 1044: return;
-: 1045: }
-: 1046: }
-: 1047:
289: 1048: *have_pending_txes = !dlist_is_empty(&lsn_mapping);
call 0 returned 100%
-: 1049:}
-: 1050:
-: 1051:/*
-: 1052: * Store current remote/local lsn pair in the tracking list.
-: 1053: */
-: 1054:static void
function store_flush_position called 104 returned 100% blocks executed 100%
104: 1055:store_flush_position(XLogRecPtr remote_lsn)
-: 1056:{
-: 1057: FlushPosition *flushpos;
-: 1058:
-: 1059: /* Need to do this in permanent context */
104: 1060: MemoryContextSwitchTo(ApplyContext);
call 0 returned 100%
-: 1061:
-: 1062: /* Track commit lsn */
104: 1063: flushpos = (FlushPosition *) palloc(sizeof(FlushPosition));
call 0 returned 100%
104: 1064: flushpos->local_end = XactLastCommitEnd;
104: 1065: flushpos->remote_end = remote_lsn;
-: 1066:
104: 1067: dlist_push_tail(&lsn_mapping, &flushpos->node);
call 0 returned 100%
104: 1068: MemoryContextSwitchTo(ApplyMessageContext);
call 0 returned 100%
104: 1069:}
-: 1070:
-: 1071:
-: 1072:/* Update statistics of the worker. */
-: 1073:static void
function UpdateWorkerStats called 1844 returned 100% blocks executed 100%
1844: 1074:UpdateWorkerStats(XLogRecPtr last_lsn, TimestampTz send_time, bool reply)
-: 1075:{
1844: 1076: MyLogicalRepWorker->last_lsn = last_lsn;
1844: 1077: MyLogicalRepWorker->last_send_time = send_time;
1844: 1078: MyLogicalRepWorker->last_recv_time = GetCurrentTimestamp();
call 0 returned 100%
1844: 1079: if (reply)
branch 0 taken 46% (fallthrough)
branch 1 taken 54%
-: 1080: {
848: 1081: MyLogicalRepWorker->reply_lsn = last_lsn;
848: 1082: MyLogicalRepWorker->reply_time = send_time;
-: 1083: }
1844: 1084:}
-: 1085:
-: 1086:/*
-: 1087: * Apply main loop.
-: 1088: */
-: 1089:static void
function LogicalRepApplyLoop called 25 returned 0% blocks executed 84%
25: 1090:LogicalRepApplyLoop(XLogRecPtr last_received)
-: 1091:{
25: 1092: TimestampTz last_recv_timestamp = GetCurrentTimestamp();
call 0 returned 100%
-: 1093:
-: 1094: /*
-: 1095: * Init the ApplyMessageContext which we clean up after each replication
-: 1096: * protocol message.
-: 1097: */
25: 1098: ApplyMessageContext = AllocSetContextCreate(ApplyContext,
call 0 returned 100%
-: 1099: "ApplyMessageContext",
-: 1100: ALLOCSET_DEFAULT_SIZES);
-: 1101:
-: 1102: /* mark as idle, before starting to loop */
25: 1103: pgstat_report_activity(STATE_IDLE, NULL);
call 0 returned 100%
-: 1104:
-: 1105: for (;;)
-: 1106: {
1045: 1107: pgsocket fd = PGINVALID_SOCKET;
-: 1108: int rc;
-: 1109: int len;
1045: 1110: char *buf = NULL;
1045: 1111: bool endofstream = false;
1045: 1112: bool ping_sent = false;
-: 1113: long wait_time;
-: 1114:
1045: 1115: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1116:
1045: 1117: MemoryContextSwitchTo(ApplyMessageContext);
call 0 returned 100%
-: 1118:
1045: 1119: len = walrcv_receive(wrconn, &buf, &fd);
call 0 returned 99%
-: 1120:
1042: 1121: if (len != 0)
branch 0 taken 93% (fallthrough)
branch 1 taken 7%
-: 1122: {
-: 1123: /* Process the data */
-: 1124: for (;;)
-: 1125: {
2811: 1126: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1127:
2811: 1128: if (len == 0)
branch 0 taken 34% (fallthrough)
branch 1 taken 66%
-: 1129: {
965: 1130: break;
-: 1131: }
1846: 1132: else if (len < 0)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 1133: {
2: 1134: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 1135: (errmsg("data stream from publisher has ended")));
2: 1136: endofstream = true;
2: 1137: break;
-: 1138: }
-: 1139: else
-: 1140: {
-: 1141: int c;
-: 1142: StringInfoData s;
-: 1143:
-: 1144: /* Reset timeout. */
1844: 1145: last_recv_timestamp = GetCurrentTimestamp();
call 0 returned 100%
1844: 1146: ping_sent = false;
-: 1147:
-: 1148: /* Ensure we are reading the data into our memory context. */
1844: 1149: MemoryContextSwitchTo(ApplyMessageContext);
call 0 returned 100%
-: 1150:
1844: 1151: s.data = buf;
1844: 1152: s.len = len;
1844: 1153: s.cursor = 0;
1844: 1154: s.maxlen = -1;
-: 1155:
1844: 1156: c = pq_getmsgbyte(&s);
call 0 returned 100%
-: 1157:
1844: 1158: if (c == 'w')
branch 0 taken 54% (fallthrough)
branch 1 taken 46%
-: 1159: {
-: 1160: XLogRecPtr start_lsn;
-: 1161: XLogRecPtr end_lsn;
-: 1162: TimestampTz send_time;
-: 1163:
996: 1164: start_lsn = pq_getmsgint64(&s);
call 0 returned 100%
996: 1165: end_lsn = pq_getmsgint64(&s);
call 0 returned 100%
996: 1166: send_time = pq_getmsgint64(&s);
call 0 returned 100%
-: 1167:
996: 1168: if (last_received < start_lsn)
branch 0 taken 71% (fallthrough)
branch 1 taken 29%
708: 1169: last_received = start_lsn;
-: 1170:
996: 1171: if (last_received < end_lsn)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1172: last_received = end_lsn;
-: 1173:
996: 1174: UpdateWorkerStats(last_received, send_time, false);
call 0 returned 100%
-: 1175:
996: 1176: apply_dispatch(&s);
call 0 returned 99%
-: 1177: }
848: 1178: else if (c == 'k')
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1179: {
-: 1180: XLogRecPtr end_lsn;
-: 1181: TimestampTz timestamp;
-: 1182: bool reply_requested;
-: 1183:
848: 1184: end_lsn = pq_getmsgint64(&s);
call 0 returned 100%
848: 1185: timestamp = pq_getmsgint64(&s);
call 0 returned 100%
848: 1186: reply_requested = pq_getmsgbyte(&s);
call 0 returned 100%
-: 1187:
848: 1188: if (last_received < end_lsn)
branch 0 taken 8% (fallthrough)
branch 1 taken 92%
70: 1189: last_received = end_lsn;
-: 1190:
848: 1191: send_feedback(last_received, reply_requested, false);
call 0 returned 100%
848: 1192: UpdateWorkerStats(last_received, timestamp, true);
call 0 returned 100%
-: 1193: }
-: 1194: /* other message types are purposefully ignored */
-: 1195:
1843: 1196: MemoryContextReset(ApplyMessageContext);
call 0 returned 100%
-: 1197: }
-: 1198:
1843: 1199: len = walrcv_receive(wrconn, &buf, &fd);
call 0 returned 100%
1843: 1200: }
-: 1201: }
-: 1202:
-: 1203: /* confirm all writes so far */
1041: 1204: send_feedback(last_received, false, false);
call 0 returned 100%
-: 1205:
1041: 1206: if (!in_remote_transaction)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
-: 1207: {
-: 1208: /*
-: 1209: * If we didn't get any transactions for a while there might be
-: 1210: * unconsumed invalidation messages in the queue, consume them
-: 1211: * now.
-: 1212: */
1029: 1213: AcceptInvalidationMessages();
call 0 returned 100%
1029: 1214: maybe_reread_subscription();
call 0 returned 99%
-: 1215:
-: 1216: /* Process any table synchronization changes. */
1026: 1217: process_syncing_tables(last_received);
call 0 returned 99%
-: 1218: }
-: 1219:
-: 1220: /* Cleanup the memory. */
1035: 1221: MemoryContextResetAndDeleteChildren(ApplyMessageContext);
call 0 returned 100%
1035: 1222: MemoryContextSwitchTo(TopMemoryContext);
call 0 returned 100%
-: 1223:
-: 1224: /* Check if we need to exit the streaming loop. */
1035: 1225: if (endofstream)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 1226: {
-: 1227: TimeLineID tli;
-: 1228:
2: 1229: walrcv_endstreaming(wrconn, &tli);
call 0 returned 0%
#####: 1230: break;
-: 1231: }
-: 1232:
-: 1233: /*
-: 1234: * Wait for more data or latch. If we have unflushed transactions,
-: 1235: * wake up after WalWriterDelay to see if they've been flushed yet (in
-: 1236: * which case we should send a feedback message). Otherwise, there's
-: 1237: * no particular urgency about waking up unless we get data or a
-: 1238: * signal.
-: 1239: */
1033: 1240: if (!dlist_is_empty(&lsn_mapping))
call 0 returned 100%
branch 1 taken 82% (fallthrough)
branch 2 taken 18%
846: 1241: wait_time = WalWriterDelay;
-: 1242: else
187: 1243: wait_time = NAPTIME_PER_CYCLE;
-: 1244:
1033: 1245: rc = WaitLatchOrSocket(MyLatch,
call 0 returned 100%
-: 1246: WL_SOCKET_READABLE | WL_LATCH_SET |
-: 1247: WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-: 1248: fd, wait_time,
-: 1249: WAIT_EVENT_LOGICAL_APPLY_MAIN);
-: 1250:
1033: 1251: if (rc & WL_LATCH_SET)
branch 0 taken 7% (fallthrough)
branch 1 taken 93%
-: 1252: {
70: 1253: ResetLatch(MyLatch);
call 0 returned 100%
70: 1254: CHECK_FOR_INTERRUPTS();
branch 0 taken 19% (fallthrough)
branch 1 taken 81%
call 2 returned 0%
-: 1255: }
-: 1256:
1020: 1257: if (got_SIGHUP)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1258: {
#####: 1259: got_SIGHUP = false;
#####: 1260: ProcessConfigFile(PGC_SIGHUP);
call 0 never executed
-: 1261: }
-: 1262:
1020: 1263: if (rc & WL_TIMEOUT)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 1264: {
-: 1265: /*
-: 1266: * We didn't receive anything new. If we haven't heard anything
-: 1267: * from the server for more than wal_receiver_timeout / 2, ping
-: 1268: * the server. Also, if it's been longer than
-: 1269: * wal_receiver_status_interval since the last update we sent,
-: 1270: * send a status update to the master anyway, to report any
-: 1271: * progress in applying WAL.
-: 1272: */
11: 1273: bool requestReply = false;
-: 1274:
-: 1275: /*
-: 1276: * Check if time since last receive from standby has reached the
-: 1277: * configured limit.
-: 1278: */
11: 1279: if (wal_receiver_timeout > 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1280: {
11: 1281: TimestampTz now = GetCurrentTimestamp();
call 0 returned 100%
-: 1282: TimestampTz timeout;
-: 1283:
11: 1284: timeout =
11: 1285: TimestampTzPlusMilliseconds(last_recv_timestamp,
-: 1286: wal_receiver_timeout);
-: 1287:
11: 1288: if (now >= timeout)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1289: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 1290: (errmsg("terminating logical replication worker due to timeout")));
-: 1291:
-: 1292: /*
-: 1293: * We didn't receive anything new, for half of receiver
-: 1294: * replication timeout. Ping the server.
-: 1295: */
11: 1296: if (!ping_sent)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1297: {
11: 1298: timeout = TimestampTzPlusMilliseconds(last_recv_timestamp,
-: 1299: (wal_receiver_timeout / 2));
11: 1300: if (now >= timeout)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1301: {
#####: 1302: requestReply = true;
#####: 1303: ping_sent = true;
-: 1304: }
-: 1305: }
-: 1306: }
-: 1307:
11: 1308: send_feedback(last_received, requestReply, requestReply);
call 0 returned 100%
-: 1309: }
1020: 1310: }
#####: 1311:}
-: 1312:
-: 1313:/*
-: 1314: * Send a Standby Status Update message to server.
-: 1315: *
-: 1316: * 'recvpos' is the latest LSN we've received data to, force is set if we need
-: 1317: * to send a response to avoid timeouts.
-: 1318: */
-: 1319:static void
function send_feedback called 1900 returned 100% blocks executed 93%
1900: 1320:send_feedback(XLogRecPtr recvpos, bool force, bool requestReply)
-: 1321:{
-: 1322: static StringInfo reply_message = NULL;
-: 1323: static TimestampTz send_time = 0;
-: 1324:
-: 1325: static XLogRecPtr last_recvpos = InvalidXLogRecPtr;
-: 1326: static XLogRecPtr last_writepos = InvalidXLogRecPtr;
-: 1327: static XLogRecPtr last_flushpos = InvalidXLogRecPtr;
-: 1328:
-: 1329: XLogRecPtr writepos;
-: 1330: XLogRecPtr flushpos;
-: 1331: TimestampTz now;
-: 1332: bool have_pending_txes;
-: 1333:
-: 1334: /*
-: 1335: * If the user doesn't want status to be reported to the publisher, be
-: 1336: * sure to exit before doing anything at all.
-: 1337: */
1900: 1338: if (!force && wal_receiver_status_interval <= 0)
branch 0 taken 60% (fallthrough)
branch 1 taken 40%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
933: 1339: return;
-: 1340:
-: 1341: /* It's legal to not pass a recvpos */
1900: 1342: if (recvpos < last_recvpos)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1343: recvpos = last_recvpos;
-: 1344:
1900: 1345: get_flush_position(&writepos, &flushpos, &have_pending_txes);
call 0 returned 100%
-: 1346:
-: 1347: /*
-: 1348: * No outstanding transactions to flush, we can report the latest received
-: 1349: * position. This is important for synchronous replication.
-: 1350: */
1900: 1351: if (!have_pending_txes)
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
289: 1352: flushpos = writepos = recvpos;
-: 1353:
1900: 1354: if (writepos < last_writepos)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1355: writepos = last_writepos;
-: 1356:
1900: 1357: if (flushpos < last_flushpos)
branch 0 taken 84% (fallthrough)
branch 1 taken 16%
1599: 1358: flushpos = last_flushpos;
-: 1359:
1900: 1360: now = GetCurrentTimestamp();
call 0 returned 100%
-: 1361:
-: 1362: /* if we've already reported everything we're good */
3042: 1363: if (!force &&
branch 0 taken 60% (fallthrough)
branch 1 taken 40%
branch 2 taken 83% (fallthrough)
branch 3 taken 17%
2085: 1364: writepos == last_writepos &&
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
1880: 1365: flushpos == last_flushpos &&
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
937: 1366: !TimestampDifferenceExceeds(send_time, now,
call 0 returned 100%
-: 1367: wal_receiver_status_interval * 1000))
933: 1368: return;
967: 1369: send_time = now;
-: 1370:
967: 1371: if (!reply_message)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
-: 1372: {
25: 1373: MemoryContext oldctx = MemoryContextSwitchTo(ApplyContext);
call 0 returned 100%
-: 1374:
25: 1375: reply_message = makeStringInfo();
call 0 returned 100%
25: 1376: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 1377: }
-: 1378: else
942: 1379: resetStringInfo(reply_message);
call 0 returned 100%
-: 1380:
967: 1381: pq_sendbyte(reply_message, 'r');
call 0 returned 100%
967: 1382: pq_sendint64(reply_message, recvpos); /* write */
call 0 returned 100%
967: 1383: pq_sendint64(reply_message, flushpos); /* flush */
call 0 returned 100%
967: 1384: pq_sendint64(reply_message, writepos); /* apply */
call 0 returned 100%
967: 1385: pq_sendint64(reply_message, now); /* sendTime */
call 0 returned 100%
967: 1386: pq_sendbyte(reply_message, requestReply); /* replyRequested */
call 0 returned 100%
-: 1387:
967: 1388: elog(DEBUG2, "sending feedback (force %d) to recv %X/%X, write %X/%X, flush %X/%X",
call 0 returned 100%
call 1 returned 100%
-: 1389: force,
-: 1390: (uint32) (recvpos >> 32), (uint32) recvpos,
-: 1391: (uint32) (writepos >> 32), (uint32) writepos,
-: 1392: (uint32) (flushpos >> 32), (uint32) flushpos
-: 1393: );
-: 1394:
967: 1395: walrcv_send(wrconn, reply_message->data, reply_message->len);
call 0 returned 100%
-: 1396:
967: 1397: if (recvpos > last_recvpos)
branch 0 taken 21% (fallthrough)
branch 1 taken 79%
200: 1398: last_recvpos = recvpos;
967: 1399: if (writepos > last_writepos)
branch 0 taken 21% (fallthrough)
branch 1 taken 79%
200: 1400: last_writepos = writepos;
967: 1401: if (flushpos > last_flushpos)
branch 0 taken 14% (fallthrough)
branch 1 taken 86%
132: 1402: last_flushpos = flushpos;
-: 1403:}
-: 1404:
-: 1405:/*
-: 1406: * Reread subscription info if needed. Most changes will be exit.
-: 1407: */
-: 1408:static void
function maybe_reread_subscription called 1164 returned 99% blocks executed 66%
1164: 1409:maybe_reread_subscription(void)
-: 1410:{
-: 1411: MemoryContext oldctx;
-: 1412: Subscription *newsub;
1164: 1413: bool started_tx = false;
-: 1414:
-: 1415: /* When cache state is valid there is nothing to do here. */
1164: 1416: if (MySubscriptionValid)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
2318: 1417: return;
-: 1418:
-: 1419: /* This function might be called inside or outside of transaction. */
7: 1420: if (!IsTransactionState())
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
-: 1421: {
7: 1422: StartTransactionCommand();
call 0 returned 100%
7: 1423: started_tx = true;
-: 1424: }
-: 1425:
-: 1426: /* Ensure allocations in permanent context. */
7: 1427: oldctx = MemoryContextSwitchTo(ApplyContext);
call 0 returned 100%
-: 1428:
7: 1429: newsub = GetSubscription(MyLogicalRepWorker->subid, true);
call 0 returned 100%
-: 1430:
-: 1431: /*
-: 1432: * Exit if the subscription was removed. This normally should not happen
-: 1433: * as the worker gets killed during DROP SUBSCRIPTION.
-: 1434: */
7: 1435: if (!newsub)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1436: {
#####: 1437: ereport(LOG,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 1438: (errmsg("logical replication apply worker for subscription \"%s\" will "
-: 1439: "stop because the subscription was removed",
-: 1440: MySubscription->name)));
-: 1441:
#####: 1442: proc_exit(0);
call 0 never executed
-: 1443: }
-: 1444:
-: 1445: /*
-: 1446: * Exit if the subscription was disabled. This normally should not happen
-: 1447: * as the worker gets killed during ALTER SUBSCRIPTION ... DISABLE.
-: 1448: */
7: 1449: if (!newsub->enabled)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1450: {
#####: 1451: ereport(LOG,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 1452: (errmsg("logical replication apply worker for subscription \"%s\" will "
-: 1453: "stop because the subscription was disabled",
-: 1454: MySubscription->name)));
-: 1455:
#####: 1456: proc_exit(0);
call 0 never executed
-: 1457: }
-: 1458:
-: 1459: /*
-: 1460: * Exit if connection string was changed. The launcher will start new
-: 1461: * worker.
-: 1462: */
7: 1463: if (strcmp(newsub->conninfo, MySubscription->conninfo) != 0)
branch 0 taken 14% (fallthrough)
branch 1 taken 86%
-: 1464: {
1: 1465: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 1466: (errmsg("logical replication apply worker for subscription \"%s\" will "
-: 1467: "restart because the connection information was changed",
-: 1468: MySubscription->name)));
-: 1469:
1: 1470: proc_exit(0);
call 0 returned 0%
-: 1471: }
-: 1472:
-: 1473: /*
-: 1474: * Exit if subscription name was changed (it's used for
-: 1475: * fallback_application_name). The launcher will start new worker.
-: 1476: */
6: 1477: if (strcmp(newsub->name, MySubscription->name) != 0)
branch 0 taken 17% (fallthrough)
branch 1 taken 83%
-: 1478: {
1: 1479: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 1480: (errmsg("logical replication apply worker for subscription \"%s\" will "
-: 1481: "restart because subscription was renamed",
-: 1482: MySubscription->name)));
-: 1483:
1: 1484: proc_exit(0);
call 0 returned 0%
-: 1485: }
-: 1486:
-: 1487: /* !slotname should never happen when enabled is true. */
5: 1488: Assert(newsub->slotname);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1489:
-: 1490: /*
-: 1491: * We need to make new connection to new slot if slot name has changed so
-: 1492: * exit here as well if that's the case.
-: 1493: */
5: 1494: if (strcmp(newsub->slotname, MySubscription->slotname) != 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1495: {
#####: 1496: ereport(LOG,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 1497: (errmsg("logical replication apply worker for subscription \"%s\" will "
-: 1498: "restart because the replication slot name was changed",
-: 1499: MySubscription->name)));
-: 1500:
#####: 1501: proc_exit(0);
call 0 never executed
-: 1502: }
-: 1503:
-: 1504: /*
-: 1505: * Exit if publication list was changed. The launcher will start new
-: 1506: * worker.
-: 1507: */
5: 1508: if (!equal(newsub->publications, MySubscription->publications))
call 0 returned 100%
branch 1 taken 20% (fallthrough)
branch 2 taken 80%
-: 1509: {
1: 1510: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 1511: (errmsg("logical replication apply worker for subscription \"%s\" will "
-: 1512: "restart because subscription's publications were changed",
-: 1513: MySubscription->name)));
-: 1514:
1: 1515: proc_exit(0);
call 0 returned 0%
-: 1516: }
-: 1517:
-: 1518: /* Check for other changes that should never happen too. */
4: 1519: if (newsub->dbid != MySubscription->dbid)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1520: {
#####: 1521: elog(ERROR, "subscription %u changed unexpectedly",
call 0 never executed
call 1 never executed
call 2 never executed
-: 1522: MyLogicalRepWorker->subid);
-: 1523: }
-: 1524:
-: 1525: /* Clean old subscription info and switch to new one. */
4: 1526: FreeSubscription(MySubscription);
call 0 returned 100%
4: 1527: MySubscription = newsub;
-: 1528:
4: 1529: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 1530:
-: 1531: /* Change synchronous commit according to the user's wishes */
4: 1532: SetConfigOption("synchronous_commit", MySubscription->synccommit,
call 0 returned 100%
-: 1533: PGC_BACKEND, PGC_S_OVERRIDE);
-: 1534:
4: 1535: if (started_tx)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
4: 1536: CommitTransactionCommand();
call 0 returned 100%
-: 1537:
4: 1538: MySubscriptionValid = true;
-: 1539:}
-: 1540:
-: 1541:/*
-: 1542: * Callback from subscription syscache invalidation.
-: 1543: */
-: 1544:static void
function subscription_change_cb called 8 returned 100% blocks executed 100%
8: 1545:subscription_change_cb(Datum arg, int cacheid, uint32 hashvalue)
-: 1546:{
8: 1547: MySubscriptionValid = false;
8: 1548:}
-: 1549:
-: 1550:/* SIGHUP: set flag to reload configuration at next convenient time */
-: 1551:static void
function logicalrep_worker_sighup called 0 returned 0% blocks executed 0%
#####: 1552:logicalrep_worker_sighup(SIGNAL_ARGS)
-: 1553:{
#####: 1554: int save_errno = errno;
call 0 never executed
-: 1555:
#####: 1556: got_SIGHUP = true;
-: 1557:
-: 1558: /* Waken anything waiting on the process latch */
#####: 1559: SetLatch(MyLatch);
call 0 never executed
-: 1560:
#####: 1561: errno = save_errno;
call 0 never executed
#####: 1562:}
-: 1563:
-: 1564:/* Logical Replication Apply worker entry point */
-: 1565:void
function ApplyWorkerMain called 61 returned 0% blocks executed 74%
61: 1566:ApplyWorkerMain(Datum main_arg)
-: 1567:{
61: 1568: int worker_slot = DatumGetInt32(main_arg);
-: 1569: MemoryContext oldctx;
-: 1570: char originname[NAMEDATALEN];
-: 1571: XLogRecPtr origin_startpos;
-: 1572: char *myslotname;
-: 1573: WalRcvStreamOptions options;
-: 1574:
-: 1575: /* Attach to slot */
61: 1576: logicalrep_worker_attach(worker_slot);
call 0 returned 100%
-: 1577:
-: 1578: /* Setup signal handling */
61: 1579: pqsignal(SIGHUP, logicalrep_worker_sighup);
call 0 returned 100%
61: 1580: pqsignal(SIGTERM, die);
call 0 returned 100%
61: 1581: BackgroundWorkerUnblockSignals();
call 0 returned 100%
-: 1582:
-: 1583: /*
-: 1584: * We don't currently need any ResourceOwner in a walreceiver process, but
-: 1585: * if we did, we could call CreateAuxProcessResourceOwner here.
-: 1586: */
-: 1587:
-: 1588: /* Initialise stats to a sanish value */
122: 1589: MyLogicalRepWorker->last_send_time = MyLogicalRepWorker->last_recv_time =
61: 1590: MyLogicalRepWorker->reply_time = GetCurrentTimestamp();
call 0 returned 100%
-: 1591:
-: 1592: /* Load the libpq-specific functions */
61: 1593: load_file("libpqwalreceiver", false);
call 0 returned 100%
-: 1594:
-: 1595: /* Run as replica session replication role. */
61: 1596: SetConfigOption("session_replication_role", "replica",
call 0 returned 100%
-: 1597: PGC_SUSET, PGC_S_OVERRIDE);
-: 1598:
-: 1599: /* Connect to our database. */
61: 1600: BackgroundWorkerInitializeConnectionByOid(MyLogicalRepWorker->dbid,
call 0 returned 100%
61: 1601: MyLogicalRepWorker->userid,
-: 1602: 0);
-: 1603:
-: 1604: /* Load the subscription into persistent memory context. */
61: 1605: ApplyContext = AllocSetContextCreate(TopMemoryContext,
call 0 returned 100%
-: 1606: "ApplyContext",
-: 1607: ALLOCSET_DEFAULT_SIZES);
61: 1608: StartTransactionCommand();
call 0 returned 100%
61: 1609: oldctx = MemoryContextSwitchTo(ApplyContext);
call 0 returned 100%
-: 1610:
61: 1611: MySubscription = GetSubscription(MyLogicalRepWorker->subid, true);
call 0 returned 100%
61: 1612: if (!MySubscription)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1613: {
#####: 1614: ereport(LOG,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 1615: (errmsg("logical replication apply worker for subscription %u will not "
-: 1616: "start because the subscription was removed during startup",
-: 1617: MyLogicalRepWorker->subid)));
#####: 1618: proc_exit(0);
call 0 never executed
-: 1619: }
-: 1620:
61: 1621: MySubscriptionValid = true;
61: 1622: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 1623:
61: 1624: if (!MySubscription->enabled)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1625: {
#####: 1626: ereport(LOG,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 1627: (errmsg("logical replication apply worker for subscription \"%s\" will not "
-: 1628: "start because the subscription was disabled during startup",
-: 1629: MySubscription->name)));
-: 1630:
#####: 1631: proc_exit(0);
call 0 never executed
-: 1632: }
-: 1633:
-: 1634: /* Setup synchronous commit according to the user's wishes */
61: 1635: SetConfigOption("synchronous_commit", MySubscription->synccommit,
call 0 returned 100%
-: 1636: PGC_BACKEND, PGC_S_OVERRIDE);
-: 1637:
-: 1638: /* Keep us informed about subscription changes. */
61: 1639: CacheRegisterSyscacheCallback(SUBSCRIPTIONOID,
call 0 returned 100%
-: 1640: subscription_change_cb,
-: 1641: (Datum) 0);
-: 1642:
61: 1643: if (am_tablesync_worker())
call 0 returned 100%
branch 1 taken 61% (fallthrough)
branch 2 taken 39%
37: 1644: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 100%
-: 1645: (errmsg("logical replication table synchronization worker for subscription \"%s\", table \"%s\" has started",
-: 1646: MySubscription->name, get_rel_name(MyLogicalRepWorker->relid))));
-: 1647: else
24: 1648: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 1649: (errmsg("logical replication apply worker for subscription \"%s\" has started",
-: 1650: MySubscription->name)));
-: 1651:
61: 1652: CommitTransactionCommand();
call 0 returned 100%
-: 1653:
-: 1654: /* Connect to the origin and start the replication. */
61: 1655: elog(DEBUG1, "connecting to publisher using connection string \"%s\"",
call 0 returned 100%
call 1 returned 100%
-: 1656: MySubscription->conninfo);
-: 1657:
61: 1658: if (am_tablesync_worker())
call 0 returned 100%
branch 1 taken 61% (fallthrough)
branch 2 taken 39%
-: 1659: {
-: 1660: char *syncslotname;
-: 1661:
-: 1662: /* This is table synchronization worker, call initial sync. */
37: 1663: syncslotname = LogicalRepSyncTableStart(&origin_startpos);
call 0 returned 8%
-: 1664:
-: 1665: /* The slot name needs to be allocated in permanent memory context. */
3: 1666: oldctx = MemoryContextSwitchTo(ApplyContext);
call 0 returned 100%
3: 1667: myslotname = pstrdup(syncslotname);
call 0 returned 100%
3: 1668: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 1669:
3: 1670: pfree(syncslotname);
call 0 returned 100%
-: 1671: }
-: 1672: else
-: 1673: {
-: 1674: /* This is main apply worker */
-: 1675: RepOriginId originid;
-: 1676: TimeLineID startpointTLI;
-: 1677: char *err;
-: 1678:
24: 1679: myslotname = MySubscription->slotname;
-: 1680:
-: 1681: /*
-: 1682: * This shouldn't happen if the subscription is enabled, but guard
-: 1683: * against DDL bugs or manual catalog changes. (libpqwalreceiver will
-: 1684: * crash if slot is NULL.)
-: 1685: */
24: 1686: if (!myslotname)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1687: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 1688: (errmsg("subscription has no replication slot set")));
-: 1689:
-: 1690: /* Setup replication origin tracking. */
24: 1691: StartTransactionCommand();
call 0 returned 100%
24: 1692: snprintf(originname, sizeof(originname), "pg_%u", MySubscription->oid);
call 0 returned 100%
24: 1693: originid = replorigin_by_name(originname, true);
call 0 returned 100%
24: 1694: if (!OidIsValid(originid))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1695: originid = replorigin_create(originname);
call 0 never executed
24: 1696: replorigin_session_setup(originid);
call 0 returned 100%
24: 1697: replorigin_session_origin = originid;
24: 1698: origin_startpos = replorigin_session_get_progress(false);
call 0 returned 100%
24: 1699: CommitTransactionCommand();
call 0 returned 100%
-: 1700:
24: 1701: wrconn = walrcv_connect(MySubscription->conninfo, true, MySubscription->name,
call 0 returned 100%
-: 1702: &err);
24: 1703: if (wrconn == NULL)
branch 0 taken 8% (fallthrough)
branch 1 taken 92%
2: 1704: ereport(ERROR,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 0%
call 5 never executed
-: 1705: (errmsg("could not connect to the publisher: %s", err)));
-: 1706:
-: 1707: /*
-: 1708: * We don't really use the output identify_system for anything but it
-: 1709: * does some initializations on the upstream so let's still call it.
-: 1710: */
22: 1711: (void) walrcv_identify_system(wrconn, &startpointTLI);
call 0 returned 100%
-: 1712:
-: 1713: }
-: 1714:
-: 1715: /*
-: 1716: * Setup callback for syscache so that we know when something changes in
-: 1717: * the subscription relation state.
-: 1718: */
25: 1719: CacheRegisterSyscacheCallback(SUBSCRIPTIONRELMAP,
call 0 returned 100%
-: 1720: invalidate_syncing_table_states,
-: 1721: (Datum) 0);
-: 1722:
-: 1723: /* Build logical replication streaming options. */
25: 1724: options.logical = true;
25: 1725: options.startpoint = origin_startpos;
25: 1726: options.slotname = myslotname;
25: 1727: options.proto.logical.proto_version = LOGICALREP_PROTO_VERSION_NUM;
25: 1728: options.proto.logical.publication_names = MySubscription->publications;
-: 1729:
-: 1730: /* Start normal logical streaming replication. */
25: 1731: walrcv_startstreaming(wrconn, &options);
call 0 returned 100%
-: 1732:
-: 1733: /* Run the main loop. */
25: 1734: LogicalRepApplyLoop(origin_startpos);
call 0 returned 0%
-: 1735:
#####: 1736: proc_exit(0);
-: 1737:}
-: 1738:
-: 1739:/*
-: 1740: * Is current process a logical replication worker?
-: 1741: */
-: 1742:bool
function IsLogicalWorker called 154 returned 100% blocks executed 100%
154: 1743:IsLogicalWorker(void)
-: 1744:{
154: 1745: return MyLogicalRepWorker != NULL;
-: 1746:}
0001-Add-logical_decoding_work_mem-to-limit-ReorderBuffer-patch_coverage/reorderbuffer.c.gcov 0000664 0001750 0000000 00000626500 13560210457 031674 0 ustar vignesh root -: 0:Source:reorderbuffer.c
-: 0:Graph:./reorderbuffer.gcno
-: 0:Data:./reorderbuffer.gcda
-: 0:Runs:6532
-: 0:Programs:1
-: 1:/*-------------------------------------------------------------------------
-: 2: *
-: 3: * reorderbuffer.c
-: 4: * PostgreSQL logical replay/reorder buffer management
-: 5: *
-: 6: *
-: 7: * Copyright (c) 2012-2019, PostgreSQL Global Development Group
-: 8: *
-: 9: *
-: 10: * IDENTIFICATION
-: 11: * src/backend/replication/reorderbuffer.c
-: 12: *
-: 13: * NOTES
-: 14: * This module gets handed individual pieces of transactions in the order
-: 15: * they are written to the WAL and is responsible to reassemble them into
-: 16: * toplevel transaction sized pieces. When a transaction is completely
-: 17: * reassembled - signalled by reading the transaction commit record - it
-: 18: * will then call the output plugin (cf. ReorderBufferCommit()) with the
-: 19: * individual changes. The output plugins rely on snapshots built by
-: 20: * snapbuild.c which hands them to us.
-: 21: *
-: 22: * Transactions and subtransactions/savepoints in postgres are not
-: 23: * immediately linked to each other from outside the performing
-: 24: * backend. Only at commit/abort (or special xact_assignment records) they
-: 25: * are linked together. Which means that we will have to splice together a
-: 26: * toplevel transaction from its subtransactions. To do that efficiently we
-: 27: * build a binary heap indexed by the smallest current lsn of the individual
-: 28: * subtransactions' changestreams. As the individual streams are inherently
-: 29: * ordered by LSN - since that is where we build them from - the transaction
-: 30: * can easily be reassembled by always using the subtransaction with the
-: 31: * smallest current LSN from the heap.
-: 32: *
-: 33: * In order to cope with large transactions - which can be several times as
-: 34: * big as the available memory - this module supports spooling the contents
-: 35: * of a large transactions to disk. When the transaction is replayed the
-: 36: * contents of individual (sub-)transactions will be read from disk in
-: 37: * chunks.
-: 38: *
-: 39: * This module also has to deal with reassembling toast records from the
-: 40: * individual chunks stored in WAL. When a new (or initial) version of a
-: 41: * tuple is stored in WAL it will always be preceded by the toast chunks
-: 42: * emitted for the columns stored out of line. Within a single toplevel
-: 43: * transaction there will be no other data carrying records between a row's
-: 44: * toast chunks and the row data itself. See ReorderBufferToast* for
-: 45: * details.
-: 46: *
-: 47: * ReorderBuffer uses two special memory context types - SlabContext for
-: 48: * allocations of fixed-length structures (changes and transactions), and
-: 49: * GenerationContext for the variable-length transaction data (allocated
-: 50: * and freed in groups with similar lifespan).
-: 51: *
-: 52: * To limit the amount of memory used by decoded changes, we track memory
-: 53: * used at the reorder buffer level (i.e. total amount of memory), and for
-: 54: * each toplevel transaction. When the total amount of used memory exceeds
-: 55: * the limit, the toplevel transaction consuming the most memory is then
-: 56: * serialized to disk.
-: 57: *
-: 58: * Only decoded changes are evicted from memory (spilled to disk), not the
-: 59: * transaction records. The number of toplevel transactions is limited,
-: 60: * but a transaction with many subtransactions may still consume significant
-: 61: * amounts of memory. The transaction records are fairly small, though, and
-: 62: * are not included in the memory limit.
-: 63: *
-: 64: * The current eviction algorithm is very simple - the transaction is
-: 65: * picked merely by size, while it might be useful to also consider age
-: 66: * (LSN) of the changes for example. With the new Generational memory
-: 67: * allocator, evicting the oldest changes would make it more likely the
-: 68: * memory gets actually freed.
-: 69: *
-: 70: * We still rely on max_changes_in_memory when loading serialized changes
-: 71: * back into memory. At that point we can't use the memory limit directly
-: 72: * as we load the subxacts independently. One option do deal with this
-: 73: * would be to count the subxacts, and allow each to allocate 1/N of the
-: 74: * memory limit. That however does not seem very appealing, because with
-: 75: * many subtransactions it may easily cause trashing (short cycles of
-: 76: * deserializing and applying very few changes). We probably should give
-: 77: * a bit more memory to the oldest subtransactions, because it's likely
-: 78: * the source for the next sequence of changes.
-: 79: *
-: 80: * -------------------------------------------------------------------------
-: 81: */
-: 82:#include "postgres.h"
-: 83:
-: 84:#include <unistd.h>
-: 85:#include <sys/stat.h>
-: 86:
-: 87:#include "access/detoast.h"
-: 88:#include "access/heapam.h"
-: 89:#include "access/rewriteheap.h"
-: 90:#include "access/transam.h"
-: 91:#include "access/xact.h"
-: 92:#include "access/xlog_internal.h"
-: 93:#include "catalog/catalog.h"
-: 94:#include "lib/binaryheap.h"
-: 95:#include "miscadmin.h"
-: 96:#include "pgstat.h"
-: 97:#include "replication/logical.h"
-: 98:#include "replication/reorderbuffer.h"
-: 99:#include "replication/slot.h"
-: 100:#include "replication/snapbuild.h" /* just for SnapBuildSnapDecRefcount */
-: 101:#include "storage/bufmgr.h"
-: 102:#include "storage/fd.h"
-: 103:#include "storage/sinval.h"
-: 104:#include "utils/builtins.h"
-: 105:#include "utils/combocid.h"
-: 106:#include "utils/memdebug.h"
-: 107:#include "utils/memutils.h"
-: 108:#include "utils/rel.h"
-: 109:#include "utils/relfilenodemap.h"
-: 110:
-: 111:
-: 112:/* entry for a hash table we use to map from xid to our transaction state */
-: 113:typedef struct ReorderBufferTXNByIdEnt
-: 114:{
-: 115: TransactionId xid;
-: 116: ReorderBufferTXN *txn;
-: 117:} ReorderBufferTXNByIdEnt;
-: 118:
-: 119:/* data structures for (relfilenode, ctid) => (cmin, cmax) mapping */
-: 120:typedef struct ReorderBufferTupleCidKey
-: 121:{
-: 122: RelFileNode relnode;
-: 123: ItemPointerData tid;
-: 124:} ReorderBufferTupleCidKey;
-: 125:
-: 126:typedef struct ReorderBufferTupleCidEnt
-: 127:{
-: 128: ReorderBufferTupleCidKey key;
-: 129: CommandId cmin;
-: 130: CommandId cmax;
-: 131: CommandId combocid; /* just for debugging */
-: 132:} ReorderBufferTupleCidEnt;
-: 133:
-: 134:/* k-way in-order change iteration support structures */
-: 135:typedef struct ReorderBufferIterTXNEntry
-: 136:{
-: 137: XLogRecPtr lsn;
-: 138: ReorderBufferChange *change;
-: 139: ReorderBufferTXN *txn;
-: 140: int fd;
-: 141: XLogSegNo segno;
-: 142:} ReorderBufferIterTXNEntry;
-: 143:
-: 144:typedef struct ReorderBufferIterTXNState
-: 145:{
-: 146: binaryheap *heap;
-: 147: Size nr_txns;
-: 148: dlist_head old_change;
-: 149: ReorderBufferIterTXNEntry entries[FLEXIBLE_ARRAY_MEMBER];
-: 150:} ReorderBufferIterTXNState;
-: 151:
-: 152:/* toast datastructures */
-: 153:typedef struct ReorderBufferToastEnt
-: 154:{
-: 155: Oid chunk_id; /* toast_table.chunk_id */
-: 156: int32 last_chunk_seq; /* toast_table.chunk_seq of the last chunk we
-: 157: * have seen */
-: 158: Size num_chunks; /* number of chunks we've already seen */
-: 159: Size size; /* combined size of chunks seen */
-: 160: dlist_head chunks; /* linked list of chunks */
-: 161: struct varlena *reconstructed; /* reconstructed varlena now pointed to in
-: 162: * main tup */
-: 163:} ReorderBufferToastEnt;
-: 164:
-: 165:/* Disk serialization support datastructures */
-: 166:typedef struct ReorderBufferDiskChange
-: 167:{
-: 168: Size size;
-: 169: ReorderBufferChange change;
-: 170: /* data follows */
-: 171:} ReorderBufferDiskChange;
-: 172:
-: 173:/*
-: 174: * Maximum number of changes kept in memory, per transaction. After that,
-: 175: * changes are spooled to disk.
-: 176: *
-: 177: * The current value should be sufficient to decode the entire transaction
-: 178: * without hitting disk in OLTP workloads, while starting to spool to disk in
-: 179: * other workloads reasonably fast.
-: 180: *
-: 181: * At some point in the future it probably makes sense to have a more elaborate
-: 182: * resource management here, but it's not entirely clear what that would look
-: 183: * like.
-: 184: */
-: 185:int logical_decoding_work_mem;
-: 186:static const Size max_changes_in_memory = 4096; /* XXX for restore only */
-: 187:
-: 188:/* ---------------------------------------
-: 189: * primary reorderbuffer support routines
-: 190: * ---------------------------------------
-: 191: */
-: 192:static ReorderBufferTXN *ReorderBufferGetTXN(ReorderBuffer *rb);
-: 193:static void ReorderBufferReturnTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 194:static ReorderBufferTXN *ReorderBufferTXNByXid(ReorderBuffer *rb,
-: 195: TransactionId xid, bool create, bool *is_new,
-: 196: XLogRecPtr lsn, bool create_as_top);
-: 197:static void ReorderBufferTransferSnapToParent(ReorderBufferTXN *txn,
-: 198: ReorderBufferTXN *subtxn);
-: 199:
-: 200:static void AssertTXNLsnOrder(ReorderBuffer *rb);
-: 201:
-: 202:/* ---------------------------------------
-: 203: * support functions for lsn-order iterating over the ->changes of a
-: 204: * transaction and its subtransactions
-: 205: *
-: 206: * used for iteration over the k-way heap merge of a transaction and its
-: 207: * subtransactions
-: 208: * ---------------------------------------
-: 209: */
-: 210:static ReorderBufferIterTXNState *ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 211:static ReorderBufferChange *ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state);
-: 212:static void ReorderBufferIterTXNFinish(ReorderBuffer *rb,
-: 213: ReorderBufferIterTXNState *state);
-: 214:static void ReorderBufferExecuteInvalidations(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 215:
-: 216:/*
-: 217: * ---------------------------------------
-: 218: * Disk serialization support functions
-: 219: * ---------------------------------------
-: 220: */
-: 221:static void ReorderBufferCheckMemoryLimit(ReorderBuffer *rb);
-: 222:static void ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 223:static void ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 224: int fd, ReorderBufferChange *change);
-: 225:static Size ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 226: int *fd, XLogSegNo *segno);
-: 227:static void ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 228: char *change);
-: 229:static void ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 230:static void ReorderBufferCleanupSerializedTXNs(const char *slotname);
-: 231:static void ReorderBufferSerializedPath(char *path, ReplicationSlot *slot,
-: 232: TransactionId xid, XLogSegNo segno);
-: 233:
-: 234:static void ReorderBufferFreeSnap(ReorderBuffer *rb, Snapshot snap);
-: 235:static Snapshot ReorderBufferCopySnap(ReorderBuffer *rb, Snapshot orig_snap,
-: 236: ReorderBufferTXN *txn, CommandId cid);
-: 237:
-: 238:/* ---------------------------------------
-: 239: * toast reassembly support
-: 240: * ---------------------------------------
-: 241: */
-: 242:static void ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 243:static void ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 244:static void ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 245: Relation relation, ReorderBufferChange *change);
-: 246:static void ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 247: Relation relation, ReorderBufferChange *change);
-: 248:
-: 249:/*
-: 250: * ---------------------------------------
-: 251: * memory accounting
-: 252: * ---------------------------------------
-: 253: */
-: 254:static Size ReorderBufferChangeSize(ReorderBufferChange *change);
-: 255:static void ReorderBufferChangeMemoryUpdate(ReorderBuffer *rb,
-: 256: ReorderBufferChange *change, bool addition);
-: 257:
-: 258:/*
-: 259: * Allocate a new ReorderBuffer and clean out any old serialized state from
-: 260: * prior ReorderBuffer instances for the same slot.
-: 261: */
-: 262:ReorderBuffer *
function ReorderBufferAllocate called 276 returned 100% blocks executed 92%
276: 263:ReorderBufferAllocate(void)
-: 264:{
-: 265: ReorderBuffer *buffer;
-: 266: HASHCTL hash_ctl;
-: 267: MemoryContext new_ctx;
-: 268:
276: 269: Assert(MyReplicationSlot != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 270:
-: 271: /* allocate memory in own context, to have better accountability */
276: 272: new_ctx = AllocSetContextCreate(CurrentMemoryContext,
call 0 returned 100%
-: 273: "ReorderBuffer",
-: 274: ALLOCSET_DEFAULT_SIZES);
-: 275:
276: 276: buffer =
call 0 returned 100%
-: 277: (ReorderBuffer *) MemoryContextAlloc(new_ctx, sizeof(ReorderBuffer));
-: 278:
276: 279: memset(&hash_ctl, 0, sizeof(hash_ctl));
-: 280:
276: 281: buffer->context = new_ctx;
-: 282:
276: 283: buffer->change_context = SlabContextCreate(new_ctx,
call 0 returned 100%
-: 284: "Change",
-: 285: SLAB_DEFAULT_BLOCK_SIZE,
-: 286: sizeof(ReorderBufferChange));
-: 287:
276: 288: buffer->txn_context = SlabContextCreate(new_ctx,
call 0 returned 100%
-: 289: "TXN",
-: 290: SLAB_DEFAULT_BLOCK_SIZE,
-: 291: sizeof(ReorderBufferTXN));
-: 292:
276: 293: buffer->tup_context = GenerationContextCreate(new_ctx,
call 0 returned 100%
-: 294: "Tuples",
-: 295: SLAB_LARGE_BLOCK_SIZE);
-: 296:
276: 297: hash_ctl.keysize = sizeof(TransactionId);
276: 298: hash_ctl.entrysize = sizeof(ReorderBufferTXNByIdEnt);
276: 299: hash_ctl.hcxt = buffer->context;
-: 300:
276: 301: buffer->by_txn = hash_create("ReorderBufferByXid", 1000, &hash_ctl,
call 0 returned 100%
-: 302: HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
-: 303:
276: 304: buffer->by_txn_last_xid = InvalidTransactionId;
276: 305: buffer->by_txn_last_txn = NULL;
-: 306:
276: 307: buffer->outbuf = NULL;
276: 308: buffer->outbufsize = 0;
276: 309: buffer->size = 0;
-: 310:
276: 311: buffer->spillCount = 0;
276: 312: buffer->spillTxns = 0;
276: 313: buffer->spillBytes = 0;
-: 314:
276: 315: buffer->current_restart_decoding_lsn = InvalidXLogRecPtr;
-: 316:
276: 317: dlist_init(&buffer->toplevel_by_lsn);
call 0 returned 100%
276: 318: dlist_init(&buffer->txns_by_base_snapshot_lsn);
call 0 returned 100%
-: 319:
-: 320: /*
-: 321: * Ensure there's no stale data from prior uses of this slot, in case some
-: 322: * prior exit avoided calling ReorderBufferFree. Failure to do this can
-: 323: * produce duplicated txns, and it's very cheap if there's nothing there.
-: 324: */
276: 325: ReorderBufferCleanupSerializedTXNs(NameStr(MyReplicationSlot->data.name));
call 0 returned 100%
-: 326:
276: 327: return buffer;
-: 328:}
-: 329:
-: 330:/*
-: 331: * Free a ReorderBuffer
-: 332: */
-: 333:void
function ReorderBufferFree called 249 returned 100% blocks executed 100%
249: 334:ReorderBufferFree(ReorderBuffer *rb)
-: 335:{
249: 336: MemoryContext context = rb->context;
-: 337:
-: 338: /*
-: 339: * We free separately allocated data by entirely scrapping reorderbuffer's
-: 340: * memory context.
-: 341: */
249: 342: MemoryContextDelete(context);
call 0 returned 100%
-: 343:
-: 344: /* Free disk space used by unconsumed reorder buffers */
249: 345: ReorderBufferCleanupSerializedTXNs(NameStr(MyReplicationSlot->data.name));
call 0 returned 100%
249: 346:}
-: 347:
-: 348:/*
-: 349: * Get an unused, possibly preallocated, ReorderBufferTXN.
-: 350: */
-: 351:static ReorderBufferTXN *
function ReorderBufferGetTXN called 1995 returned 100% blocks executed 100%
1995: 352:ReorderBufferGetTXN(ReorderBuffer *rb)
-: 353:{
-: 354: ReorderBufferTXN *txn;
-: 355:
1995: 356: txn = (ReorderBufferTXN *)
call 0 returned 100%
1995: 357: MemoryContextAlloc(rb->txn_context, sizeof(ReorderBufferTXN));
-: 358:
1995: 359: memset(txn, 0, sizeof(ReorderBufferTXN));
-: 360:
1995: 361: dlist_init(&txn->changes);
call 0 returned 100%
1995: 362: dlist_init(&txn->tuplecids);
call 0 returned 100%
1995: 363: dlist_init(&txn->subtxns);
call 0 returned 100%
-: 364:
1995: 365: return txn;
-: 366:}
-: 367:
-: 368:/*
-: 369: * Free a ReorderBufferTXN.
-: 370: */
-: 371:static void
function ReorderBufferReturnTXN called 1989 returned 100% blocks executed 100%
1989: 372:ReorderBufferReturnTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 373:{
-: 374: /* clean the lookup cache if we were cached (quite likely) */
1989: 375: if (rb->by_txn_last_xid == txn->xid)
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
-: 376: {
1890: 377: rb->by_txn_last_xid = InvalidTransactionId;
1890: 378: rb->by_txn_last_txn = NULL;
-: 379: }
-: 380:
-: 381: /* free data that's contained */
-: 382:
1989: 383: if (txn->tuplecid_hash != NULL)
branch 0 taken 8% (fallthrough)
branch 1 taken 92%
-: 384: {
167: 385: hash_destroy(txn->tuplecid_hash);
call 0 returned 100%
167: 386: txn->tuplecid_hash = NULL;
-: 387: }
-: 388:
1989: 389: if (txn->invalidations)
branch 0 taken 23% (fallthrough)
branch 1 taken 77%
-: 390: {
451: 391: pfree(txn->invalidations);
call 0 returned 100%
451: 392: txn->invalidations = NULL;
-: 393: }
-: 394:
1989: 395: pfree(txn);
call 0 returned 100%
1989: 396:}
-: 397:
-: 398:/*
-: 399: * Get an fresh ReorderBufferChange.
-: 400: */
-: 401:ReorderBufferChange *
function ReorderBufferGetChange called 1357892 returned 100% blocks executed 100%
1357892: 402:ReorderBufferGetChange(ReorderBuffer *rb)
-: 403:{
-: 404: ReorderBufferChange *change;
-: 405:
1357892: 406: change = (ReorderBufferChange *)
call 0 returned 100%
1357892: 407: MemoryContextAlloc(rb->change_context, sizeof(ReorderBufferChange));
-: 408:
1357892: 409: memset(change, 0, sizeof(ReorderBufferChange));
1357892: 410: return change;
-: 411:}
-: 412:
-: 413:/*
-: 414: * Free an ReorderBufferChange.
-: 415: */
-: 416:void
function ReorderBufferReturnChange called 1357888 returned 100% blocks executed 100%
1357888: 417:ReorderBufferReturnChange(ReorderBuffer *rb, ReorderBufferChange *change)
-: 418:{
-: 419: /* update memory accounting info */
1357888: 420: ReorderBufferChangeMemoryUpdate(rb, change, false);
call 0 returned 100%
-: 421:
-: 422: /* free contained data */
1357888: 423: switch (change->action)
branch 0 taken 96%
branch 1 taken 1%
branch 2 taken 1%
branch 3 taken 1%
branch 4 taken 3%
branch 5 taken 0%
-: 424: {
-: 425: case REORDER_BUFFER_CHANGE_INSERT:
-: 426: case REORDER_BUFFER_CHANGE_UPDATE:
-: 427: case REORDER_BUFFER_CHANGE_DELETE:
-: 428: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
1310341: 429: if (change->data.tp.newtuple)
branch 0 taken 90% (fallthrough)
branch 1 taken 10%
-: 430: {
1175572: 431: ReorderBufferReturnTupleBuf(rb, change->data.tp.newtuple);
call 0 returned 100%
1175572: 432: change->data.tp.newtuple = NULL;
-: 433: }
-: 434:
1310341: 435: if (change->data.tp.oldtuple)
branch 0 taken 5% (fallthrough)
branch 1 taken 95%
-: 436: {
66017: 437: ReorderBufferReturnTupleBuf(rb, change->data.tp.oldtuple);
call 0 returned 100%
66017: 438: change->data.tp.oldtuple = NULL;
-: 439: }
1310341: 440: break;
-: 441: case REORDER_BUFFER_CHANGE_MESSAGE:
23: 442: if (change->data.msg.prefix != NULL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
23: 443: pfree(change->data.msg.prefix);
call 0 returned 100%
23: 444: change->data.msg.prefix = NULL;
23: 445: if (change->data.msg.message != NULL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
23: 446: pfree(change->data.msg.message);
call 0 returned 100%
23: 447: change->data.msg.message = NULL;
23: 448: break;
-: 449: case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
481: 450: if (change->data.snapshot)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 451: {
481: 452: ReorderBufferFreeSnap(rb, change->data.snapshot);
call 0 returned 100%
481: 453: change->data.snapshot = NULL;
-: 454: }
481: 455: break;
-: 456: /* no data in addition to the struct itself */
-: 457: case REORDER_BUFFER_CHANGE_TRUNCATE:
8: 458: if (change->data.truncate.relids != NULL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 459: {
8: 460: ReorderBufferReturnRelids(rb, change->data.truncate.relids);
call 0 returned 100%
8: 461: change->data.truncate.relids = NULL;
-: 462: }
8: 463: break;
-: 464: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
-: 465: case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
-: 466: case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
47035: 467: break;
-: 468: }
-: 469:
1357888: 470: pfree(change);
call 0 returned 100%
1357888: 471:}
-: 472:
-: 473:/*
-: 474: * Get a fresh ReorderBufferTupleBuf fitting at least a tuple of size
-: 475: * tuple_len (excluding header overhead).
-: 476: */
-: 477:ReorderBufferTupleBuf *
function ReorderBufferGetTupleBuf called 1241591 returned 100% blocks executed 100%
1241591: 478:ReorderBufferGetTupleBuf(ReorderBuffer *rb, Size tuple_len)
-: 479:{
-: 480: ReorderBufferTupleBuf *tuple;
-: 481: Size alloc_len;
-: 482:
1241591: 483: alloc_len = tuple_len + SizeofHeapTupleHeader;
-: 484:
1241591: 485: tuple = (ReorderBufferTupleBuf *)
call 0 returned 100%
1241591: 486: MemoryContextAlloc(rb->tup_context,
-: 487: sizeof(ReorderBufferTupleBuf) +
-: 488: MAXIMUM_ALIGNOF + alloc_len);
1241591: 489: tuple->alloc_tuple_size = alloc_len;
1241591: 490: tuple->tuple.t_data = ReorderBufferTupleBufData(tuple);
-: 491:
1241591: 492: return tuple;
-: 493:}
-: 494:
-: 495:/*
-: 496: * Free an ReorderBufferTupleBuf.
-: 497: */
-: 498:void
function ReorderBufferReturnTupleBuf called 1241589 returned 100% blocks executed 100%
1241589: 499:ReorderBufferReturnTupleBuf(ReorderBuffer *rb, ReorderBufferTupleBuf *tuple)
-: 500:{
1241589: 501: pfree(tuple);
call 0 returned 100%
1241589: 502:}
-: 503:
-: 504:/*
-: 505: * Get an array for relids of truncated relations.
-: 506: *
-: 507: * We use the global memory context (for the whole reorder buffer), because
-: 508: * none of the existing ones seems like a good match (some are SLAB, so we
-: 509: * can't use those, and tup_context is meant for tuple data, not relids). We
-: 510: * could add yet another context, but it seems like an overkill - TRUNCATE is
-: 511: * not particularly common operation, so it does not seem worth it.
-: 512: */
-: 513:Oid *
function ReorderBufferGetRelids called 8 returned 100% blocks executed 100%
8: 514:ReorderBufferGetRelids(ReorderBuffer *rb, int nrelids)
-: 515:{
-: 516: Oid *relids;
-: 517: Size alloc_len;
-: 518:
8: 519: alloc_len = sizeof(Oid) * nrelids;
-: 520:
8: 521: relids = (Oid *) MemoryContextAlloc(rb->context, alloc_len);
call 0 returned 100%
-: 522:
8: 523: return relids;
-: 524:}
-: 525:
-: 526:/*
-: 527: * Free an array of relids.
-: 528: */
-: 529:void
function ReorderBufferReturnRelids called 8 returned 100% blocks executed 100%
8: 530:ReorderBufferReturnRelids(ReorderBuffer *rb, Oid *relids)
-: 531:{
8: 532: pfree(relids);
call 0 returned 100%
8: 533:}
-: 534:
-: 535:/*
-: 536: * Return the ReorderBufferTXN from the given buffer, specified by Xid.
-: 537: * If create is true, and a transaction doesn't already exist, create it
-: 538: * (with the given LSN, and as top transaction if that's specified);
-: 539: * when this happens, is_new is set to true.
-: 540: */
-: 541:static ReorderBufferTXN *
function ReorderBufferTXNByXid called 4354640 returned 100% blocks executed 88%
4354640: 542:ReorderBufferTXNByXid(ReorderBuffer *rb, TransactionId xid, bool create,
-: 543: bool *is_new, XLogRecPtr lsn, bool create_as_top)
-: 544:{
-: 545: ReorderBufferTXN *txn;
-: 546: ReorderBufferTXNByIdEnt *ent;
-: 547: bool found;
-: 548:
4354640: 549: Assert(TransactionIdIsValid(xid));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 550:
-: 551: /*
-: 552: * Check the one-entry lookup cache first
-: 553: */
8707389: 554: if (TransactionIdIsValid(rb->by_txn_last_xid) &&
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 84% (fallthrough)
branch 3 taken 16%
4352749: 555: rb->by_txn_last_xid == xid)
-: 556: {
3677378: 557: txn = rb->by_txn_last_txn;
-: 558:
3677378: 559: if (txn != NULL)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
-: 560: {
-: 561: /* found it, and it's valid */
3677375: 562: if (is_new)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
1267: 563: *is_new = false;
3677375: 564: return txn;
-: 565: }
-: 566:
-: 567: /*
-: 568: * cached as non-existent, and asked not to create? Then nothing else
-: 569: * to do.
-: 570: */
3: 571: if (!create)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
3: 572: return NULL;
-: 573: /* otherwise fall through to create it */
-: 574: }
-: 575:
-: 576: /*
-: 577: * If the cache wasn't hit or it yielded an "does-not-exist" and we want
-: 578: * to create an entry.
-: 579: */
-: 580:
-: 581: /* search the lookup table */
677262: 582: ent = (ReorderBufferTXNByIdEnt *)
call 0 returned 100%
677262: 583: hash_search(rb->by_txn,
-: 584: (void *) &xid,
-: 585: create ? HASH_ENTER : HASH_FIND,
-: 586: &found);
677262: 587: if (found)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
675038: 588: txn = ent->txn;
2224: 589: else if (create)
branch 0 taken 90% (fallthrough)
branch 1 taken 10%
-: 590: {
-: 591: /* initialize the new entry, if creation was requested */
1995: 592: Assert(ent != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1995: 593: Assert(lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 594:
1995: 595: ent->txn = ReorderBufferGetTXN(rb);
call 0 returned 100%
1995: 596: ent->txn->xid = xid;
1995: 597: txn = ent->txn;
1995: 598: txn->first_lsn = lsn;
1995: 599: txn->restart_decoding_lsn = rb->current_restart_decoding_lsn;
-: 600:
1995: 601: if (create_as_top)
branch 0 taken 69% (fallthrough)
branch 1 taken 31%
-: 602: {
1382: 603: dlist_push_tail(&rb->toplevel_by_lsn, &txn->node);
call 0 returned 100%
1382: 604: AssertTXNLsnOrder(rb);
call 0 returned 100%
-: 605: }
-: 606: }
-: 607: else
229: 608: txn = NULL; /* not found and not asked to create */
-: 609:
-: 610: /* update cache */
677262: 611: rb->by_txn_last_xid = xid;
677262: 612: rb->by_txn_last_txn = txn;
-: 613:
677262: 614: if (is_new)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
1516: 615: *is_new = !found;
-: 616:
677262: 617: Assert(!create || txn != NULL);
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
call 4 never executed
677262: 618: return txn;
-: 619:}
-: 620:
-: 621:/*
-: 622: * Queue a change into a transaction so it can be replayed upon commit.
-: 623: */
-: 624:void
function ReorderBufferQueueChange called 1197196 returned 100% blocks executed 86%
1197196: 625:ReorderBufferQueueChange(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn,
-: 626: ReorderBufferChange *change)
-: 627:{
-: 628: ReorderBufferTXN *txn;
-: 629:
1197196: 630: txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
call 0 returned 100%
-: 631:
1197196: 632: change->lsn = lsn;
1197196: 633: change->txn = txn;
-: 634:
1197196: 635: Assert(InvalidXLogRecPtr != lsn);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1197196: 636: dlist_push_tail(&txn->changes, &change->node);
call 0 returned 100%
1197196: 637: txn->nentries++;
1197196: 638: txn->nentries_mem++;
-: 639:
-: 640: /* update memory accounting information */
1197196: 641: ReorderBufferChangeMemoryUpdate(rb, change, true);
call 0 returned 100%
-: 642:
-: 643: /* check the memory limits and evict something if needed */
1197196: 644: ReorderBufferCheckMemoryLimit(rb);
call 0 returned 100%
1197196: 645:}
-: 646:
-: 647:/*
-: 648: * Queue message into a transaction so it can be processed upon commit.
-: 649: */
-: 650:void
function ReorderBufferQueueMessage called 25 returned 100% blocks executed 82%
25: 651:ReorderBufferQueueMessage(ReorderBuffer *rb, TransactionId xid,
-: 652: Snapshot snapshot, XLogRecPtr lsn,
-: 653: bool transactional, const char *prefix,
-: 654: Size message_size, const char *message)
-: 655:{
25: 656: if (transactional)
branch 0 taken 88% (fallthrough)
branch 1 taken 12%
-: 657: {
-: 658: MemoryContext oldcontext;
-: 659: ReorderBufferChange *change;
-: 660:
22: 661: Assert(xid != InvalidTransactionId);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 662:
22: 663: oldcontext = MemoryContextSwitchTo(rb->context);
call 0 returned 100%
-: 664:
22: 665: change = ReorderBufferGetChange(rb);
call 0 returned 100%
22: 666: change->action = REORDER_BUFFER_CHANGE_MESSAGE;
22: 667: change->data.msg.prefix = pstrdup(prefix);
call 0 returned 100%
22: 668: change->data.msg.message_size = message_size;
22: 669: change->data.msg.message = palloc(message_size);
call 0 returned 100%
22: 670: memcpy(change->data.msg.message, message, message_size);
-: 671:
22: 672: ReorderBufferQueueChange(rb, xid, lsn, change);
call 0 returned 100%
-: 673:
22: 674: MemoryContextSwitchTo(oldcontext);
call 0 returned 100%
-: 675: }
-: 676: else
-: 677: {
3: 678: ReorderBufferTXN *txn = NULL;
3: 679: volatile Snapshot snapshot_now = snapshot;
-: 680:
3: 681: if (xid != InvalidTransactionId)
branch 0 taken 67% (fallthrough)
branch 1 taken 33%
2: 682: txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
call 0 returned 100%
-: 683:
-: 684: /* setup snapshot to allow catalog access */
3: 685: SetupHistoricSnapshot(snapshot_now, NULL);
call 0 returned 100%
3: 686: PG_TRY();
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
-: 687: {
3: 688: rb->message(rb, txn, lsn, false, prefix, message_size, message);
call 0 returned 100%
-: 689:
3: 690: TeardownHistoricSnapshot(false);
call 0 returned 100%
-: 691: }
#####: 692: PG_CATCH();
-: 693: {
#####: 694: TeardownHistoricSnapshot(true);
call 0 never executed
#####: 695: PG_RE_THROW();
call 0 never executed
-: 696: }
3: 697: PG_END_TRY();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 698: }
25: 699:}
-: 700:
-: 701:/*
-: 702: * AssertTXNLsnOrder
-: 703: * Verify LSN ordering of transaction lists in the reorderbuffer
-: 704: *
-: 705: * Other LSN-related invariants are checked too.
-: 706: *
-: 707: * No-op if assertions are not in use.
-: 708: */
-: 709:static void
function AssertTXNLsnOrder called 3526 returned 100% blocks executed 66%
3526: 710:AssertTXNLsnOrder(ReorderBuffer *rb)
-: 711:{
-: 712:#ifdef USE_ASSERT_CHECKING
-: 713: dlist_iter iter;
3526: 714: XLogRecPtr prev_first_lsn = InvalidXLogRecPtr;
3526: 715: XLogRecPtr prev_base_snap_lsn = InvalidXLogRecPtr;
-: 716:
7288: 717: dlist_foreach(iter, &rb->toplevel_by_lsn)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 52%
branch 3 taken 48% (fallthrough)
-: 718: {
3762: 719: ReorderBufferTXN *cur_txn = dlist_container(ReorderBufferTXN, node,
-: 720: iter.cur);
-: 721:
-: 722: /* start LSN must be set */
3762: 723: Assert(cur_txn->first_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 724:
-: 725: /* If there is an end LSN, it must be higher than start LSN */
3762: 726: if (cur_txn->end_lsn != InvalidXLogRecPtr)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 727: Assert(cur_txn->first_lsn <= cur_txn->end_lsn);
branch 0 never executed
branch 1 never executed
call 2 never executed
-: 728:
-: 729: /* Current initial LSN must be strictly higher than previous */
3762: 730: if (prev_first_lsn != InvalidXLogRecPtr)
branch 0 taken 10% (fallthrough)
branch 1 taken 90%
369: 731: Assert(prev_first_lsn < cur_txn->first_lsn);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 732:
-: 733: /* known-as-subtxn txns must not be listed */
3762: 734: Assert(!cur_txn->is_known_as_subxact);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 735:
3762: 736: prev_first_lsn = cur_txn->first_lsn;
-: 737: }
-: 738:
5253: 739: dlist_foreach(iter, &rb->txns_by_base_snapshot_lsn)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 33%
branch 3 taken 67% (fallthrough)
-: 740: {
1727: 741: ReorderBufferTXN *cur_txn = dlist_container(ReorderBufferTXN,
-: 742: base_snapshot_node,
-: 743: iter.cur);
-: 744:
-: 745: /* base snapshot (and its LSN) must be set */
1727: 746: Assert(cur_txn->base_snapshot != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1727: 747: Assert(cur_txn->base_snapshot_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 748:
-: 749: /* current LSN must be strictly higher than previous */
1727: 750: if (prev_base_snap_lsn != InvalidXLogRecPtr)
branch 0 taken 12% (fallthrough)
branch 1 taken 88%
211: 751: Assert(prev_base_snap_lsn < cur_txn->base_snapshot_lsn);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 752:
-: 753: /* known-as-subtxn txns must not be listed */
1727: 754: Assert(!cur_txn->is_known_as_subxact);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 755:
1727: 756: prev_base_snap_lsn = cur_txn->base_snapshot_lsn;
-: 757: }
-: 758:#endif
3526: 759:}
-: 760:
-: 761:/*
-: 762: * ReorderBufferGetOldestTXN
-: 763: * Return oldest transaction in reorderbuffer
-: 764: */
-: 765:ReorderBufferTXN *
function ReorderBufferGetOldestTXN called 73 returned 100% blocks executed 82%
73: 766:ReorderBufferGetOldestTXN(ReorderBuffer *rb)
-: 767:{
-: 768: ReorderBufferTXN *txn;
-: 769:
73: 770: AssertTXNLsnOrder(rb);
call 0 returned 100%
-: 771:
73: 772: if (dlist_is_empty(&rb->toplevel_by_lsn))
call 0 returned 100%
branch 1 taken 88% (fallthrough)
branch 2 taken 12%
64: 773: return NULL;
-: 774:
9: 775: txn = dlist_head_element(ReorderBufferTXN, node, &rb->toplevel_by_lsn);
call 0 returned 100%
-: 776:
9: 777: Assert(!txn->is_known_as_subxact);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
9: 778: Assert(txn->first_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
9: 779: return txn;
-: 780:}
-: 781:
-: 782:/*
-: 783: * ReorderBufferGetOldestXmin
-: 784: * Return oldest Xmin in reorderbuffer
-: 785: *
-: 786: * Returns oldest possibly running Xid from the point of view of snapshots
-: 787: * used in the transactions kept by reorderbuffer, or InvalidTransactionId if
-: 788: * there are none.
-: 789: *
-: 790: * Since snapshots are assigned monotonically, this equals the Xmin of the
-: 791: * base snapshot with minimal base_snapshot_lsn.
-: 792: */
-: 793:TransactionId
function ReorderBufferGetOldestXmin called 80 returned 100% blocks executed 100%
80: 794:ReorderBufferGetOldestXmin(ReorderBuffer *rb)
-: 795:{
-: 796: ReorderBufferTXN *txn;
-: 797:
80: 798: AssertTXNLsnOrder(rb);
call 0 returned 100%
-: 799:
80: 800: if (dlist_is_empty(&rb->txns_by_base_snapshot_lsn))
call 0 returned 100%
branch 1 taken 89% (fallthrough)
branch 2 taken 11%
71: 801: return InvalidTransactionId;
-: 802:
9: 803: txn = dlist_head_element(ReorderBufferTXN, base_snapshot_node,
call 0 returned 100%
-: 804: &rb->txns_by_base_snapshot_lsn);
9: 805: return txn->base_snapshot->xmin;
-: 806:}
-: 807:
-: 808:void
function ReorderBufferSetRestartPoint called 80 returned 100% blocks executed 100%
80: 809:ReorderBufferSetRestartPoint(ReorderBuffer *rb, XLogRecPtr ptr)
-: 810:{
80: 811: rb->current_restart_decoding_lsn = ptr;
80: 812:}
-: 813:
-: 814:/*
-: 815: * ReorderBufferAssignChild
-: 816: *
-: 817: * Make note that we know that subxid is a subtransaction of xid, seen as of
-: 818: * the given lsn.
-: 819: */
-: 820:void
function ReorderBufferAssignChild called 712 returned 100% blocks executed 78%
712: 821:ReorderBufferAssignChild(ReorderBuffer *rb, TransactionId xid,
-: 822: TransactionId subxid, XLogRecPtr lsn)
-: 823:{
-: 824: ReorderBufferTXN *txn;
-: 825: ReorderBufferTXN *subtxn;
-: 826: bool new_top;
-: 827: bool new_sub;
-: 828:
712: 829: txn = ReorderBufferTXNByXid(rb, xid, true, &new_top, lsn, true);
call 0 returned 100%
712: 830: subtxn = ReorderBufferTXNByXid(rb, subxid, true, &new_sub, lsn, false);
call 0 returned 100%
-: 831:
712: 832: if (new_top && !new_sub)
branch 0 taken 14% (fallthrough)
branch 1 taken 86%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 833: elog(ERROR, "subtransaction logged without previous top-level txn record");
call 0 never executed
call 1 never executed
call 2 never executed
-: 834:
712: 835: if (!new_sub)
branch 0 taken 14% (fallthrough)
branch 1 taken 86%
-: 836: {
99: 837: if (subtxn->is_known_as_subxact)
branch 0 taken 81% (fallthrough)
branch 1 taken 19%
-: 838: {
-: 839: /* already associated, nothing to do */
792: 840: return;
-: 841: }
-: 842: else
-: 843: {
-: 844: /*
-: 845: * We already saw this transaction, but initially added it to the
-: 846: * list of top-level txns. Now that we know it's not top-level,
-: 847: * remove it from there.
-: 848: */
19: 849: dlist_delete(&subtxn->node);
call 0 returned 100%
-: 850: }
-: 851: }
-: 852:
632: 853: subtxn->is_known_as_subxact = true;
632: 854: subtxn->toplevel_xid = xid;
632: 855: Assert(subtxn->nsubtxns == 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 856:
-: 857: /* add to subtransaction list */
632: 858: dlist_push_tail(&txn->subtxns, &subtxn->node);
call 0 returned 100%
632: 859: txn->nsubtxns++;
-: 860:
-: 861: /* Possibly transfer the subtxn's snapshot to its top-level txn. */
632: 862: ReorderBufferTransferSnapToParent(txn, subtxn);
call 0 returned 100%
-: 863:
-: 864: /* Verify LSN-ordering invariant */
632: 865: AssertTXNLsnOrder(rb);
call 0 returned 100%
-: 866:}
-: 867:
-: 868:/*
-: 869: * ReorderBufferTransferSnapToParent
-: 870: * Transfer base snapshot from subtxn to top-level txn, if needed
-: 871: *
-: 872: * This is done if the top-level txn doesn't have a base snapshot, or if the
-: 873: * subtxn's base snapshot has an earlier LSN than the top-level txn's base
-: 874: * snapshot's LSN. This can happen if there are no changes in the toplevel
-: 875: * txn but there are some in the subtxn, or the first change in subtxn has
-: 876: * earlier LSN than first change in the top-level txn and we learned about
-: 877: * their kinship only now.
-: 878: *
-: 879: * The subtransaction's snapshot is cleared regardless of the transfer
-: 880: * happening, since it's not needed anymore in either case.
-: 881: *
-: 882: * We do this as soon as we become aware of their kinship, to avoid queueing
-: 883: * extra snapshots to txns known-as-subtxns -- only top-level txns will
-: 884: * receive further snapshots.
-: 885: */
-: 886:static void
function ReorderBufferTransferSnapToParent called 632 returned 100% blocks executed 93%
632: 887:ReorderBufferTransferSnapToParent(ReorderBufferTXN *txn,
-: 888: ReorderBufferTXN *subtxn)
-: 889:{
632: 890: Assert(subtxn->toplevel_xid == txn->xid);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 891:
632: 892: if (subtxn->base_snapshot != NULL)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
-: 893: {
34: 894: if (txn->base_snapshot == NULL ||
branch 0 taken 89% (fallthrough)
branch 1 taken 11%
branch 2 taken 6% (fallthrough)
branch 3 taken 94%
16: 895: subtxn->base_snapshot_lsn < txn->base_snapshot_lsn)
-: 896: {
-: 897: /*
-: 898: * If the toplevel transaction already has a base snapshot but
-: 899: * it's newer than the subxact's, purge it.
-: 900: */
3: 901: if (txn->base_snapshot != NULL)
branch 0 taken 33% (fallthrough)
branch 1 taken 67%
-: 902: {
1: 903: SnapBuildSnapDecRefcount(txn->base_snapshot);
call 0 returned 100%
1: 904: dlist_delete(&txn->base_snapshot_node);
call 0 returned 100%
-: 905: }
-: 906:
-: 907: /*
-: 908: * The snapshot is now the top transaction's; transfer it, and
-: 909: * adjust the list position of the top transaction in the list by
-: 910: * moving it to where the subtransaction is.
-: 911: */
3: 912: txn->base_snapshot = subtxn->base_snapshot;
3: 913: txn->base_snapshot_lsn = subtxn->base_snapshot_lsn;
3: 914: dlist_insert_before(&subtxn->base_snapshot_node,
call 0 returned 100%
-: 915: &txn->base_snapshot_node);
-: 916:
-: 917: /*
-: 918: * The subtransaction doesn't have a snapshot anymore (so it
-: 919: * mustn't be in the list.)
-: 920: */
3: 921: subtxn->base_snapshot = NULL;
3: 922: subtxn->base_snapshot_lsn = InvalidXLogRecPtr;
3: 923: dlist_delete(&subtxn->base_snapshot_node);
call 0 returned 100%
-: 924: }
-: 925: else
-: 926: {
-: 927: /* Base snap of toplevel is fine, so subxact's is not needed */
15: 928: SnapBuildSnapDecRefcount(subtxn->base_snapshot);
call 0 returned 100%
15: 929: dlist_delete(&subtxn->base_snapshot_node);
call 0 returned 100%
15: 930: subtxn->base_snapshot = NULL;
15: 931: subtxn->base_snapshot_lsn = InvalidXLogRecPtr;
-: 932: }
-: 933: }
632: 934:}
-: 935:
-: 936:/*
-: 937: * Associate a subtransaction with its toplevel transaction at commit
-: 938: * time. There may be no further changes added after this.
-: 939: */
-: 940:void
function ReorderBufferCommitChild called 114 returned 100% blocks executed 100%
114: 941:ReorderBufferCommitChild(ReorderBuffer *rb, TransactionId xid,
-: 942: TransactionId subxid, XLogRecPtr commit_lsn,
-: 943: XLogRecPtr end_lsn)
-: 944:{
-: 945: ReorderBufferTXN *subtxn;
-: 946:
114: 947: subtxn = ReorderBufferTXNByXid(rb, subxid, false, NULL,
call 0 returned 100%
-: 948: InvalidXLogRecPtr, false);
-: 949:
-: 950: /*
-: 951: * No need to do anything if that subtxn didn't contain any changes
-: 952: */
114: 953: if (!subtxn)
branch 0 taken 13% (fallthrough)
branch 1 taken 87%
129: 954: return;
-: 955:
99: 956: subtxn->final_lsn = commit_lsn;
99: 957: subtxn->end_lsn = end_lsn;
-: 958:
-: 959: /*
-: 960: * Assign this subxact as a child of the toplevel xact (no-op if already
-: 961: * done.)
-: 962: */
99: 963: ReorderBufferAssignChild(rb, xid, subxid, InvalidXLogRecPtr);
call 0 returned 100%
-: 964:}
-: 965:
-: 966:
-: 967:/*
-: 968: * Support for efficiently iterating over a transaction's and its
-: 969: * subtransactions' changes.
-: 970: *
-: 971: * We do by doing a k-way merge between transactions/subtransactions. For that
-: 972: * we model the current heads of the different transactions as a binary heap
-: 973: * so we easily know which (sub-)transaction has the change with the smallest
-: 974: * lsn next.
-: 975: *
-: 976: * We assume the changes in individual transactions are already sorted by LSN.
-: 977: */
-: 978:
-: 979:/*
-: 980: * Binary heap comparison function.
-: 981: */
-: 982:static int
function ReorderBufferIterCompare called 50069 returned 100% blocks executed 100%
50069: 983:ReorderBufferIterCompare(Datum a, Datum b, void *arg)
-: 984:{
50069: 985: ReorderBufferIterTXNState *state = (ReorderBufferIterTXNState *) arg;
50069: 986: XLogRecPtr pos_a = state->entries[DatumGetInt32(a)].lsn;
50069: 987: XLogRecPtr pos_b = state->entries[DatumGetInt32(b)].lsn;
-: 988:
50069: 989: if (pos_a < pos_b)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
50058: 990: return 1;
11: 991: else if (pos_a == pos_b)
branch 0 taken 9% (fallthrough)
branch 1 taken 91%
1: 992: return 0;
10: 993: return -1;
-: 994:}
-: 995:
-: 996:/*
-: 997: * Allocate & initialize an iterator which iterates in lsn order over a
-: 998: * transaction and all its subtransactions.
-: 999: */
-: 1000:static ReorderBufferIterTXNState *
function ReorderBufferIterTXNInit called 445 returned 100% blocks executed 95%
445: 1001:ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 1002:{
445: 1003: Size nr_txns = 0;
-: 1004: ReorderBufferIterTXNState *state;
-: 1005: dlist_iter cur_txn_i;
-: 1006: int32 off;
-: 1007:
-: 1008: /*
-: 1009: * Calculate the size of our heap: one element for every transaction that
-: 1010: * contains changes. (Besides the transactions already in the reorder
-: 1011: * buffer, we count the one we were directly passed.)
-: 1012: */
445: 1013: if (txn->nentries > 0)
branch 0 taken 97% (fallthrough)
branch 1 taken 3%
433: 1014: nr_txns++;
-: 1015:
544: 1016: dlist_foreach(cur_txn_i, &txn->subtxns)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 18%
branch 3 taken 82% (fallthrough)
-: 1017: {
-: 1018: ReorderBufferTXN *cur_txn;
-: 1019:
99: 1020: cur_txn = dlist_container(ReorderBufferTXN, node, cur_txn_i.cur);
-: 1021:
99: 1022: if (cur_txn->nentries > 0)
branch 0 taken 32% (fallthrough)
branch 1 taken 68%
32: 1023: nr_txns++;
-: 1024: }
-: 1025:
-: 1026: /*
-: 1027: * TODO: Consider adding fastpath for the rather common nr_txns=1 case, no
-: 1028: * need to allocate/build a heap then.
-: 1029: */
-: 1030:
-: 1031: /* allocate iteration state */
445: 1032: state = (ReorderBufferIterTXNState *)
call 0 returned 100%
445: 1033: MemoryContextAllocZero(rb->context,
-: 1034: sizeof(ReorderBufferIterTXNState) +
445: 1035: sizeof(ReorderBufferIterTXNEntry) * nr_txns);
-: 1036:
445: 1037: state->nr_txns = nr_txns;
445: 1038: dlist_init(&state->old_change);
call 0 returned 100%
-: 1039:
910: 1040: for (off = 0; off < state->nr_txns; off++)
branch 0 taken 51%
branch 1 taken 49% (fallthrough)
-: 1041: {
465: 1042: state->entries[off].fd = -1;
465: 1043: state->entries[off].segno = 0;
-: 1044: }
-: 1045:
-: 1046: /* allocate heap */
445: 1047: state->heap = binaryheap_allocate(state->nr_txns,
call 0 returned 100%
-: 1048: ReorderBufferIterCompare,
-: 1049: state);
-: 1050:
-: 1051: /*
-: 1052: * Now insert items into the binary heap, in an unordered fashion. (We
-: 1053: * will run a heap assembly step at the end; this is more efficient.)
-: 1054: */
-: 1055:
445: 1056: off = 0;
-: 1057:
-: 1058: /* add toplevel transaction if it contains changes */
445: 1059: if (txn->nentries > 0)
branch 0 taken 97% (fallthrough)
branch 1 taken 3%
-: 1060: {
-: 1061: ReorderBufferChange *cur_change;
-: 1062:
433: 1063: if (txn->serialized)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
-: 1064: {
-: 1065: /* serialize remaining changes */
13: 1066: ReorderBufferSerializeTXN(rb, txn);
call 0 returned 100%
13: 1067: ReorderBufferRestoreChanges(rb, txn, &state->entries[off].fd,
call 0 returned 100%
-: 1068: &state->entries[off].segno);
-: 1069: }
-: 1070:
433: 1071: cur_change = dlist_head_element(ReorderBufferChange, node,
call 0 returned 100%
-: 1072: &txn->changes);
-: 1073:
433: 1074: state->entries[off].lsn = cur_change->lsn;
433: 1075: state->entries[off].change = cur_change;
433: 1076: state->entries[off].txn = txn;
-: 1077:
433: 1078: binaryheap_add_unordered(state->heap, Int32GetDatum(off++));
call 0 returned 100%
-: 1079: }
-: 1080:
-: 1081: /* add subtransactions if they contain changes */
544: 1082: dlist_foreach(cur_txn_i, &txn->subtxns)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 18%
branch 3 taken 82% (fallthrough)
-: 1083: {
-: 1084: ReorderBufferTXN *cur_txn;
-: 1085:
99: 1086: cur_txn = dlist_container(ReorderBufferTXN, node, cur_txn_i.cur);
-: 1087:
99: 1088: if (cur_txn->nentries > 0)
branch 0 taken 32% (fallthrough)
branch 1 taken 68%
-: 1089: {
-: 1090: ReorderBufferChange *cur_change;
-: 1091:
32: 1092: if (cur_txn->serialized)
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
-: 1093: {
-: 1094: /* serialize remaining changes */
16: 1095: ReorderBufferSerializeTXN(rb, cur_txn);
call 0 returned 100%
16: 1096: ReorderBufferRestoreChanges(rb, cur_txn,
call 0 returned 100%
-: 1097: &state->entries[off].fd,
-: 1098: &state->entries[off].segno);
-: 1099: }
32: 1100: cur_change = dlist_head_element(ReorderBufferChange, node,
call 0 returned 100%
-: 1101: &cur_txn->changes);
-: 1102:
32: 1103: state->entries[off].lsn = cur_change->lsn;
32: 1104: state->entries[off].change = cur_change;
32: 1105: state->entries[off].txn = cur_txn;
-: 1106:
32: 1107: binaryheap_add_unordered(state->heap, Int32GetDatum(off++));
call 0 returned 100%
-: 1108: }
-: 1109: }
-: 1110:
-: 1111: /* assemble a valid binary heap */
445: 1112: binaryheap_build(state->heap);
call 0 returned 100%
-: 1113:
445: 1114: return state;
-: 1115:}
-: 1116:
-: 1117:/*
-: 1118: * Return the next change when iterating over a transaction and its
-: 1119: * subtransactions.
-: 1120: *
-: 1121: * Returns NULL when no further changes exist.
-: 1122: */
-: 1123:static ReorderBufferChange *
function ReorderBufferIterTXNNext called 157321 returned 100% blocks executed 93%
157321: 1124:ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state)
-: 1125:{
-: 1126: ReorderBufferChange *change;
-: 1127: ReorderBufferIterTXNEntry *entry;
-: 1128: int32 off;
-: 1129:
-: 1130: /* nothing there anymore */
157321: 1131: if (state->heap->bh_size == 0)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
445: 1132: return NULL;
-: 1133:
156876: 1134: off = DatumGetInt32(binaryheap_first(state->heap));
call 0 returned 100%
156876: 1135: entry = &state->entries[off];
-: 1136:
-: 1137: /* free memory we might have "leaked" in the previous *Next call */
156876: 1138: if (!dlist_is_empty(&state->old_change))
call 0 returned 100%
branch 1 taken 1% (fallthrough)
branch 2 taken 99%
-: 1139: {
39: 1140: change = dlist_container(ReorderBufferChange, node,
call 0 returned 100%
-: 1141: dlist_pop_head_node(&state->old_change));
39: 1142: ReorderBufferReturnChange(rb, change);
call 0 returned 100%
39: 1143: Assert(dlist_is_empty(&state->old_change));
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 1144: }
-: 1145:
156876: 1146: change = entry->change;
-: 1147:
-: 1148: /*
-: 1149: * update heap with information about which transaction has the next
-: 1150: * relevant change in LSN order
-: 1151: */
-: 1152:
-: 1153: /* there are in-memory changes */
156876: 1154: if (dlist_has_next(&entry->txn->changes, &entry->change->node))
call 0 returned 100%
branch 1 taken 99% (fallthrough)
branch 2 taken 1%
-: 1155: {
156381: 1156: dlist_node *next = dlist_next_node(&entry->txn->changes, &change->node);
call 0 returned 100%
156381: 1157: ReorderBufferChange *next_change =
156381: 1158: dlist_container(ReorderBufferChange, node, next);
-: 1159:
-: 1160: /* txn stays the same */
156381: 1161: state->entries[off].lsn = next_change->lsn;
156381: 1162: state->entries[off].change = next_change;
-: 1163:
156381: 1164: binaryheap_replace_first(state->heap, Int32GetDatum(off));
call 0 returned 100%
156381: 1165: return change;
-: 1166: }
-: 1167:
-: 1168: /* try to load changes from disk */
495: 1169: if (entry->txn->nentries != entry->txn->nentries_mem)
branch 0 taken 11% (fallthrough)
branch 1 taken 89%
-: 1170: {
-: 1171: /*
-: 1172: * Ugly: restoring changes will reuse *Change records, thus delete the
-: 1173: * current one from the per-tx list and only free in the next call.
-: 1174: */
54: 1175: dlist_delete(&change->node);
call 0 returned 100%
54: 1176: dlist_push_tail(&state->old_change, &change->node);
call 0 returned 100%
-: 1177:
54: 1178: if (ReorderBufferRestoreChanges(rb, entry->txn, &entry->fd,
call 0 returned 100%
branch 1 taken 56% (fallthrough)
branch 2 taken 44%
-: 1179: &state->entries[off].segno))
-: 1180: {
-: 1181: /* successfully restored changes from disk */
30: 1182: ReorderBufferChange *next_change =
call 0 returned 100%
30: 1183: dlist_head_element(ReorderBufferChange, node,
-: 1184: &entry->txn->changes);
-: 1185:
30: 1186: elog(DEBUG2, "restored %u/%u changes from disk",
call 0 returned 100%
call 1 returned 100%
-: 1187: (uint32) entry->txn->nentries_mem,
-: 1188: (uint32) entry->txn->nentries);
-: 1189:
30: 1190: Assert(entry->txn->nentries_mem);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1191: /* txn stays the same */
30: 1192: state->entries[off].lsn = next_change->lsn;
30: 1193: state->entries[off].change = next_change;
30: 1194: binaryheap_replace_first(state->heap, Int32GetDatum(off));
call 0 returned 100%
-: 1195:
30: 1196: return change;
-: 1197: }
-: 1198: }
-: 1199:
-: 1200: /* ok, no changes there anymore, remove */
465: 1201: binaryheap_remove_first(state->heap);
call 0 returned 100%
-: 1202:
465: 1203: return change;
-: 1204:}
-: 1205:
-: 1206:/*
-: 1207: * Deallocate the iterator
-: 1208: */
-: 1209:static void
function ReorderBufferIterTXNFinish called 445 returned 100% blocks executed 87%
445: 1210:ReorderBufferIterTXNFinish(ReorderBuffer *rb,
-: 1211: ReorderBufferIterTXNState *state)
-: 1212:{
-: 1213: int32 off;
-: 1214:
910: 1215: for (off = 0; off < state->nr_txns; off++)
branch 0 taken 51%
branch 1 taken 49% (fallthrough)
-: 1216: {
465: 1217: if (state->entries[off].fd != -1)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1218: CloseTransientFile(state->entries[off].fd);
call 0 never executed
-: 1219: }
-: 1220:
-: 1221: /* free memory we might have "leaked" in the last *Next call */
445: 1222: if (!dlist_is_empty(&state->old_change))
call 0 returned 100%
branch 1 taken 3% (fallthrough)
branch 2 taken 97%
-: 1223: {
-: 1224: ReorderBufferChange *change;
-: 1225:
14: 1226: change = dlist_container(ReorderBufferChange, node,
call 0 returned 100%
-: 1227: dlist_pop_head_node(&state->old_change));
14: 1228: ReorderBufferReturnChange(rb, change);
call 0 returned 100%
14: 1229: Assert(dlist_is_empty(&state->old_change));
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 1230: }
-: 1231:
445: 1232: binaryheap_free(state->heap);
call 0 returned 100%
445: 1233: pfree(state);
call 0 returned 100%
445: 1234:}
-: 1235:
-: 1236:/*
-: 1237: * Cleanup the contents of a transaction, usually after the transaction
-: 1238: * committed or aborted.
-: 1239: */
-: 1240:static void
function ReorderBufferCleanupTXN called 1989 returned 100% blocks executed 79%
1989: 1241:ReorderBufferCleanupTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 1242:{
-: 1243: bool found;
-: 1244: dlist_mutable_iter iter;
-: 1245:
-: 1246: /* cleanup subtransactions & their changes */
2088: 1247: dlist_foreach_modify(iter, &txn->subtxns)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 5%
branch 3 taken 95% (fallthrough)
-: 1248: {
-: 1249: ReorderBufferTXN *subtxn;
-: 1250:
99: 1251: subtxn = dlist_container(ReorderBufferTXN, node, iter.cur);
-: 1252:
-: 1253: /*
-: 1254: * Subtransactions are always associated to the toplevel TXN, even if
-: 1255: * they originally were happening inside another subtxn, so we won't
-: 1256: * ever recurse more than one level deep here.
-: 1257: */
99: 1258: Assert(subtxn->is_known_as_subxact);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
99: 1259: Assert(subtxn->nsubtxns == 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1260:
99: 1261: ReorderBufferCleanupTXN(rb, subtxn);
call 0 returned 100%
-: 1262: }
-: 1263:
-: 1264: /* cleanup changes in the toplevel txn */
54572: 1265: dlist_foreach_modify(iter, &txn->changes)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 96%
branch 3 taken 4% (fallthrough)
-: 1266: {
-: 1267: ReorderBufferChange *change;
-: 1268:
52583: 1269: change = dlist_container(ReorderBufferChange, node, iter.cur);
-: 1270:
-: 1271: /* Check we're not mixing changes from different transactions. */
52583: 1272: Assert(change->txn == txn);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1273:
52583: 1274: ReorderBufferReturnChange(rb, change);
call 0 returned 100%
-: 1275: }
-: 1276:
-: 1277: /*
-: 1278: * Cleanup the tuplecids we stored for decoding catalog snapshot access.
-: 1279: * They are always stored in the toplevel transaction.
-: 1280: */
15619: 1281: dlist_foreach_modify(iter, &txn->tuplecids)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 87%
branch 3 taken 13% (fallthrough)
-: 1282: {
-: 1283: ReorderBufferChange *change;
-: 1284:
13630: 1285: change = dlist_container(ReorderBufferChange, node, iter.cur);
-: 1286:
-: 1287: /* Check we're not mixing changes from different transactions. */
13630: 1288: Assert(change->txn == txn);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
13630: 1289: Assert(change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1290:
13630: 1291: ReorderBufferReturnChange(rb, change);
call 0 returned 100%
-: 1292: }
-: 1293:
-: 1294: /*
-: 1295: * Cleanup the base snapshot, if set.
-: 1296: */
1989: 1297: if (txn->base_snapshot != NULL)
branch 0 taken 67% (fallthrough)
branch 1 taken 33%
-: 1298: {
1341: 1299: SnapBuildSnapDecRefcount(txn->base_snapshot);
call 0 returned 100%
1341: 1300: dlist_delete(&txn->base_snapshot_node);
call 0 returned 100%
-: 1301: }
-: 1302:
-: 1303: /*
-: 1304: * Remove TXN from its containing list.
-: 1305: *
-: 1306: * Note: if txn->is_known_as_subxact, we are deleting the TXN from its
-: 1307: * parent's list of known subxacts; this leaves the parent's nsubxacts
-: 1308: * count too high, but we don't care. Otherwise, we are deleting the TXN
-: 1309: * from the LSN-ordered list of toplevel TXNs.
-: 1310: */
1989: 1311: dlist_delete(&txn->node);
call 0 returned 100%
-: 1312:
-: 1313: /* now remove reference from buffer */
1989: 1314: hash_search(rb->by_txn,
call 0 returned 100%
1989: 1315: (void *) &txn->xid,
-: 1316: HASH_REMOVE,
-: 1317: &found);
1989: 1318: Assert(found);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1319:
-: 1320: /* remove entries spilled to disk */
1989: 1321: if (txn->serialized)
branch 0 taken 10% (fallthrough)
branch 1 taken 90%
191: 1322: ReorderBufferRestoreCleanup(rb, txn);
call 0 returned 100%
-: 1323:
-: 1324: /* deallocate */
1989: 1325: ReorderBufferReturnTXN(rb, txn);
call 0 returned 100%
1989: 1326:}
-: 1327:
-: 1328:/*
-: 1329: * Build a hash with a (relfilenode, ctid) -> (cmin, cmax) mapping for use by
-: 1330: * HeapTupleSatisfiesHistoricMVCC.
-: 1331: */
-: 1332:static void
function ReorderBufferBuildTupleCidHash called 445 returned 100% blocks executed 81%
445: 1333:ReorderBufferBuildTupleCidHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 1334:{
-: 1335: dlist_iter iter;
-: 1336: HASHCTL hash_ctl;
-: 1337:
445: 1338: if (!txn->has_catalog_changes || dlist_is_empty(&txn->tuplecids))
branch 0 taken 38% (fallthrough)
branch 1 taken 62%
call 2 returned 100%
branch 3 taken 2% (fallthrough)
branch 4 taken 98%
723: 1339: return;
-: 1340:
167: 1341: memset(&hash_ctl, 0, sizeof(hash_ctl));
-: 1342:
167: 1343: hash_ctl.keysize = sizeof(ReorderBufferTupleCidKey);
167: 1344: hash_ctl.entrysize = sizeof(ReorderBufferTupleCidEnt);
167: 1345: hash_ctl.hcxt = rb->context;
-: 1346:
-: 1347: /*
-: 1348: * create the hash with the exact number of to-be-stored tuplecids from
-: 1349: * the start
-: 1350: */
167: 1351: txn->tuplecid_hash =
167: 1352: hash_create("ReorderBufferTupleCid", txn->ntuplecids, &hash_ctl,
call 0 returned 100%
-: 1353: HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
-: 1354:
5671: 1355: dlist_foreach(iter, &txn->tuplecids)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 97%
branch 3 taken 3% (fallthrough)
-: 1356: {
-: 1357: ReorderBufferTupleCidKey key;
-: 1358: ReorderBufferTupleCidEnt *ent;
-: 1359: bool found;
-: 1360: ReorderBufferChange *change;
-: 1361:
5504: 1362: change = dlist_container(ReorderBufferChange, node, iter.cur);
-: 1363:
5504: 1364: Assert(change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1365:
-: 1366: /* be careful about padding */
5504: 1367: memset(&key, 0, sizeof(ReorderBufferTupleCidKey));
-: 1368:
5504: 1369: key.relnode = change->data.tuplecid.node;
-: 1370:
5504: 1371: ItemPointerCopy(&change->data.tuplecid.tid,
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1372: &key.tid);
-: 1373:
5504: 1374: ent = (ReorderBufferTupleCidEnt *)
call 0 returned 100%
5504: 1375: hash_search(txn->tuplecid_hash,
-: 1376: (void *) &key,
-: 1377: HASH_ENTER | HASH_FIND,
-: 1378: &found);
5504: 1379: if (!found)
branch 0 taken 74% (fallthrough)
branch 1 taken 26%
-: 1380: {
4055: 1381: ent->cmin = change->data.tuplecid.cmin;
4055: 1382: ent->cmax = change->data.tuplecid.cmax;
4055: 1383: ent->combocid = change->data.tuplecid.combocid;
-: 1384: }
-: 1385: else
-: 1386: {
-: 1387: /*
-: 1388: * Maybe we already saw this tuple before in this transaction, but
-: 1389: * if so it must have the same cmin.
-: 1390: */
1449: 1391: Assert(ent->cmin == change->data.tuplecid.cmin);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1392:
-: 1393: /*
-: 1394: * cmax may be initially invalid, but once set it can only grow,
-: 1395: * and never become invalid again.
-: 1396: */
1449: 1397: Assert((ent->cmax == InvalidCommandId) ||
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 0% (fallthrough)
branch 5 taken 100%
call 6 never executed
-: 1398: ((change->data.tuplecid.cmax != InvalidCommandId) &&
-: 1399: (change->data.tuplecid.cmax > ent->cmax)));
1449: 1400: ent->cmax = change->data.tuplecid.cmax;
-: 1401: }
-: 1402: }
-: 1403:}
-: 1404:
-: 1405:/*
-: 1406: * Copy a provided snapshot so we can modify it privately. This is needed so
-: 1407: * that catalog modifying transactions can look into intermediate catalog
-: 1408: * states.
-: 1409: */
-: 1410:static Snapshot
function ReorderBufferCopySnap called 335 returned 100% blocks executed 90%
335: 1411:ReorderBufferCopySnap(ReorderBuffer *rb, Snapshot orig_snap,
-: 1412: ReorderBufferTXN *txn, CommandId cid)
-: 1413:{
-: 1414: Snapshot snap;
-: 1415: dlist_iter iter;
335: 1416: int i = 0;
-: 1417: Size size;
-: 1418:
335: 1419: size = sizeof(SnapshotData) +
670: 1420: sizeof(TransactionId) * orig_snap->xcnt +
335: 1421: sizeof(TransactionId) * (txn->nsubtxns + 1);
-: 1422:
335: 1423: snap = MemoryContextAllocZero(rb->context, size);
call 0 returned 100%
335: 1424: memcpy(snap, orig_snap, sizeof(SnapshotData));
-: 1425:
335: 1426: snap->copied = true;
335: 1427: snap->active_count = 1; /* mark as active so nobody frees it */
335: 1428: snap->regd_count = 0;
335: 1429: snap->xip = (TransactionId *) (snap + 1);
-: 1430:
335: 1431: memcpy(snap->xip, orig_snap->xip, sizeof(TransactionId) * snap->xcnt);
-: 1432:
-: 1433: /*
-: 1434: * snap->subxip contains all txids that belong to our transaction which we
-: 1435: * need to check via cmin/cmax. That's why we store the toplevel
-: 1436: * transaction in there as well.
-: 1437: */
335: 1438: snap->subxip = snap->xip + snap->xcnt;
335: 1439: snap->subxip[i++] = txn->xid;
-: 1440:
-: 1441: /*
-: 1442: * subxcnt isn't decreased when subtransactions abort, so count manually.
-: 1443: * Since it's an upper boundary it is safe to use it for the allocation
-: 1444: * above.
-: 1445: */
335: 1446: snap->subxcnt = 1;
-: 1447:
338: 1448: dlist_foreach(iter, &txn->subtxns)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 1%
branch 3 taken 99% (fallthrough)
-: 1449: {
-: 1450: ReorderBufferTXN *sub_txn;
-: 1451:
3: 1452: sub_txn = dlist_container(ReorderBufferTXN, node, iter.cur);
3: 1453: snap->subxip[i++] = sub_txn->xid;
3: 1454: snap->subxcnt++;
-: 1455: }
-: 1456:
-: 1457: /* sort so we can bsearch() later */
335: 1458: qsort(snap->subxip, snap->subxcnt, sizeof(TransactionId), xidComparator);
call 0 returned 100%
-: 1459:
-: 1460: /* store the specified current CommandId */
335: 1461: snap->curcid = cid;
-: 1462:
335: 1463: return snap;
-: 1464:}
-: 1465:
-: 1466:/*
-: 1467: * Free a previously ReorderBufferCopySnap'ed snapshot
-: 1468: */
-: 1469:static void
function ReorderBufferFreeSnap called 816 returned 100% blocks executed 100%
816: 1470:ReorderBufferFreeSnap(ReorderBuffer *rb, Snapshot snap)
-: 1471:{
816: 1472: if (snap->copied)
branch 0 taken 41% (fallthrough)
branch 1 taken 59%
337: 1473: pfree(snap);
call 0 returned 100%
-: 1474: else
479: 1475: SnapBuildSnapDecRefcount(snap);
call 0 returned 100%
816: 1476:}
-: 1477:
-: 1478:/*
-: 1479: * Perform the replay of a transaction and its non-aborted subtransactions.
-: 1480: *
-: 1481: * Subtransactions previously have to be processed by
-: 1482: * ReorderBufferCommitChild(), even if previously assigned to the toplevel
-: 1483: * transaction with ReorderBufferAssignChild.
-: 1484: *
-: 1485: * We currently can only decode a transaction's contents when its commit
-: 1486: * record is read because that's the only place where we know about cache
-: 1487: * invalidations. Thus, once a toplevel commit is read, we iterate over the top
-: 1488: * and subtransactions (using a k-way merge) and replay the changes in lsn
-: 1489: * order.
-: 1490: */
-: 1491:void
function ReorderBufferCommit called 447 returned 100% blocks executed 71%
447: 1492:ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
-: 1493: XLogRecPtr commit_lsn, XLogRecPtr end_lsn,
-: 1494: TimestampTz commit_time,
-: 1495: RepOriginId origin_id, XLogRecPtr origin_lsn)
-: 1496:{
-: 1497: ReorderBufferTXN *txn;
-: 1498: volatile Snapshot snapshot_now;
447: 1499: volatile CommandId command_id = FirstCommandId;
-: 1500: bool using_subtxn;
447: 1501: ReorderBufferIterTXNState *volatile iterstate = NULL;
-: 1502:
447: 1503: txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
call 0 returned 100%
-: 1504: false);
-: 1505:
-: 1506: /* unknown transaction, nothing to replay */
447: 1507: if (txn == NULL)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
1: 1508: return;
-: 1509:
446: 1510: txn->final_lsn = commit_lsn;
446: 1511: txn->end_lsn = end_lsn;
446: 1512: txn->commit_time = commit_time;
446: 1513: txn->origin_id = origin_id;
446: 1514: txn->origin_lsn = origin_lsn;
-: 1515:
-: 1516: /*
-: 1517: * If this transaction has no snapshot, it didn't make any changes to the
-: 1518: * database, so there's nothing to decode. Note that
-: 1519: * ReorderBufferCommitChild will have transferred any snapshots from
-: 1520: * subtransactions if there were any.
-: 1521: */
446: 1522: if (txn->base_snapshot == NULL)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 1523: {
1: 1524: Assert(txn->ninvalidations == 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1: 1525: ReorderBufferCleanupTXN(rb, txn);
call 0 returned 100%
1: 1526: return;
-: 1527: }
-: 1528:
445: 1529: snapshot_now = txn->base_snapshot;
-: 1530:
-: 1531: /* build data to be able to lookup the CommandIds of catalog tuples */
445: 1532: ReorderBufferBuildTupleCidHash(rb, txn);
call 0 returned 100%
-: 1533:
-: 1534: /* setup the initial snapshot */
445: 1535: SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash);
call 0 returned 100%
-: 1536:
-: 1537: /*
-: 1538: * Decoding needs access to syscaches et al., which in turn use
-: 1539: * heavyweight locks and such. Thus we need to have enough state around to
-: 1540: * keep track of those. The easiest way is to simply use a transaction
-: 1541: * internally. That also allows us to easily enforce that nothing writes
-: 1542: * to the database by checking for xid assignments.
-: 1543: *
-: 1544: * When we're called via the SQL SRF there's already a transaction
-: 1545: * started, so start an explicit subtransaction there.
-: 1546: */
445: 1547: using_subtxn = IsTransactionOrTransactionBlock();
call 0 returned 100%
-: 1548:
445: 1549: PG_TRY();
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
-: 1550: {
-: 1551: ReorderBufferChange *change;
445: 1552: ReorderBufferChange *specinsert = NULL;
-: 1553:
445: 1554: if (using_subtxn)
branch 0 taken 73% (fallthrough)
branch 1 taken 27%
325: 1555: BeginInternalSubTransaction("replay");
call 0 returned 100%
-: 1556: else
120: 1557: StartTransactionCommand();
call 0 returned 100%
-: 1558:
445: 1559: rb->begin(rb, txn);
call 0 returned 100%
-: 1560:
445: 1561: iterstate = ReorderBufferIterTXNInit(rb, txn);
call 0 returned 100%
157766: 1562: while ((change = ReorderBufferIterTXNNext(rb, iterstate)) != NULL)
call 0 returned 100%
branch 1 taken 99%
branch 2 taken 1% (fallthrough)
-: 1563: {
156876: 1564: Relation relation = NULL;
-: 1565: Oid reloid;
-: 1566:
156876: 1567: switch (change->action)
branch 0 taken 1%
branch 1 taken 94%
branch 2 taken 1%
branch 3 taken 1%
branch 4 taken 1%
branch 5 taken 1%
branch 6 taken 3%
branch 7 taken 0%
branch 8 taken 0%
-: 1568: {
-: 1569: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
-: 1570:
-: 1571: /*
-: 1572: * Confirmation for speculative insertion arrived. Simply
-: 1573: * use as a normal record. It'll be cleaned up at the end
-: 1574: * of INSERT processing.
-: 1575: */
1782: 1576: if (specinsert == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1577: elog(ERROR, "invalid ordering of speculative insertion changes");
call 0 never executed
call 1 never executed
call 2 never executed
1782: 1578: Assert(specinsert->data.tp.oldtuple == NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1782: 1579: change = specinsert;
1782: 1580: change->action = REORDER_BUFFER_CHANGE_INSERT;
-: 1581:
-: 1582: /* intentionally fall through */
-: 1583: case REORDER_BUFFER_CHANGE_INSERT:
-: 1584: case REORDER_BUFFER_CHANGE_UPDATE:
-: 1585: case REORDER_BUFFER_CHANGE_DELETE:
149494: 1586: Assert(snapshot_now);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1587:
149494: 1588: reloid = RelidByRelfilenode(change->data.tp.relnode.spcNode,
call 0 returned 100%
-: 1589: change->data.tp.relnode.relNode);
-: 1590:
-: 1591: /*
-: 1592: * Mapped catalog tuple without data, emitted while
-: 1593: * catalog table was in the process of being rewritten. We
-: 1594: * can fail to look up the relfilenode, because the
-: 1595: * relmapper has no "historic" view, in contrast to normal
-: 1596: * the normal catalog during decoding. Thus repeated
-: 1597: * rewrites can cause a lookup failure. That's OK because
-: 1598: * we do not decode catalog changes anyway. Normally such
-: 1599: * tuples would be skipped over below, but we can't
-: 1600: * identify whether the table should be logically logged
-: 1601: * without mapping the relfilenode to the oid.
-: 1602: */
149571: 1603: if (reloid == InvalidOid &&
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
154: 1604: change->data.tp.newtuple == NULL &&
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
77: 1605: change->data.tp.oldtuple == NULL)
-: 1606: goto change_done;
149417: 1607: else if (reloid == InvalidOid)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1608: elog(ERROR, "could not map filenode \"%s\" to relation OID",
call 0 never executed
call 1 never executed
call 2 never executed
call 3 never executed
-: 1609: relpathperm(change->data.tp.relnode,
-: 1610: MAIN_FORKNUM));
-: 1611:
149417: 1612: relation = RelationIdGetRelation(reloid);
call 0 returned 100%
-: 1613:
149417: 1614: if (!RelationIsValid(relation))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1615: elog(ERROR, "could not open relation with OID %u (for filenode \"%s\")",
call 0 never executed
call 1 never executed
call 2 never executed
call 3 never executed
-: 1616: reloid,
-: 1617: relpathperm(change->data.tp.relnode,
-: 1618: MAIN_FORKNUM));
-: 1619:
149417: 1620: if (!RelationIsLogicallyLogged(relation))
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
call 4 returned 100%
branch 5 taken 99% (fallthrough)
branch 6 taken 1%
-: 1621: goto change_done;
-: 1622:
-: 1623: /*
-: 1624: * Ignore temporary heaps created during DDL unless the
-: 1625: * plugin has asked for them.
-: 1626: */
147530: 1627: if (relation->rd_rel->relrewrite && !rb->output_rewrites)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
branch 2 taken 95% (fallthrough)
branch 3 taken 5%
20: 1628: goto change_done;
-: 1629:
-: 1630: /*
-: 1631: * For now ignore sequence changes entirely. Most of the
-: 1632: * time they don't log changes using records we
-: 1633: * understand, so it doesn't make sense to handle the few
-: 1634: * cases we do.
-: 1635: */
147510: 1636: if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1637: goto change_done;
-: 1638:
-: 1639: /* user-triggered change */
147510: 1640: if (!IsToastRelation(relation))
call 0 returned 100%
branch 1 taken 99% (fallthrough)
branch 2 taken 1%
-: 1641: {
146981: 1642: ReorderBufferToastReplace(rb, txn, relation, change);
call 0 returned 100%
146981: 1643: rb->apply_change(rb, txn, relation, change);
call 0 returned 100%
-: 1644:
-: 1645: /*
-: 1646: * Only clear reassembled toast chunks if we're sure
-: 1647: * they're not required anymore. The creator of the
-: 1648: * tuple tells us.
-: 1649: */
146981: 1650: if (change->data.tp.clear_toast_afterwards)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
146779: 1651: ReorderBufferToastReset(rb, txn);
call 0 returned 100%
-: 1652: }
-: 1653: /* we're not interested in toast deletions */
529: 1654: else if (change->action == REORDER_BUFFER_CHANGE_INSERT)
branch 0 taken 57% (fallthrough)
branch 1 taken 43%
-: 1655: {
-: 1656: /*
-: 1657: * Need to reassemble the full toasted Datum in
-: 1658: * memory, to ensure the chunks don't get reused till
-: 1659: * we're done remove it from the list of this
-: 1660: * transaction's changes. Otherwise it will get
-: 1661: * freed/reused while restoring spooled data from
-: 1662: * disk.
-: 1663: */
301: 1664: Assert(change->data.tp.newtuple != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1665:
301: 1666: dlist_delete(&change->node);
call 0 returned 100%
301: 1667: ReorderBufferToastAppendChunk(rb, txn, relation,
call 0 returned 100%
-: 1668: change);
-: 1669: }
-: 1670:
-: 1671: change_done:
-: 1672:
-: 1673: /*
-: 1674: * Either speculative insertion was confirmed, or it was
-: 1675: * unsuccessful and the record isn't needed anymore.
-: 1676: */
149494: 1677: if (specinsert != NULL)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 1678: {
1782: 1679: ReorderBufferReturnChange(rb, specinsert);
call 0 returned 100%
1782: 1680: specinsert = NULL;
-: 1681: }
-: 1682:
149494: 1683: if (relation != NULL)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
-: 1684: {
149417: 1685: RelationClose(relation);
call 0 returned 100%
149417: 1686: relation = NULL;
-: 1687: }
149494: 1688: break;
-: 1689:
-: 1690: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
-: 1691:
-: 1692: /*
-: 1693: * Speculative insertions are dealt with by delaying the
-: 1694: * processing of the insert until the confirmation record
-: 1695: * arrives. For that we simply unlink the record from the
-: 1696: * chain, so it does not get freed/reused while restoring
-: 1697: * spooled data from disk.
-: 1698: *
-: 1699: * This is safe in the face of concurrent catalog changes
-: 1700: * because the relevant relation can't be changed between
-: 1701: * speculative insertion and confirmation due to
-: 1702: * CheckTableNotInUse() and locking.
-: 1703: */
-: 1704:
-: 1705: /* clear out a pending (and thus failed) speculation */
1782: 1706: if (specinsert != NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1707: {
#####: 1708: ReorderBufferReturnChange(rb, specinsert);
call 0 never executed
#####: 1709: specinsert = NULL;
-: 1710: }
-: 1711:
-: 1712: /* and memorize the pending insertion */
1782: 1713: dlist_delete(&change->node);
call 0 returned 100%
1782: 1714: specinsert = change;
1782: 1715: break;
-: 1716:
-: 1717: case REORDER_BUFFER_CHANGE_TRUNCATE:
-: 1718: {
-: 1719: int i;
8: 1720: int nrelids = change->data.truncate.nrelids;
8: 1721: int nrelations = 0;
-: 1722: Relation *relations;
-: 1723:
8: 1724: relations = palloc0(nrelids * sizeof(Relation));
call 0 returned 100%
18: 1725: for (i = 0; i < nrelids; i++)
branch 0 taken 56%
branch 1 taken 44% (fallthrough)
-: 1726: {
10: 1727: Oid relid = change->data.truncate.relids[i];
-: 1728: Relation relation;
-: 1729:
10: 1730: relation = RelationIdGetRelation(relid);
call 0 returned 100%
-: 1731:
10: 1732: if (!RelationIsValid(relation))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1733: elog(ERROR, "could not open relation with OID %u", relid);
call 0 never executed
call 1 never executed
call 2 never executed
-: 1734:
10: 1735: if (!RelationIsLogicallyLogged(relation))
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
call 4 returned 100%
branch 5 taken 0% (fallthrough)
branch 6 taken 100%
#####: 1736: continue;
-: 1737:
10: 1738: relations[nrelations++] = relation;
-: 1739: }
-: 1740:
8: 1741: rb->apply_truncate(rb, txn, nrelations, relations, change);
call 0 returned 100%
-: 1742:
18: 1743: for (i = 0; i < nrelations; i++)
branch 0 taken 56%
branch 1 taken 44% (fallthrough)
10: 1744: RelationClose(relations[i]);
call 0 returned 100%
-: 1745:
8: 1746: break;
-: 1747: }
-: 1748:
-: 1749: case REORDER_BUFFER_CHANGE_MESSAGE:
15: 1750: rb->message(rb, txn, change->lsn, true,
call 0 returned 100%
5: 1751: change->data.msg.prefix,
-: 1752: change->data.msg.message_size,
5: 1753: change->data.msg.message);
5: 1754: break;
-: 1755:
-: 1756: case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
-: 1757: /* get rid of the old */
186: 1758: TeardownHistoricSnapshot(false);
call 0 returned 100%
-: 1759:
186: 1760: if (snapshot_now->copied)
branch 0 taken 90% (fallthrough)
branch 1 taken 10%
-: 1761: {
168: 1762: ReorderBufferFreeSnap(rb, snapshot_now);
call 0 returned 100%
168: 1763: snapshot_now =
168: 1764: ReorderBufferCopySnap(rb, change->data.snapshot,
call 0 returned 100%
-: 1765: txn, command_id);
-: 1766: }
-: 1767:
-: 1768: /*
-: 1769: * Restored from disk, need to be careful not to double
-: 1770: * free. We could introduce refcounting for that, but for
-: 1771: * now this seems infrequent enough not to care.
-: 1772: */
18: 1773: else if (change->data.snapshot->copied)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1774: {
#####: 1775: snapshot_now =
#####: 1776: ReorderBufferCopySnap(rb, change->data.snapshot,
call 0 never executed
-: 1777: txn, command_id);
-: 1778: }
-: 1779: else
-: 1780: {
18: 1781: snapshot_now = change->data.snapshot;
-: 1782: }
-: 1783:
-: 1784:
-: 1785: /* and continue with the new one */
186: 1786: SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash);
call 0 returned 100%
186: 1787: break;
-: 1788:
-: 1789: case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
5401: 1790: Assert(change->data.command_id != InvalidCommandId);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1791:
5401: 1792: if (command_id < change->data.command_id)
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
-: 1793: {
785: 1794: command_id = change->data.command_id;
-: 1795:
785: 1796: if (!snapshot_now->copied)
branch 0 taken 21% (fallthrough)
branch 1 taken 79%
-: 1797: {
-: 1798: /* we don't use the global one anymore */
167: 1799: snapshot_now = ReorderBufferCopySnap(rb, snapshot_now,
call 0 returned 100%
-: 1800: txn, command_id);
-: 1801: }
-: 1802:
785: 1803: snapshot_now->curcid = command_id;
-: 1804:
785: 1805: TeardownHistoricSnapshot(false);
call 0 returned 100%
785: 1806: SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash);
call 0 returned 100%
-: 1807:
-: 1808: /*
-: 1809: * Every time the CommandId is incremented, we could
-: 1810: * see new catalog contents, so execute all
-: 1811: * invalidations.
-: 1812: */
785: 1813: ReorderBufferExecuteInvalidations(rb, txn);
call 0 returned 100%
-: 1814: }
-: 1815:
5401: 1816: break;
-: 1817:
-: 1818: case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
#####: 1819: elog(ERROR, "tuplecid value in changequeue");
call 0 never executed
call 1 never executed
call 2 never executed
-: 1820: break;
-: 1821: }
-: 1822: }
-: 1823:
-: 1824: /*
-: 1825: * There's a speculative insertion remaining, just clean in up, it
-: 1826: * can't have been successful, otherwise we'd gotten a confirmation
-: 1827: * record.
-: 1828: */
445: 1829: if (specinsert)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1830: {
#####: 1831: ReorderBufferReturnChange(rb, specinsert);
call 0 never executed
#####: 1832: specinsert = NULL;
-: 1833: }
-: 1834:
-: 1835: /* clean up the iterator */
445: 1836: ReorderBufferIterTXNFinish(rb, iterstate);
call 0 returned 100%
445: 1837: iterstate = NULL;
-: 1838:
-: 1839: /* call commit callback */
445: 1840: rb->commit(rb, txn, commit_lsn);
call 0 returned 100%
-: 1841:
-: 1842: /* this is just a sanity check against bad output plugin behaviour */
445: 1843: if (GetCurrentTransactionIdIfAny() != InvalidTransactionId)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 1844: elog(ERROR, "output plugin used XID %u",
call 0 never executed
call 1 never executed
call 2 never executed
call 3 never executed
-: 1845: GetCurrentTransactionId());
-: 1846:
-: 1847: /* cleanup */
445: 1848: TeardownHistoricSnapshot(false);
call 0 returned 100%
-: 1849:
-: 1850: /*
-: 1851: * Aborting the current (sub-)transaction as a whole has the right
-: 1852: * semantics. We want all locks acquired in here to be released, not
-: 1853: * reassigned to the parent and we do not want any database access
-: 1854: * have persistent effects.
-: 1855: */
445: 1856: AbortCurrentTransaction();
call 0 returned 100%
-: 1857:
-: 1858: /* make sure there's no cache pollution */
445: 1859: ReorderBufferExecuteInvalidations(rb, txn);
call 0 returned 100%
-: 1860:
445: 1861: if (using_subtxn)
branch 0 taken 73% (fallthrough)
branch 1 taken 27%
325: 1862: RollbackAndReleaseCurrentSubTransaction();
call 0 returned 100%
-: 1863:
445: 1864: if (snapshot_now->copied)
branch 0 taken 38% (fallthrough)
branch 1 taken 62%
167: 1865: ReorderBufferFreeSnap(rb, snapshot_now);
call 0 returned 100%
-: 1866:
-: 1867: /* remove potential on-disk data, and deallocate */
445: 1868: ReorderBufferCleanupTXN(rb, txn);
call 0 returned 100%
-: 1869: }
#####: 1870: PG_CATCH();
-: 1871: {
-: 1872: /* TODO: Encapsulate cleanup from the PG_TRY and PG_CATCH blocks */
#####: 1873: if (iterstate)
branch 0 never executed
branch 1 never executed
#####: 1874: ReorderBufferIterTXNFinish(rb, iterstate);
call 0 never executed
-: 1875:
#####: 1876: TeardownHistoricSnapshot(true);
call 0 never executed
-: 1877:
-: 1878: /*
-: 1879: * Force cache invalidation to happen outside of a valid transaction
-: 1880: * to prevent catalog access as we just caught an error.
-: 1881: */
#####: 1882: AbortCurrentTransaction();
call 0 never executed
-: 1883:
-: 1884: /* make sure there's no cache pollution */
#####: 1885: ReorderBufferExecuteInvalidations(rb, txn);
call 0 never executed
-: 1886:
#####: 1887: if (using_subtxn)
branch 0 never executed
branch 1 never executed
#####: 1888: RollbackAndReleaseCurrentSubTransaction();
call 0 never executed
-: 1889:
#####: 1890: if (snapshot_now->copied)
branch 0 never executed
branch 1 never executed
#####: 1891: ReorderBufferFreeSnap(rb, snapshot_now);
call 0 never executed
-: 1892:
-: 1893: /* remove potential on-disk data, and deallocate */
#####: 1894: ReorderBufferCleanupTXN(rb, txn);
call 0 never executed
-: 1895:
#####: 1896: PG_RE_THROW();
call 0 never executed
-: 1897: }
445: 1898: PG_END_TRY();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1899:}
-: 1900:
-: 1901:/*
-: 1902: * Abort a transaction that possibly has previous changes. Needs to be first
-: 1903: * called for subtransactions and then for the toplevel xid.
-: 1904: *
-: 1905: * NB: Transactions handled here have to have actively aborted (i.e. have
-: 1906: * produced an abort record). Implicitly aborted transactions are handled via
-: 1907: * ReorderBufferAbortOld(); transactions we're just not interested in, but
-: 1908: * which have committed are handled in ReorderBufferForget().
-: 1909: *
-: 1910: * This function purges this transaction and its contents from memory and
-: 1911: * disk.
-: 1912: */
-: 1913:void
function ReorderBufferAbort called 23 returned 100% blocks executed 80%
23: 1914:ReorderBufferAbort(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn)
-: 1915:{
-: 1916: ReorderBufferTXN *txn;
-: 1917:
23: 1918: txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
call 0 returned 100%
-: 1919: false);
-: 1920:
-: 1921: /* unknown, nothing to remove */
23: 1922: if (txn == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
23: 1923: return;
-: 1924:
-: 1925: /* cosmetic... */
23: 1926: txn->final_lsn = lsn;
-: 1927:
-: 1928: /* remove potential on-disk data, and deallocate */
23: 1929: ReorderBufferCleanupTXN(rb, txn);
call 0 returned 100%
-: 1930:}
-: 1931:
-: 1932:/*
-: 1933: * Abort all transactions that aren't actually running anymore because the
-: 1934: * server restarted.
-: 1935: *
-: 1936: * NB: These really have to be transactions that have aborted due to a server
-: 1937: * crash/immediate restart, as we don't deal with invalidations here.
-: 1938: */
-: 1939:void
function ReorderBufferAbortOld called 350 returned 100% blocks executed 79%
350: 1940:ReorderBufferAbortOld(ReorderBuffer *rb, TransactionId oldestRunningXid)
-: 1941:{
-: 1942: dlist_mutable_iter it;
-: 1943:
-: 1944: /*
-: 1945: * Iterate through all (potential) toplevel TXNs and abort all that are
-: 1946: * older than what possibly can be running. Once we've found the first
-: 1947: * that is alive we stop, there might be some that acquired an xid earlier
-: 1948: * but started writing later, but it's unlikely and they will be cleaned
-: 1949: * up in a later call to this function.
-: 1950: */
352: 1951: dlist_foreach_modify(it, &rb->toplevel_by_lsn)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 3%
branch 3 taken 97% (fallthrough)
-: 1952: {
-: 1953: ReorderBufferTXN *txn;
-: 1954:
11: 1955: txn = dlist_container(ReorderBufferTXN, node, it.cur);
-: 1956:
11: 1957: if (TransactionIdPrecedes(txn->xid, oldestRunningXid))
call 0 returned 100%
branch 1 taken 18% (fallthrough)
branch 2 taken 82%
-: 1958: {
-: 1959: /*
-: 1960: * We set final_lsn on a transaction when we decode its commit or
-: 1961: * abort record, but we never see those records for crashed
-: 1962: * transactions. To ensure cleanup of these transactions, set
-: 1963: * final_lsn to that of their last change; this causes
-: 1964: * ReorderBufferRestoreCleanup to do the right thing.
-: 1965: */
2: 1966: if (txn->serialized && txn->final_lsn == 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
branch 2 never executed
branch 3 never executed
-: 1967: {
#####: 1968: ReorderBufferChange *last =
call 0 never executed
#####: 1969: dlist_tail_element(ReorderBufferChange, node, &txn->changes);
-: 1970:
#####: 1971: txn->final_lsn = last->lsn;
-: 1972: }
-: 1973:
2: 1974: elog(DEBUG2, "aborting old transaction %u", txn->xid);
call 0 returned 100%
call 1 returned 100%
-: 1975:
-: 1976: /* remove potential on-disk data, and deallocate this tx */
2: 1977: ReorderBufferCleanupTXN(rb, txn);
call 0 returned 100%
-: 1978: }
-: 1979: else
18: 1980: return;
-: 1981: }
-: 1982:}
-: 1983:
-: 1984:/*
-: 1985: * Forget the contents of a transaction if we aren't interested in its
-: 1986: * contents. Needs to be first called for subtransactions and then for the
-: 1987: * toplevel xid.
-: 1988: *
-: 1989: * This is significantly different to ReorderBufferAbort() because
-: 1990: * transactions that have committed need to be treated differently from aborted
-: 1991: * ones since they may have modified the catalog.
-: 1992: *
-: 1993: * Note that this is only allowed to be called in the moment a transaction
-: 1994: * commit has just been read, not earlier; otherwise later records referring
-: 1995: * to this xid might re-create the transaction incompletely.
-: 1996: */
-: 1997:void
function ReorderBufferForget called 1519 returned 100% blocks executed 90%
1519: 1998:ReorderBufferForget(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn)
-: 1999:{
-: 2000: ReorderBufferTXN *txn;
-: 2001:
1519: 2002: txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
call 0 returned 100%
-: 2003: false);
-: 2004:
-: 2005: /* unknown, nothing to forget */
1519: 2006: if (txn == NULL)
branch 0 taken 7% (fallthrough)
branch 1 taken 93%
1619: 2007: return;
-: 2008:
-: 2009: /* cosmetic... */
1419: 2010: txn->final_lsn = lsn;
-: 2011:
-: 2012: /*
-: 2013: * Process cache invalidation messages if there are any. Even if we're not
-: 2014: * interested in the transaction's contents, it could have manipulated the
-: 2015: * catalog and we need to update the caches according to that.
-: 2016: */
1419: 2017: if (txn->base_snapshot != NULL && txn->ninvalidations > 0)
branch 0 taken 62% (fallthrough)
branch 1 taken 38%
branch 2 taken 32% (fallthrough)
branch 3 taken 68%
283: 2018: ReorderBufferImmediateInvalidation(rb, txn->ninvalidations,
call 0 returned 100%
-: 2019: txn->invalidations);
-: 2020: else
1136: 2021: Assert(txn->ninvalidations == 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2022:
-: 2023: /* remove potential on-disk data, and deallocate */
1419: 2024: ReorderBufferCleanupTXN(rb, txn);
call 0 returned 100%
-: 2025:}
-: 2026:
-: 2027:/*
-: 2028: * Execute invalidations happening outside the context of a decoded
-: 2029: * transaction. That currently happens either for xid-less commits
-: 2030: * (cf. RecordTransactionCommit()) or for invalidations in uninteresting
-: 2031: * transactions (via ReorderBufferForget()).
-: 2032: */
-: 2033:void
function ReorderBufferImmediateInvalidation called 283 returned 100% blocks executed 100%
283: 2034:ReorderBufferImmediateInvalidation(ReorderBuffer *rb, uint32 ninvalidations,
-: 2035: SharedInvalidationMessage *invalidations)
-: 2036:{
283: 2037: bool use_subtxn = IsTransactionOrTransactionBlock();
call 0 returned 100%
-: 2038: int i;
-: 2039:
283: 2040: if (use_subtxn)
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
268: 2041: BeginInternalSubTransaction("replay");
call 0 returned 100%
-: 2042:
-: 2043: /*
-: 2044: * Force invalidations to happen outside of a valid transaction - that way
-: 2045: * entries will just be marked as invalid without accessing the catalog.
-: 2046: * That's advantageous because we don't need to setup the full state
-: 2047: * necessary for catalog access.
-: 2048: */
283: 2049: if (use_subtxn)
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
268: 2050: AbortCurrentTransaction();
call 0 returned 100%
-: 2051:
13593: 2052: for (i = 0; i < ninvalidations; i++)
branch 0 taken 98%
branch 1 taken 2% (fallthrough)
13310: 2053: LocalExecuteInvalidationMessage(&invalidations[i]);
call 0 returned 100%
-: 2054:
283: 2055: if (use_subtxn)
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
268: 2056: RollbackAndReleaseCurrentSubTransaction();
call 0 returned 100%
283: 2057:}
-: 2058:
-: 2059:/*
-: 2060: * Tell reorderbuffer about an xid seen in the WAL stream. Has to be called at
-: 2061: * least once for every xid in XLogRecord->xl_xid (other places in records
-: 2062: * may, but do not have to be passed through here).
-: 2063: *
-: 2064: * Reorderbuffer keeps some datastructures about transactions in LSN order,
-: 2065: * for efficiency. To do that it has to know about when transactions are seen
-: 2066: * first in the WAL. As many types of records are not actually interesting for
-: 2067: * logical decoding, they do not necessarily pass though here.
-: 2068: */
-: 2069:void
function ReorderBufferProcessXid called 1593866 returned 100% blocks executed 100%
1593866: 2070:ReorderBufferProcessXid(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn)
-: 2071:{
-: 2072: /* many records won't have an xid assigned, centralize check here */
1593866: 2073: if (xid != InvalidTransactionId)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
1593191: 2074: ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
call 0 returned 100%
1593866: 2075:}
-: 2076:
-: 2077:/*
-: 2078: * Add a new snapshot to this transaction that may only used after lsn 'lsn'
-: 2079: * because the previous snapshot doesn't describe the catalog correctly for
-: 2080: * following rows.
-: 2081: */
-: 2082:void
function ReorderBufferAddSnapshot called 481 returned 100% blocks executed 100%
481: 2083:ReorderBufferAddSnapshot(ReorderBuffer *rb, TransactionId xid,
-: 2084: XLogRecPtr lsn, Snapshot snap)
-: 2085:{
481: 2086: ReorderBufferChange *change = ReorderBufferGetChange(rb);
call 0 returned 100%
-: 2087:
481: 2088: change->data.snapshot = snap;
481: 2089: change->action = REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT;
-: 2090:
481: 2091: ReorderBufferQueueChange(rb, xid, lsn, change);
call 0 returned 100%
481: 2092:}
-: 2093:
-: 2094:/*
-: 2095: * Set up the transaction's base snapshot.
-: 2096: *
-: 2097: * If we know that xid is a subtransaction, set the base snapshot on the
-: 2098: * top-level transaction instead.
-: 2099: */
-: 2100:void
function ReorderBufferSetBaseSnapshot called 1359 returned 100% blocks executed 80%
1359: 2101:ReorderBufferSetBaseSnapshot(ReorderBuffer *rb, TransactionId xid,
-: 2102: XLogRecPtr lsn, Snapshot snap)
-: 2103:{
-: 2104: ReorderBufferTXN *txn;
-: 2105: bool is_new;
-: 2106:
1359: 2107: AssertArg(snap != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2108:
-: 2109: /*
-: 2110: * Fetch the transaction to operate on. If we know it's a subtransaction,
-: 2111: * operate on its top-level transaction instead.
-: 2112: */
1359: 2113: txn = ReorderBufferTXNByXid(rb, xid, true, &is_new, lsn, true);
call 0 returned 100%
1359: 2114: if (txn->is_known_as_subxact)
branch 0 taken 7% (fallthrough)
branch 1 taken 93%
92: 2115: txn = ReorderBufferTXNByXid(rb, txn->toplevel_xid, false,
call 0 returned 100%
-: 2116: NULL, InvalidXLogRecPtr, false);
1359: 2117: Assert(txn->base_snapshot == NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2118:
1359: 2119: txn->base_snapshot = snap;
1359: 2120: txn->base_snapshot_lsn = lsn;
1359: 2121: dlist_push_tail(&rb->txns_by_base_snapshot_lsn, &txn->base_snapshot_node);
call 0 returned 100%
-: 2122:
1359: 2123: AssertTXNLsnOrder(rb);
call 0 returned 100%
1359: 2124:}
-: 2125:
-: 2126:/*
-: 2127: * Access the catalog with this CommandId at this point in the changestream.
-: 2128: *
-: 2129: * May only be called for command ids > 1
-: 2130: */
-: 2131:void
function ReorderBufferAddNewCommandId called 13630 returned 100% blocks executed 100%
13630: 2132:ReorderBufferAddNewCommandId(ReorderBuffer *rb, TransactionId xid,
-: 2133: XLogRecPtr lsn, CommandId cid)
-: 2134:{
13630: 2135: ReorderBufferChange *change = ReorderBufferGetChange(rb);
call 0 returned 100%
-: 2136:
13630: 2137: change->data.command_id = cid;
13630: 2138: change->action = REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID;
-: 2139:
13630: 2140: ReorderBufferQueueChange(rb, xid, lsn, change);
call 0 returned 100%
13630: 2141:}
-: 2142:
-: 2143:/*
-: 2144: * Update the memory accounting info. We track memory used by the whole
-: 2145: * reorder buffer and the transaction containing the change.
-: 2146: */
-: 2147:static void
function ReorderBufferChangeMemoryUpdate called 2702588 returned 100% blocks executed 83%
2702588: 2148:ReorderBufferChangeMemoryUpdate(ReorderBuffer *rb,
-: 2149: ReorderBufferChange *change,
-: 2150: bool addition)
-: 2151:{
-: 2152: Size sz;
-: 2153:
2702588: 2154: Assert(change->txn);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2155:
-: 2156: /*
-: 2157: * Ignore tuple CID changes, because those are not evicted when
-: 2158: * reaching memory limit. So we just don't count them, because it
-: 2159: * might easily trigger a pointless attempt to spill/stream.
-: 2160: */
2702588: 2161: if (change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
2716218: 2162: return;
-: 2163:
2688958: 2164: sz = ReorderBufferChangeSize(change);
call 0 returned 100%
-: 2165:
2688958: 2166: if (addition)
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
-: 2167: {
1344481: 2168: change->txn->size += sz;
1344481: 2169: rb->size += sz;
-: 2170: }
-: 2171: else
-: 2172: {
1344477: 2173: Assert((rb->size >= sz) && (change->txn->size >= sz));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
call 4 never executed
1344477: 2174: change->txn->size -= sz;
1344477: 2175: rb->size -= sz;
-: 2176: }
-: 2177:}
-: 2178:
-: 2179:/*
-: 2180: * Add new (relfilenode, tid) -> (cmin, cmax) mappings.
-: 2181: *
-: 2182: * We do not include this change type in memory accounting, because we
-: 2183: * keep CIDs in a separate list and do not evict them when reaching
-: 2184: * the memory limit.
-: 2185: */
-: 2186:void
function ReorderBufferAddNewTupleCids called 13630 returned 100% blocks executed 100%
13630: 2187:ReorderBufferAddNewTupleCids(ReorderBuffer *rb, TransactionId xid,
-: 2188: XLogRecPtr lsn, RelFileNode node,
-: 2189: ItemPointerData tid, CommandId cmin,
-: 2190: CommandId cmax, CommandId combocid)
-: 2191:{
13630: 2192: ReorderBufferChange *change = ReorderBufferGetChange(rb);
call 0 returned 100%
-: 2193: ReorderBufferTXN *txn;
-: 2194:
13630: 2195: txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
call 0 returned 100%
-: 2196:
13630: 2197: change->data.tuplecid.node = node;
13630: 2198: change->data.tuplecid.tid = tid;
13630: 2199: change->data.tuplecid.cmin = cmin;
13630: 2200: change->data.tuplecid.cmax = cmax;
13630: 2201: change->data.tuplecid.combocid = combocid;
13630: 2202: change->lsn = lsn;
13630: 2203: change->txn = txn;
13630: 2204: change->action = REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID;
-: 2205:
13630: 2206: dlist_push_tail(&txn->tuplecids, &change->node);
call 0 returned 100%
13630: 2207: txn->ntuplecids++;
13630: 2208:}
-: 2209:
-: 2210:/*
-: 2211: * Setup the invalidation of the toplevel transaction.
-: 2212: *
-: 2213: * This needs to be done before ReorderBufferCommit is called!
-: 2214: */
-: 2215:void
function ReorderBufferAddInvalidations called 451 returned 100% blocks executed 56%
451: 2216:ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid,
-: 2217: XLogRecPtr lsn, Size nmsgs,
-: 2218: SharedInvalidationMessage *msgs)
-: 2219:{
-: 2220: ReorderBufferTXN *txn;
-: 2221:
451: 2222: txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
call 0 returned 100%
-: 2223:
451: 2224: if (txn->ninvalidations != 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2225: elog(ERROR, "only ever add one set of invalidations");
call 0 never executed
call 1 never executed
call 2 never executed
-: 2226:
451: 2227: Assert(nmsgs > 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2228:
451: 2229: txn->ninvalidations = nmsgs;
451: 2230: txn->invalidations = (SharedInvalidationMessage *)
451: 2231: MemoryContextAlloc(rb->context,
call 0 returned 100%
-: 2232: sizeof(SharedInvalidationMessage) * nmsgs);
451: 2233: memcpy(txn->invalidations, msgs,
-: 2234: sizeof(SharedInvalidationMessage) * nmsgs);
451: 2235:}
-: 2236:
-: 2237:/*
-: 2238: * Apply all invalidations we know. Possibly we only need parts at this point
-: 2239: * in the changestream but we don't know which those are.
-: 2240: */
-: 2241:static void
function ReorderBufferExecuteInvalidations called 1230 returned 100% blocks executed 100%
1230: 2242:ReorderBufferExecuteInvalidations(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 2243:{
-: 2244: int i;
-: 2245:
107454: 2246: for (i = 0; i < txn->ninvalidations; i++)
branch 0 taken 99%
branch 1 taken 1% (fallthrough)
106224: 2247: LocalExecuteInvalidationMessage(&txn->invalidations[i]);
call 0 returned 100%
1230: 2248:}
-: 2249:
-: 2250:/*
-: 2251: * Mark a transaction as containing catalog changes
-: 2252: */
-: 2253:void
function ReorderBufferXidSetCatalogChanges called 14681 returned 100% blocks executed 100%
14681: 2254:ReorderBufferXidSetCatalogChanges(ReorderBuffer *rb, TransactionId xid,
-: 2255: XLogRecPtr lsn)
-: 2256:{
-: 2257: ReorderBufferTXN *txn;
-: 2258:
14681: 2259: txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
call 0 returned 100%
-: 2260:
14681: 2261: txn->has_catalog_changes = true;
14681: 2262:}
-: 2263:
-: 2264:/*
-: 2265: * Query whether a transaction is already *known* to contain catalog
-: 2266: * changes. This can be wrong until directly before the commit!
-: 2267: */
-: 2268:bool
function ReorderBufferXidHasCatalogChanges called 2080 returned 100% blocks executed 100%
2080: 2269:ReorderBufferXidHasCatalogChanges(ReorderBuffer *rb, TransactionId xid)
-: 2270:{
-: 2271: ReorderBufferTXN *txn;
-: 2272:
2080: 2273: txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
call 0 returned 100%
-: 2274: false);
2080: 2275: if (txn == NULL)
branch 0 taken 6% (fallthrough)
branch 1 taken 94%
116: 2276: return false;
-: 2277:
1964: 2278: return txn->has_catalog_changes;
-: 2279:}
-: 2280:
-: 2281:/*
-: 2282: * ReorderBufferXidHasBaseSnapshot
-: 2283: * Have we already set the base snapshot for the given txn/subtxn?
-: 2284: */
-: 2285:bool
function ReorderBufferXidHasBaseSnapshot called 1192910 returned 100% blocks executed 86%
1192910: 2286:ReorderBufferXidHasBaseSnapshot(ReorderBuffer *rb, TransactionId xid)
-: 2287:{
-: 2288: ReorderBufferTXN *txn;
-: 2289:
1192910: 2290: txn = ReorderBufferTXNByXid(rb, xid, false,
call 0 returned 100%
-: 2291: NULL, InvalidXLogRecPtr, false);
-: 2292:
-: 2293: /* transaction isn't known yet, ergo no snapshot */
1192910: 2294: if (txn == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2295: return false;
-: 2296:
-: 2297: /* a known subtxn? operate on top-level txn instead */
1192910: 2298: if (txn->is_known_as_subxact)
branch 0 taken 28% (fallthrough)
branch 1 taken 72%
335521: 2299: txn = ReorderBufferTXNByXid(rb, txn->toplevel_xid, false,
call 0 returned 100%
-: 2300: NULL, InvalidXLogRecPtr, false);
-: 2301:
1192910: 2302: return txn->base_snapshot != NULL;
-: 2303:}
-: 2304:
-: 2305:
-: 2306:/*
-: 2307: * ---------------------------------------
-: 2308: * Disk serialization support
-: 2309: * ---------------------------------------
-: 2310: */
-: 2311:
-: 2312:/*
-: 2313: * Ensure the IO buffer is >= sz.
-: 2314: */
-: 2315:static void
function ReorderBufferSerializeReserve called 2566128 returned 100% blocks executed 100%
2566128: 2316:ReorderBufferSerializeReserve(ReorderBuffer *rb, Size sz)
-: 2317:{
2566128: 2318: if (!rb->outbufsize)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 2319: {
29: 2320: rb->outbuf = MemoryContextAlloc(rb->context, sz);
call 0 returned 100%
29: 2321: rb->outbufsize = sz;
-: 2322: }
2566099: 2323: else if (rb->outbufsize < sz)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 2324: {
252: 2325: rb->outbuf = repalloc(rb->outbuf, sz);
call 0 returned 100%
252: 2326: rb->outbufsize = sz;
-: 2327: }
2566128: 2328:}
-: 2329:
-: 2330:/*
-: 2331: * Find the largest transaction (toplevel or subxact) to evict (spill to disk).
-: 2332: *
-: 2333: * XXX With many subtransactions this might be quite slow, because we'll have
-: 2334: * to walk through all of them. There are some options how we could improve
-: 2335: * that: (a) maintain some secondary structure with transactions sorted by
-: 2336: * amount of changes, (b) not looking for the entirely largest transaction,
-: 2337: * but e.g. for transaction using at least some fraction of the memory limit,
-: 2338: * and (c) evicting multiple transactions at once, e.g. to free a given portion
-: 2339: * of the memory limit (e.g. 50%).
-: 2340: */
-: 2341:static ReorderBufferTXN *
function ReorderBufferLargestTXN called 2618 returned 100% blocks executed 80%
2618: 2342:ReorderBufferLargestTXN(ReorderBuffer *rb)
-: 2343:{
-: 2344: HASH_SEQ_STATUS hash_seq;
-: 2345: ReorderBufferTXNByIdEnt *ent;
2618: 2346: ReorderBufferTXN *largest = NULL;
-: 2347:
2618: 2348: hash_seq_init(&hash_seq, rb->by_txn);
call 0 returned 100%
9560: 2349: while ((ent = hash_seq_search(&hash_seq)) != NULL)
call 0 returned 100%
branch 1 taken 62%
branch 2 taken 38% (fallthrough)
-: 2350: {
4324: 2351: ReorderBufferTXN *txn = ent->txn;
-: 2352:
-: 2353: /* if the current transaction is larger, remember it */
4324: 2354: if ((!largest) || (txn->size > largest->size))
branch 0 taken 39% (fallthrough)
branch 1 taken 61%
branch 2 taken 61% (fallthrough)
branch 3 taken 39%
3666: 2355: largest = txn;
-: 2356: }
-: 2357:
2618: 2358: Assert(largest);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
2618: 2359: Assert(largest->size > 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
2618: 2360: Assert(largest->size <= rb->size);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2361:
2618: 2362: return largest;
-: 2363:}
-: 2364:
-: 2365:/*
-: 2366: * Check whether the logical_decoding_work_mem limit was reached, and if yes
-: 2367: * pick the transaction to evict and spill the changes to disk.
-: 2368: *
-: 2369: * XXX At this point we select just a single (largest) transaction, but
-: 2370: * we might also adapt a more elaborate eviction strategy - for example
-: 2371: * evicting enough transactions to free certain fraction (e.g. 50%) of
-: 2372: * the memory limit.
-: 2373: */
-: 2374:static void
function ReorderBufferCheckMemoryLimit called 1197196 returned 100% blocks executed 73%
1197196: 2375:ReorderBufferCheckMemoryLimit(ReorderBuffer *rb)
-: 2376:{
-: 2377: ReorderBufferTXN *txn;
-: 2378:
-: 2379: /* bail out if we haven't exceeded the memory limit */
1197196: 2380: if (rb->size < logical_decoding_work_mem * 1024L)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
2391774: 2381: return;
-: 2382:
-: 2383: /*
-: 2384: * Pick the largest transaction (or subtransaction) and evict it from
-: 2385: * memory by serializing it to disk.
-: 2386: */
2618: 2387: txn = ReorderBufferLargestTXN(rb);
call 0 returned 100%
-: 2388:
2618: 2389: ReorderBufferSerializeTXN(rb, txn);
call 0 returned 100%
-: 2390:
-: 2391: /*
-: 2392: * After eviction, the transaction should have no entries in memory, and
-: 2393: * should use 0 bytes for changes.
-: 2394: */
2618: 2395: Assert(txn->size == 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
2618: 2396: Assert(txn->nentries_mem == 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2397:
-: 2398: /*
-: 2399: * And furthermore, evicting the transaction should get us below the
-: 2400: * memory limit again - it is not possible that we're still exceeding the
-: 2401: * memory limit after evicting the transaction.
-: 2402: *
-: 2403: * This follows from the simple fact that the selected transaction is at
-: 2404: * least as large as the most recent change (which caused us to go over
-: 2405: * the memory limit). So by evicting it we're definitely back below the
-: 2406: * memory limit.
-: 2407: */
2618: 2408: Assert(rb->size < logical_decoding_work_mem * 1024L);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2409:}
-: 2410:
-: 2411:/*
-: 2412: * Spill data of a large transaction (and its subtransactions) to disk.
-: 2413: */
-: 2414:static void
function ReorderBufferSerializeTXN called 2915 returned 100% blocks executed 71%
2915: 2415:ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 2416:{
-: 2417: dlist_iter subtxn_i;
-: 2418: dlist_mutable_iter change_i;
2915: 2419: int fd = -1;
2915: 2420: XLogSegNo curOpenSegNo = 0;
2915: 2421: Size spilled = 0;
2915: 2422: Size size = txn->size;
-: 2423:
2915: 2424: elog(DEBUG2, "spill %u changes in XID %u to disk",
call 0 returned 100%
call 1 returned 100%
-: 2425: (uint32) txn->nentries_mem, txn->xid);
-: 2426:
-: 2427: /* do the same to all child TXs */
3183: 2428: dlist_foreach(subtxn_i, &txn->subtxns)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 8%
branch 3 taken 92% (fallthrough)
-: 2429: {
-: 2430: ReorderBufferTXN *subtxn;
-: 2431:
268: 2432: subtxn = dlist_container(ReorderBufferTXN, node, subtxn_i.cur);
268: 2433: ReorderBufferSerializeTXN(rb, subtxn);
call 0 returned 100%
-: 2434: }
-: 2435:
-: 2436: /* serialize changestream */
1147467: 2437: dlist_foreach_modify(change_i, &txn->changes)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 99%
branch 3 taken 1% (fallthrough)
-: 2438: {
-: 2439: ReorderBufferChange *change;
-: 2440:
1144552: 2441: change = dlist_container(ReorderBufferChange, node, change_i.cur);
-: 2442:
-: 2443: /*
-: 2444: * store in segment in which it belongs by start lsn, don't split over
-: 2445: * multiple segments tho
-: 2446: */
2286440: 2447: if (fd == -1 ||
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
1141888: 2448: !XLByteInSeg(change->lsn, curOpenSegNo, wal_segment_size))
-: 2449: {
-: 2450: char path[MAXPGPATH];
-: 2451:
2664: 2452: if (fd != -1)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2453: CloseTransientFile(fd);
call 0 never executed
-: 2454:
2664: 2455: XLByteToSeg(change->lsn, curOpenSegNo, wal_segment_size);
-: 2456:
-: 2457: /*
-: 2458: * No need to care about TLIs here, only used during a single run,
-: 2459: * so each LSN only maps to a specific WAL record.
-: 2460: */
2664: 2461: ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid,
call 0 returned 100%
-: 2462: curOpenSegNo);
-: 2463:
-: 2464: /* open segment, create it if necessary */
2664: 2465: fd = OpenTransientFile(path,
call 0 returned 100%
-: 2466: O_CREAT | O_WRONLY | O_APPEND | PG_BINARY);
-: 2467:
2664: 2468: if (fd < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2469: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2470: (errcode_for_file_access(),
-: 2471: errmsg("could not open file \"%s\": %m", path)));
-: 2472: }
-: 2473:
1144552: 2474: ReorderBufferSerializeChange(rb, txn, fd, change);
call 0 returned 100%
1144552: 2475: dlist_delete(&change->node);
call 0 returned 100%
1144552: 2476: ReorderBufferReturnChange(rb, change);
call 0 returned 100%
-: 2477:
1144552: 2478: spilled++;
-: 2479: }
-: 2480:
-: 2481: /* update the statistics */
2915: 2482: rb->spillCount += 1;
2915: 2483: rb->spillBytes += size;
-: 2484:
-: 2485: /* Don't consider already serialized transaction. */
2915: 2486: rb->spillTxns += txn->serialized ? 0 : 1;
-: 2487:
2915: 2488: Assert(spilled == txn->nentries_mem);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
2915: 2489: Assert(dlist_is_empty(&txn->changes));
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
2915: 2490: txn->nentries_mem = 0;
2915: 2491: txn->serialized = true;
-: 2492:
2915: 2493: if (fd != -1)
branch 0 taken 91% (fallthrough)
branch 1 taken 9%
2664: 2494: CloseTransientFile(fd);
call 0 returned 100%
2915: 2495:}
-: 2496:
-: 2497:/*
-: 2498: * Serialize individual change to disk.
-: 2499: */
-: 2500:static void
function ReorderBufferSerializeChange called 1144552 returned 100% blocks executed 62%
1144552: 2501:ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 2502: int fd, ReorderBufferChange *change)
-: 2503:{
-: 2504: ReorderBufferDiskChange *ondisk;
1144552: 2505: Size sz = sizeof(ReorderBufferDiskChange);
-: 2506:
1144552: 2507: ReorderBufferSerializeReserve(rb, sz);
call 0 returned 100%
-: 2508:
1144552: 2509: ondisk = (ReorderBufferDiskChange *) rb->outbuf;
1144552: 2510: memcpy(&ondisk->change, change, sizeof(ReorderBufferChange));
-: 2511:
1144552: 2512: switch (change->action)
branch 0 taken 99%
branch 1 taken 1%
branch 2 taken 1%
branch 3 taken 0%
branch 4 taken 1%
branch 5 taken 0%
-: 2513: {
-: 2514: /* fall through these, they're all similar enough */
-: 2515: case REORDER_BUFFER_CHANGE_INSERT:
-: 2516: case REORDER_BUFFER_CHANGE_UPDATE:
-: 2517: case REORDER_BUFFER_CHANGE_DELETE:
-: 2518: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
-: 2519: {
-: 2520: char *data;
-: 2521: ReorderBufferTupleBuf *oldtup,
-: 2522: *newtup;
1127401: 2523: Size oldlen = 0;
1127401: 2524: Size newlen = 0;
-: 2525:
1127401: 2526: oldtup = change->data.tp.oldtuple;
1127401: 2527: newtup = change->data.tp.newtuple;
-: 2528:
1127401: 2529: if (oldtup)
branch 0 taken 5% (fallthrough)
branch 1 taken 95%
-: 2530: {
60027: 2531: sz += sizeof(HeapTupleData);
60027: 2532: oldlen = oldtup->tuple.t_len;
60027: 2533: sz += oldlen;
-: 2534: }
-: 2535:
1127401: 2536: if (newtup)
branch 0 taken 90% (fallthrough)
branch 1 taken 10%
-: 2537: {
1013670: 2538: sz += sizeof(HeapTupleData);
1013670: 2539: newlen = newtup->tuple.t_len;
1013670: 2540: sz += newlen;
-: 2541: }
-: 2542:
-: 2543: /* make sure we have enough space */
1127401: 2544: ReorderBufferSerializeReserve(rb, sz);
call 0 returned 100%
-: 2545:
1127401: 2546: data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
-: 2547: /* might have been reallocated above */
1127401: 2548: ondisk = (ReorderBufferDiskChange *) rb->outbuf;
-: 2549:
1127401: 2550: if (oldlen)
branch 0 taken 5% (fallthrough)
branch 1 taken 95%
-: 2551: {
60027: 2552: memcpy(data, &oldtup->tuple, sizeof(HeapTupleData));
60027: 2553: data += sizeof(HeapTupleData);
-: 2554:
60027: 2555: memcpy(data, oldtup->tuple.t_data, oldlen);
60027: 2556: data += oldlen;
-: 2557: }
-: 2558:
1127401: 2559: if (newlen)
branch 0 taken 90% (fallthrough)
branch 1 taken 10%
-: 2560: {
1013670: 2561: memcpy(data, &newtup->tuple, sizeof(HeapTupleData));
1013670: 2562: data += sizeof(HeapTupleData);
-: 2563:
1013670: 2564: memcpy(data, newtup->tuple.t_data, newlen);
1013670: 2565: data += newlen;
-: 2566: }
1127401: 2567: break;
-: 2568: }
-: 2569: case REORDER_BUFFER_CHANGE_MESSAGE:
-: 2570: {
-: 2571: char *data;
12: 2572: Size prefix_size = strlen(change->data.msg.prefix) + 1;
-: 2573:
12: 2574: sz += prefix_size + change->data.msg.message_size +
-: 2575: sizeof(Size) + sizeof(Size);
12: 2576: ReorderBufferSerializeReserve(rb, sz);
call 0 returned 100%
-: 2577:
12: 2578: data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
-: 2579:
-: 2580: /* might have been reallocated above */
12: 2581: ondisk = (ReorderBufferDiskChange *) rb->outbuf;
-: 2582:
-: 2583: /* write the prefix including the size */
12: 2584: memcpy(data, &prefix_size, sizeof(Size));
12: 2585: data += sizeof(Size);
12: 2586: memcpy(data, change->data.msg.prefix,
-: 2587: prefix_size);
12: 2588: data += prefix_size;
-: 2589:
-: 2590: /* write the message including the size */
12: 2591: memcpy(data, &change->data.msg.message_size, sizeof(Size));
12: 2592: data += sizeof(Size);
12: 2593: memcpy(data, change->data.msg.message,
-: 2594: change->data.msg.message_size);
12: 2595: data += change->data.msg.message_size;
-: 2596:
12: 2597: break;
-: 2598: }
-: 2599: case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
-: 2600: {
-: 2601: Snapshot snap;
-: 2602: char *data;
-: 2603:
2: 2604: snap = change->data.snapshot;
-: 2605:
2: 2606: sz += sizeof(SnapshotData) +
4: 2607: sizeof(TransactionId) * snap->xcnt +
2: 2608: sizeof(TransactionId) * snap->subxcnt
-: 2609: ;
-: 2610:
-: 2611: /* make sure we have enough space */
2: 2612: ReorderBufferSerializeReserve(rb, sz);
call 0 returned 100%
2: 2613: data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
-: 2614: /* might have been reallocated above */
2: 2615: ondisk = (ReorderBufferDiskChange *) rb->outbuf;
-: 2616:
2: 2617: memcpy(data, snap, sizeof(SnapshotData));
2: 2618: data += sizeof(SnapshotData);
-: 2619:
2: 2620: if (snap->xcnt)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 2621: {
2: 2622: memcpy(data, snap->xip,
2: 2623: sizeof(TransactionId) * snap->xcnt);
2: 2624: data += sizeof(TransactionId) * snap->xcnt;
-: 2625: }
-: 2626:
2: 2627: if (snap->subxcnt)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 2628: {
#####: 2629: memcpy(data, snap->subxip,
#####: 2630: sizeof(TransactionId) * snap->subxcnt);
#####: 2631: data += sizeof(TransactionId) * snap->subxcnt;
-: 2632: }
2: 2633: break;
-: 2634: }
-: 2635: case REORDER_BUFFER_CHANGE_TRUNCATE:
-: 2636: {
-: 2637: Size size;
-: 2638: char *data;
-: 2639:
-: 2640: /* account for the OIDs of truncated relations */
#####: 2641: size = sizeof(Oid) * change->data.truncate.nrelids;
#####: 2642: sz += size;
-: 2643:
-: 2644: /* make sure we have enough space */
#####: 2645: ReorderBufferSerializeReserve(rb, sz);
call 0 never executed
-: 2646:
#####: 2647: data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
-: 2648: /* might have been reallocated above */
#####: 2649: ondisk = (ReorderBufferDiskChange *) rb->outbuf;
-: 2650:
#####: 2651: memcpy(data, change->data.truncate.relids, size);
#####: 2652: data += size;
-: 2653:
#####: 2654: break;
-: 2655: }
-: 2656: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
-: 2657: case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
-: 2658: case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
-: 2659: /* ReorderBufferChange contains everything important */
17137: 2660: break;
-: 2661: }
-: 2662:
1144552: 2663: ondisk->size = sz;
-: 2664:
1144552: 2665: errno = 0;
call 0 returned 100%
1144552: 2666: pgstat_report_wait_start(WAIT_EVENT_REORDER_BUFFER_WRITE);
call 0 returned 100%
1144552: 2667: if (write(fd, rb->outbuf, ondisk->size) != ondisk->size)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 2668: {
#####: 2669: int save_errno = errno;
call 0 never executed
-: 2670:
#####: 2671: CloseTransientFile(fd);
call 0 never executed
-: 2672:
-: 2673: /* if write didn't set errno, assume problem is no disk space */
#####: 2674: errno = save_errno ? save_errno : ENOSPC;
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 2675: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2676: (errcode_for_file_access(),
-: 2677: errmsg("could not write to data file for XID %u: %m",
-: 2678: txn->xid)));
-: 2679: }
1144552: 2680: pgstat_report_wait_end();
call 0 returned 100%
-: 2681:
1144552: 2682: Assert(ondisk->change.action == change->action);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1144552: 2683:}
-: 2684:
-: 2685:/*
-: 2686: * Size of a change in memory.
-: 2687: */
-: 2688:static Size
function ReorderBufferChangeSize called 2688958 returned 100% blocks executed 100%
2688958: 2689:ReorderBufferChangeSize(ReorderBufferChange *change)
-: 2690:{
2688958: 2691: Size sz = sizeof(ReorderBufferChange);
-: 2692:
2688958: 2693: switch (change->action)
branch 0 taken 97%
branch 1 taken 1%
branch 2 taken 1%
branch 3 taken 1%
branch 4 taken 2%
branch 5 taken 0%
-: 2694: {
-: 2695: /* fall through these, they're all similar enough */
-: 2696: case REORDER_BUFFER_CHANGE_INSERT:
-: 2697: case REORDER_BUFFER_CHANGE_UPDATE:
-: 2698: case REORDER_BUFFER_CHANGE_DELETE:
-: 2699: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
-: 2700: {
-: 2701: ReorderBufferTupleBuf *oldtup,
-: 2702: *newtup;
2621122: 2703: Size oldlen = 0;
2621122: 2704: Size newlen = 0;
-: 2705:
2621122: 2706: oldtup = change->data.tp.oldtuple;
2621122: 2707: newtup = change->data.tp.newtuple;
-: 2708:
2621122: 2709: if (oldtup)
branch 0 taken 5% (fallthrough)
branch 1 taken 95%
-: 2710: {
132042: 2711: sz += sizeof(HeapTupleData);
132042: 2712: oldlen = oldtup->tuple.t_len;
132042: 2713: sz += oldlen;
-: 2714: }
-: 2715:
2621122: 2716: if (newtup)
branch 0 taken 90% (fallthrough)
branch 1 taken 10%
-: 2717: {
2351584: 2718: sz += sizeof(HeapTupleData);
2351584: 2719: newlen = newtup->tuple.t_len;
2351584: 2720: sz += newlen;
-: 2721: }
-: 2722:
2621122: 2723: break;
-: 2724: }
-: 2725: case REORDER_BUFFER_CHANGE_MESSAGE:
-: 2726: {
46: 2727: Size prefix_size = strlen(change->data.msg.prefix) + 1;
-: 2728:
46: 2729: sz += prefix_size + change->data.msg.message_size +
-: 2730: sizeof(Size) + sizeof(Size);
-: 2731:
46: 2732: break;
-: 2733: }
-: 2734: case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
-: 2735: {
-: 2736: Snapshot snap;
-: 2737:
964: 2738: snap = change->data.snapshot;
-: 2739:
964: 2740: sz += sizeof(SnapshotData) +
1928: 2741: sizeof(TransactionId) * snap->xcnt +
964: 2742: sizeof(TransactionId) * snap->subxcnt;
-: 2743:
964: 2744: break;
-: 2745: }
-: 2746: case REORDER_BUFFER_CHANGE_TRUNCATE:
-: 2747: {
16: 2748: sz += sizeof(Oid) * change->data.truncate.nrelids;
-: 2749:
16: 2750: break;
-: 2751: }
-: 2752: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
-: 2753: case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
-: 2754: case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
-: 2755: /* ReorderBufferChange contains everything important */
66810: 2756: break;
-: 2757: }
-: 2758:
2688958: 2759: return sz;
-: 2760:}
-: 2761:
-: 2762:
-: 2763:/*
-: 2764: * Restore a number of changes spilled to disk back into memory.
-: 2765: */
-: 2766:static Size
function ReorderBufferRestoreChanges called 83 returned 100% blocks executed 48%
83: 2767:ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 2768: int *fd, XLogSegNo *segno)
-: 2769:{
83: 2770: Size restored = 0;
-: 2771: XLogSegNo last_segno;
-: 2772: dlist_mutable_iter cleanup_iter;
-: 2773:
83: 2774: Assert(txn->first_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
83: 2775: Assert(txn->final_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2776:
-: 2777: /* free current entries, so we have memory for more */
145070: 2778: dlist_foreach_modify(cleanup_iter, &txn->changes)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 99%
branch 3 taken 1% (fallthrough)
-: 2779: {
144987: 2780: ReorderBufferChange *cleanup =
144987: 2781: dlist_container(ReorderBufferChange, node, cleanup_iter.cur);
-: 2782:
144987: 2783: dlist_delete(&cleanup->node);
call 0 returned 100%
144987: 2784: ReorderBufferReturnChange(rb, cleanup);
call 0 returned 100%
-: 2785: }
83: 2786: txn->nentries_mem = 0;
83: 2787: Assert(dlist_is_empty(&txn->changes));
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 2788:
83: 2789: XLByteToSeg(txn->final_lsn, last_segno, wal_segment_size);
-: 2790:
147261: 2791: while (restored < max_changes_in_memory && *segno <= last_segno)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 99%
branch 3 taken 1% (fallthrough)
-: 2792: {
-: 2793: int readBytes;
-: 2794: ReorderBufferDiskChange *ondisk;
-: 2795:
147095: 2796: if (*fd == -1)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 2797: {
-: 2798: char path[MAXPGPATH];
-: 2799:
-: 2800: /* first time in */
29: 2801: if (*segno == 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
29: 2802: XLByteToSeg(txn->first_lsn, *segno, wal_segment_size);
-: 2803:
29: 2804: Assert(*segno != 0 || dlist_is_empty(&txn->changes));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
branch 3 never executed
branch 4 never executed
call 5 never executed
-: 2805:
-: 2806: /*
-: 2807: * No need to care about TLIs here, only used during a single run,
-: 2808: * so each LSN only maps to a specific WAL record.
-: 2809: */
29: 2810: ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid,
call 0 returned 100%
-: 2811: *segno);
-: 2812:
29: 2813: *fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
call 0 returned 100%
29: 2814: if (*fd < 0 && errno == ENOENT)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
branch 3 never executed
branch 4 never executed
-: 2815: {
#####: 2816: *fd = -1;
#####: 2817: (*segno)++;
#####: 2818: continue;
-: 2819: }
29: 2820: else if (*fd < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2821: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2822: (errcode_for_file_access(),
-: 2823: errmsg("could not open file \"%s\": %m",
-: 2824: path)));
-: 2825: }
-: 2826:
-: 2827: /*
-: 2828: * Read the statically sized part of a change which has information
-: 2829: * about the total size. If we couldn't read a record, we're at the
-: 2830: * end of this file.
-: 2831: */
147095: 2832: ReorderBufferSerializeReserve(rb, sizeof(ReorderBufferDiskChange));
call 0 returned 100%
147095: 2833: pgstat_report_wait_start(WAIT_EVENT_REORDER_BUFFER_READ);
call 0 returned 100%
147095: 2834: readBytes = read(*fd, rb->outbuf, sizeof(ReorderBufferDiskChange));
call 0 returned 100%
147095: 2835: pgstat_report_wait_end();
call 0 returned 100%
-: 2836:
-: 2837: /* eof */
147095: 2838: if (readBytes == 0)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 2839: {
29: 2840: CloseTransientFile(*fd);
call 0 returned 100%
29: 2841: *fd = -1;
29: 2842: (*segno)++;
29: 2843: continue;
-: 2844: }
147066: 2845: else if (readBytes < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2846: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2847: (errcode_for_file_access(),
-: 2848: errmsg("could not read from reorderbuffer spill file: %m")));
147066: 2849: else if (readBytes != sizeof(ReorderBufferDiskChange))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2850: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2851: (errcode_for_file_access(),
-: 2852: errmsg("could not read from reorderbuffer spill file: read %d instead of %u bytes",
-: 2853: readBytes,
-: 2854: (uint32) sizeof(ReorderBufferDiskChange))));
-: 2855:
147066: 2856: ondisk = (ReorderBufferDiskChange *) rb->outbuf;
-: 2857:
147066: 2858: ReorderBufferSerializeReserve(rb,
call 0 returned 100%
147066: 2859: sizeof(ReorderBufferDiskChange) + ondisk->size);
147066: 2860: ondisk = (ReorderBufferDiskChange *) rb->outbuf;
-: 2861:
147066: 2862: pgstat_report_wait_start(WAIT_EVENT_REORDER_BUFFER_READ);
call 0 returned 100%
147066: 2863: readBytes = read(*fd, rb->outbuf + sizeof(ReorderBufferDiskChange),
call 0 returned 100%
147066: 2864: ondisk->size - sizeof(ReorderBufferDiskChange));
147066: 2865: pgstat_report_wait_end();
call 0 returned 100%
-: 2866:
147066: 2867: if (readBytes < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2868: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2869: (errcode_for_file_access(),
-: 2870: errmsg("could not read from reorderbuffer spill file: %m")));
147066: 2871: else if (readBytes != ondisk->size - sizeof(ReorderBufferDiskChange))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2872: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2873: (errcode_for_file_access(),
-: 2874: errmsg("could not read from reorderbuffer spill file: read %d instead of %u bytes",
-: 2875: readBytes,
-: 2876: (uint32) (ondisk->size - sizeof(ReorderBufferDiskChange)))));
-: 2877:
-: 2878: /*
-: 2879: * ok, read a full change from disk, now restore it into proper
-: 2880: * in-memory format
-: 2881: */
147066: 2882: ReorderBufferRestoreChange(rb, txn, rb->outbuf);
call 0 returned 100%
147066: 2883: restored++;
-: 2884: }
-: 2885:
83: 2886: return restored;
-: 2887:}
-: 2888:
-: 2889:/*
-: 2890: * Convert change from its on-disk format to in-memory format and queue it onto
-: 2891: * the TXN's ->changes list.
-: 2892: *
-: 2893: * Note: although "data" is declared char*, at entry it points to a
-: 2894: * maxalign'd buffer, making it safe in most of this function to assume
-: 2895: * that the pointed-to data is suitably aligned for direct access.
-: 2896: */
-: 2897:static void
function ReorderBufferRestoreChange called 147066 returned 100% blocks executed 87%
147066: 2898:ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 2899: char *data)
-: 2900:{
-: 2901: ReorderBufferDiskChange *ondisk;
-: 2902: ReorderBufferChange *change;
-: 2903:
147066: 2904: ondisk = (ReorderBufferDiskChange *) data;
-: 2905:
147066: 2906: change = ReorderBufferGetChange(rb);
call 0 returned 100%
-: 2907:
-: 2908: /* copy static part */
147066: 2909: memcpy(change, &ondisk->change, sizeof(ReorderBufferChange));
-: 2910:
147066: 2911: data += sizeof(ReorderBufferDiskChange);
-: 2912:
-: 2913: /* restore individual stuff */
147066: 2914: switch (change->action)
branch 0 taken 99%
branch 1 taken 1%
branch 2 taken 1%
branch 3 taken 0%
branch 4 taken 1%
branch 5 taken 0%
-: 2915: {
-: 2916: /* fall through these, they're all similar enough */
-: 2917: case REORDER_BUFFER_CHANGE_INSERT:
-: 2918: case REORDER_BUFFER_CHANGE_UPDATE:
-: 2919: case REORDER_BUFFER_CHANGE_DELETE:
-: 2920: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
145188: 2921: if (change->data.tp.oldtuple)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
-: 2922: {
5006: 2923: uint32 tuplelen = ((HeapTuple) data)->t_len;
-: 2924:
5006: 2925: change->data.tp.oldtuple =
5006: 2926: ReorderBufferGetTupleBuf(rb, tuplelen - SizeofHeapTupleHeader);
call 0 returned 100%
-: 2927:
-: 2928: /* restore ->tuple */
5006: 2929: memcpy(&change->data.tp.oldtuple->tuple, data,
-: 2930: sizeof(HeapTupleData));
5006: 2931: data += sizeof(HeapTupleData);
-: 2932:
-: 2933: /* reset t_data pointer into the new tuplebuf */
10012: 2934: change->data.tp.oldtuple->tuple.t_data =
5006: 2935: ReorderBufferTupleBufData(change->data.tp.oldtuple);
-: 2936:
-: 2937: /* restore tuple data itself */
5006: 2938: memcpy(change->data.tp.oldtuple->tuple.t_data, data, tuplelen);
5006: 2939: data += tuplelen;
-: 2940: }
-: 2941:
145188: 2942: if (change->data.tp.newtuple)
branch 0 taken 93% (fallthrough)
branch 1 taken 7%
-: 2943: {
-: 2944: /* here, data might not be suitably aligned! */
-: 2945: uint32 tuplelen;
-: 2946:
134968: 2947: memcpy(&tuplelen, data + offsetof(HeapTupleData, t_len),
-: 2948: sizeof(uint32));
-: 2949:
134968: 2950: change->data.tp.newtuple =
134968: 2951: ReorderBufferGetTupleBuf(rb, tuplelen - SizeofHeapTupleHeader);
call 0 returned 100%
-: 2952:
-: 2953: /* restore ->tuple */
134968: 2954: memcpy(&change->data.tp.newtuple->tuple, data,
-: 2955: sizeof(HeapTupleData));
134968: 2956: data += sizeof(HeapTupleData);
-: 2957:
-: 2958: /* reset t_data pointer into the new tuplebuf */
269936: 2959: change->data.tp.newtuple->tuple.t_data =
134968: 2960: ReorderBufferTupleBufData(change->data.tp.newtuple);
-: 2961:
-: 2962: /* restore tuple data itself */
134968: 2963: memcpy(change->data.tp.newtuple->tuple.t_data, data, tuplelen);
134968: 2964: data += tuplelen;
-: 2965: }
-: 2966:
145188: 2967: break;
-: 2968: case REORDER_BUFFER_CHANGE_MESSAGE:
-: 2969: {
-: 2970: Size prefix_size;
-: 2971:
-: 2972: /* read prefix */
1: 2973: memcpy(&prefix_size, data, sizeof(Size));
1: 2974: data += sizeof(Size);
1: 2975: change->data.msg.prefix = MemoryContextAlloc(rb->context,
call 0 returned 100%
-: 2976: prefix_size);
1: 2977: memcpy(change->data.msg.prefix, data, prefix_size);
1: 2978: Assert(change->data.msg.prefix[prefix_size - 1] == '\0');
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1: 2979: data += prefix_size;
-: 2980:
-: 2981: /* read the message */
1: 2982: memcpy(&change->data.msg.message_size, data, sizeof(Size));
1: 2983: data += sizeof(Size);
1: 2984: change->data.msg.message = MemoryContextAlloc(rb->context,
call 0 returned 100%
-: 2985: change->data.msg.message_size);
1: 2986: memcpy(change->data.msg.message, data,
-: 2987: change->data.msg.message_size);
1: 2988: data += change->data.msg.message_size;
-: 2989:
1: 2990: break;
-: 2991: }
-: 2992: case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
-: 2993: {
-: 2994: Snapshot oldsnap;
-: 2995: Snapshot newsnap;
-: 2996: Size size;
-: 2997:
2: 2998: oldsnap = (Snapshot) data;
-: 2999:
2: 3000: size = sizeof(SnapshotData) +
4: 3001: sizeof(TransactionId) * oldsnap->xcnt +
2: 3002: sizeof(TransactionId) * (oldsnap->subxcnt + 0);
-: 3003:
2: 3004: change->data.snapshot = MemoryContextAllocZero(rb->context, size);
call 0 returned 100%
-: 3005:
2: 3006: newsnap = change->data.snapshot;
-: 3007:
2: 3008: memcpy(newsnap, data, size);
2: 3009: newsnap->xip = (TransactionId *)
-: 3010: (((char *) newsnap) + sizeof(SnapshotData));
2: 3011: newsnap->subxip = newsnap->xip + newsnap->xcnt;
2: 3012: newsnap->copied = true;
2: 3013: break;
-: 3014: }
-: 3015: /* the base struct contains all the data, easy peasy */
-: 3016: case REORDER_BUFFER_CHANGE_TRUNCATE:
-: 3017: {
-: 3018: Oid *relids;
-: 3019:
#####: 3020: relids = ReorderBufferGetRelids(rb,
call 0 never executed
#####: 3021: change->data.truncate.nrelids);
#####: 3022: memcpy(relids, data, change->data.truncate.nrelids * sizeof(Oid));
#####: 3023: change->data.truncate.relids = relids;
-: 3024:
#####: 3025: break;
-: 3026: }
-: 3027: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
-: 3028: case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
-: 3029: case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
1875: 3030: break;
-: 3031: }
-: 3032:
147066: 3033: dlist_push_tail(&txn->changes, &change->node);
call 0 returned 100%
147066: 3034: txn->nentries_mem++;
-: 3035:
-: 3036: /*
-: 3037: * Update memory accounting for the restored change. We need to do this
-: 3038: * although we don't check the memory limit when restoring the changes in
-: 3039: * this branch (we only do that when initially queueing the changes after
-: 3040: * decoding), because we will release the changes later, and that will
-: 3041: * update the accounting too (subtracting the size from the counters).
-: 3042: * And we don't want to underflow there.
-: 3043: */
147066: 3044: ReorderBufferChangeMemoryUpdate(rb, change, true);
call 0 returned 100%
147066: 3045:}
-: 3046:
-: 3047:/*
-: 3048: * Remove all on-disk stored for the passed in transaction.
-: 3049: */
-: 3050:static void
function ReorderBufferRestoreCleanup called 191 returned 100% blocks executed 45%
191: 3051:ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 3052:{
-: 3053: XLogSegNo first;
-: 3054: XLogSegNo cur;
-: 3055: XLogSegNo last;
-: 3056:
191: 3057: Assert(txn->first_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
191: 3058: Assert(txn->final_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3059:
191: 3060: XLByteToSeg(txn->first_lsn, first, wal_segment_size);
191: 3061: XLByteToSeg(txn->final_lsn, last, wal_segment_size);
-: 3062:
-: 3063: /* iterate over all possible filenames, and delete them */
382: 3064: for (cur = first; cur <= last; cur++)
branch 0 taken 50%
branch 1 taken 50% (fallthrough)
-: 3065: {
-: 3066: char path[MAXPGPATH];
-: 3067:
191: 3068: ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid, cur);
call 0 returned 100%
191: 3069: if (unlink(path) != 0 && errno != ENOENT)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
branch 4 never executed
branch 5 never executed
#####: 3070: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 3071: (errcode_for_file_access(),
-: 3072: errmsg("could not remove file \"%s\": %m", path)));
-: 3073: }
191: 3074:}
-: 3075:
-: 3076:/*
-: 3077: * Remove any leftover serialized reorder buffers from a slot directory after a
-: 3078: * prior crash or decoding session exit.
-: 3079: */
-: 3080:static void
function ReorderBufferCleanupSerializedTXNs called 535 returned 100% blocks executed 52%
535: 3081:ReorderBufferCleanupSerializedTXNs(const char *slotname)
-: 3082:{
-: 3083: DIR *spill_dir;
-: 3084: struct dirent *spill_de;
-: 3085: struct stat statbuf;
-: 3086: char path[MAXPGPATH * 2 + 12];
-: 3087:
535: 3088: sprintf(path, "pg_replslot/%s", slotname);
call 0 returned 100%
-: 3089:
-: 3090: /* we're only handling directories here, skip if it's not ours */
535: 3091: if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
branch 3 taken 0% (fallthrough)
branch 4 taken 100%
535: 3092: return;
-: 3093:
535: 3094: spill_dir = AllocateDir(path);
call 0 returned 100%
535: 3095: while ((spill_de = ReadDirExtended(spill_dir, path, INFO)) != NULL)
call 0 returned 100%
branch 1 taken 75%
branch 2 taken 25% (fallthrough)
-: 3096: {
-: 3097: /* only look at names that can be ours */
1605: 3098: if (strncmp(spill_de->d_name, "xid", 3) == 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 3099: {
#####: 3100: snprintf(path, sizeof(path),
call 0 never executed
-: 3101: "pg_replslot/%s/%s", slotname,
#####: 3102: spill_de->d_name);
-: 3103:
#####: 3104: if (unlink(path) != 0)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 3105: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 3106: (errcode_for_file_access(),
-: 3107: errmsg("could not remove file \"%s\" during removal of pg_replslot/%s/xid*: %m",
-: 3108: path, slotname)));
-: 3109: }
-: 3110: }
535: 3111: FreeDir(spill_dir);
call 0 returned 100%
-: 3112:}
-: 3113:
-: 3114:/*
-: 3115: * Given a replication slot, transaction ID and segment number, fill in the
-: 3116: * corresponding spill file into 'path', which is a caller-owned buffer of size
-: 3117: * at least MAXPGPATH.
-: 3118: */
-: 3119:static void
function ReorderBufferSerializedPath called 2884 returned 100% blocks executed 100%
2884: 3120:ReorderBufferSerializedPath(char *path, ReplicationSlot *slot, TransactionId xid,
-: 3121: XLogSegNo segno)
-: 3122:{
-: 3123: XLogRecPtr recptr;
-: 3124:
2884: 3125: XLogSegNoOffsetToRecPtr(segno, 0, wal_segment_size, recptr);
-: 3126:
8652: 3127: snprintf(path, MAXPGPATH, "pg_replslot/%s/xid-%u-lsn-%X-%X.spill",
call 0 returned 100%
2884: 3128: NameStr(MyReplicationSlot->data.name),
-: 3129: xid,
2884: 3130: (uint32) (recptr >> 32), (uint32) recptr);
2884: 3131:}
-: 3132:
-: 3133:/*
-: 3134: * Delete all data spilled to disk after we've restarted/crashed. It will be
-: 3135: * recreated when the respective slots are reused.
-: 3136: */
-: 3137:void
function StartupReorderBuffer called 546 returned 100% blocks executed 92%
546: 3138:StartupReorderBuffer(void)
-: 3139:{
-: 3140: DIR *logical_dir;
-: 3141: struct dirent *logical_de;
-: 3142:
546: 3143: logical_dir = AllocateDir("pg_replslot");
call 0 returned 100%
2194: 3144: while ((logical_de = ReadDir(logical_dir, "pg_replslot")) != NULL)
call 0 returned 100%
branch 1 taken 67%
branch 2 taken 33% (fallthrough)
-: 3145: {
1658: 3146: if (strcmp(logical_de->d_name, ".") == 0 ||
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
branch 2 taken 98% (fallthrough)
branch 3 taken 2%
556: 3147: strcmp(logical_de->d_name, "..") == 0)
1092: 3148: continue;
-: 3149:
-: 3150: /* if it cannot be a slot, skip the directory */
10: 3151: if (!ReplicationSlotValidateName(logical_de->d_name, DEBUG2))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 3152: continue;
-: 3153:
-: 3154: /*
-: 3155: * ok, has to be a surviving logical slot, iterate and delete
-: 3156: * everything starting with xid-*
-: 3157: */
10: 3158: ReorderBufferCleanupSerializedTXNs(logical_de->d_name);
call 0 returned 100%
-: 3159: }
546: 3160: FreeDir(logical_dir);
call 0 returned 100%
546: 3161:}
-: 3162:
-: 3163:/* ---------------------------------------
-: 3164: * toast reassembly support
-: 3165: * ---------------------------------------
-: 3166: */
-: 3167:
-: 3168:/*
-: 3169: * Initialize per tuple toast reconstruction support.
-: 3170: */
-: 3171:static void
function ReorderBufferToastInitHash called 17 returned 100% blocks executed 75%
17: 3172:ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 3173:{
-: 3174: HASHCTL hash_ctl;
-: 3175:
17: 3176: Assert(txn->toast_hash == NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3177:
17: 3178: memset(&hash_ctl, 0, sizeof(hash_ctl));
17: 3179: hash_ctl.keysize = sizeof(Oid);
17: 3180: hash_ctl.entrysize = sizeof(ReorderBufferToastEnt);
17: 3181: hash_ctl.hcxt = rb->context;
17: 3182: txn->toast_hash = hash_create("ReorderBufferToastHash", 5, &hash_ctl,
call 0 returned 100%
-: 3183: HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
17: 3184:}
-: 3185:
-: 3186:/*
-: 3187: * Per toast-chunk handling for toast reconstruction
-: 3188: *
-: 3189: * Appends a toast chunk so we can reconstruct it when the tuple "owning" the
-: 3190: * toasted Datum comes along.
-: 3191: */
-: 3192:static void
function ReorderBufferToastAppendChunk called 301 returned 100% blocks executed 45%
301: 3193:ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 3194: Relation relation, ReorderBufferChange *change)
-: 3195:{
-: 3196: ReorderBufferToastEnt *ent;
-: 3197: ReorderBufferTupleBuf *newtup;
-: 3198: bool found;
-: 3199: int32 chunksize;
-: 3200: bool isnull;
-: 3201: Pointer chunk;
301: 3202: TupleDesc desc = RelationGetDescr(relation);
-: 3203: Oid chunk_id;
-: 3204: int32 chunk_seq;
-: 3205:
301: 3206: if (txn->toast_hash == NULL)
branch 0 taken 6% (fallthrough)
branch 1 taken 94%
17: 3207: ReorderBufferToastInitHash(rb, txn);
call 0 returned 100%
-: 3208:
301: 3209: Assert(IsToastRelation(relation));
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 3210:
301: 3211: newtup = change->data.tp.newtuple;
301: 3212: chunk_id = DatumGetObjectId(fastgetattr(&newtup->tuple, 1, desc, &isnull));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 100% (fallthrough)
branch 5 taken 0%
branch 6 taken 0% (fallthrough)
branch 7 taken 100%
branch 8 taken 100% (fallthrough)
branch 9 taken 0%
branch 10 never executed
branch 11 never executed
branch 12 never executed
branch 13 never executed
call 14 never executed
call 15 never executed
branch 16 never executed
branch 17 never executed
call 18 never executed
301: 3213: Assert(!isnull);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
301: 3214: chunk_seq = DatumGetInt32(fastgetattr(&newtup->tuple, 2, desc, &isnull));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 97% (fallthrough)
branch 3 taken 3%
branch 4 taken 100% (fallthrough)
branch 5 taken 0%
branch 6 taken 0% (fallthrough)
branch 7 taken 100%
branch 8 taken 100% (fallthrough)
branch 9 taken 0%
branch 10 never executed
branch 11 never executed
branch 12 never executed
branch 13 never executed
call 14 never executed
call 15 returned 100%
branch 16 never executed
branch 17 never executed
call 18 never executed
301: 3215: Assert(!isnull);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3216:
301: 3217: ent = (ReorderBufferToastEnt *)
call 0 returned 100%
301: 3218: hash_search(txn->toast_hash,
-: 3219: (void *) &chunk_id,
-: 3220: HASH_ENTER,
-: 3221: &found);
-: 3222:
301: 3223: if (!found)
branch 0 taken 7% (fallthrough)
branch 1 taken 93%
-: 3224: {
21: 3225: Assert(ent->chunk_id == chunk_id);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
21: 3226: ent->num_chunks = 0;
21: 3227: ent->last_chunk_seq = 0;
21: 3228: ent->size = 0;
21: 3229: ent->reconstructed = NULL;
21: 3230: dlist_init(&ent->chunks);
call 0 returned 100%
-: 3231:
21: 3232: if (chunk_seq != 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3233: elog(ERROR, "got sequence entry %d for toast chunk %u instead of seq 0",
call 0 never executed
call 1 never executed
call 2 never executed
-: 3234: chunk_seq, chunk_id);
-: 3235: }
280: 3236: else if (found && chunk_seq != ent->last_chunk_seq + 1)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 3237: elog(ERROR, "got sequence entry %d for toast chunk %u instead of seq %d",
call 0 never executed
call 1 never executed
call 2 never executed
-: 3238: chunk_seq, chunk_id, ent->last_chunk_seq + 1);
-: 3239:
301: 3240: chunk = DatumGetPointer(fastgetattr(&newtup->tuple, 3, desc, &isnull));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 97% (fallthrough)
branch 3 taken 3%
branch 4 taken 0% (fallthrough)
branch 5 taken 100%
branch 6 never executed
branch 7 never executed
branch 8 never executed
branch 9 never executed
branch 10 never executed
branch 11 never executed
branch 12 never executed
branch 13 never executed
call 14 never executed
call 15 returned 100%
branch 16 never executed
branch 17 never executed
call 18 never executed
301: 3241: Assert(!isnull);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3242:
-: 3243: /* calculate size so we can allocate the right size at once later */
301: 3244: if (!VARATT_IS_EXTENDED(chunk))
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
301: 3245: chunksize = VARSIZE(chunk) - VARHDRSZ;
#####: 3246: else if (VARATT_IS_SHORT(chunk))
branch 0 never executed
branch 1 never executed
-: 3247: /* could happen due to heap_form_tuple doing its thing */
#####: 3248: chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
-: 3249: else
#####: 3250: elog(ERROR, "unexpected type of toast chunk");
call 0 never executed
call 1 never executed
call 2 never executed
-: 3251:
301: 3252: ent->size += chunksize;
301: 3253: ent->last_chunk_seq = chunk_seq;
301: 3254: ent->num_chunks++;
301: 3255: dlist_push_tail(&ent->chunks, &change->node);
call 0 returned 100%
301: 3256:}
-: 3257:
-: 3258:/*
-: 3259: * Rejigger change->newtuple to point to in-memory toast tuples instead to
-: 3260: * on-disk toast tuples that may not longer exist (think DROP TABLE or VACUUM).
-: 3261: *
-: 3262: * We cannot replace unchanged toast tuples though, so those will still point
-: 3263: * to on-disk toast data.
-: 3264: *
-: 3265: * While updating the existing change with detoasted tuple data, we need to
-: 3266: * update the memory accounting info, because the change size will differ.
-: 3267: * Otherwise the accounting may get out of sync, triggering serialization
-: 3268: * at unexpected times.
-: 3269: *
-: 3270: * We simply subtract size of the change before rejiggering the tuple, and
-: 3271: * then adding the new size. This makes it look like the change was removed
-: 3272: * and then added back, except it only tweaks the accounting info.
-: 3273: *
-: 3274: * In particular it can't trigger serialization, which would be pointless
-: 3275: * anyway as it happens during commit processing right before handing
-: 3276: * the change to the output plugin.
-: 3277: */
-: 3278:static void
function ReorderBufferToastReplace called 146981 returned 100% blocks executed 65%
146981: 3279:ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 3280: Relation relation, ReorderBufferChange *change)
-: 3281:{
-: 3282: TupleDesc desc;
-: 3283: int natt;
-: 3284: Datum *attrs;
-: 3285: bool *isnull;
-: 3286: bool *free;
-: 3287: HeapTuple tmphtup;
-: 3288: Relation toast_rel;
-: 3289: TupleDesc toast_desc;
-: 3290: MemoryContext oldcontext;
-: 3291: ReorderBufferTupleBuf *newtup;
-: 3292:
-: 3293: /* no toast tuples changed */
146981: 3294: if (txn->toast_hash == NULL)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
293743: 3295: return;
-: 3296:
-: 3297: /*
-: 3298: * We're going to modify the size of the change, so to make sure the
-: 3299: * accounting is correct we'll make it look like we're removing the
-: 3300: * change now (with the old size), and then re-add it at the end.
-: 3301: */
219: 3302: ReorderBufferChangeMemoryUpdate(rb, change, false);
call 0 returned 100%
-: 3303:
219: 3304: oldcontext = MemoryContextSwitchTo(rb->context);
call 0 returned 100%
-: 3305:
-: 3306: /* we should only have toast tuples in an INSERT or UPDATE */
219: 3307: Assert(change->data.tp.newtuple);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3308:
219: 3309: desc = RelationGetDescr(relation);
-: 3310:
219: 3311: toast_rel = RelationIdGetRelation(relation->rd_rel->reltoastrelid);
call 0 returned 100%
219: 3312: if (!RelationIsValid(toast_rel))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3313: elog(ERROR, "could not open relation with OID %u",
call 0 never executed
call 1 never executed
call 2 never executed
-: 3314: relation->rd_rel->reltoastrelid);
-: 3315:
219: 3316: toast_desc = RelationGetDescr(toast_rel);
-: 3317:
-: 3318: /* should we allocate from stack instead? */
219: 3319: attrs = palloc0(sizeof(Datum) * desc->natts);
call 0 returned 100%
219: 3320: isnull = palloc0(sizeof(bool) * desc->natts);
call 0 returned 100%
219: 3321: free = palloc0(sizeof(bool) * desc->natts);
call 0 returned 100%
-: 3322:
219: 3323: newtup = change->data.tp.newtuple;
-: 3324:
219: 3325: heap_deform_tuple(&newtup->tuple, desc, attrs, isnull);
call 0 returned 100%
-: 3326:
698: 3327: for (natt = 0; natt < desc->natts; natt++)
branch 0 taken 69%
branch 1 taken 31% (fallthrough)
-: 3328: {
479: 3329: Form_pg_attribute attr = TupleDescAttr(desc, natt);
-: 3330: ReorderBufferToastEnt *ent;
-: 3331: struct varlena *varlena;
-: 3332:
-: 3333: /* va_rawsize is the size of the original datum -- including header */
-: 3334: struct varatt_external toast_pointer;
-: 3335: struct varatt_indirect redirect_pointer;
479: 3336: struct varlena *new_datum = NULL;
-: 3337: struct varlena *reconstructed;
-: 3338: dlist_iter it;
479: 3339: Size data_done = 0;
-: 3340:
-: 3341: /* system columns aren't toasted */
479: 3342: if (attr->attnum < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
458: 3343: continue;
-: 3344:
479: 3345: if (attr->attisdropped)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3346: continue;
-: 3347:
-: 3348: /* not a varlena datatype */
479: 3349: if (attr->attlen != -1)
branch 0 taken 49% (fallthrough)
branch 1 taken 51%
237: 3350: continue;
-: 3351:
-: 3352: /* no data */
242: 3353: if (isnull[natt])
branch 0 taken 5% (fallthrough)
branch 1 taken 95%
12: 3354: continue;
-: 3355:
-: 3356: /* ok, we know we have a toast datum */
230: 3357: varlena = (struct varlena *) DatumGetPointer(attrs[natt]);
-: 3358:
-: 3359: /* no need to do anything if the tuple isn't external */
230: 3360: if (!VARATT_IS_EXTERNAL(varlena))
branch 0 taken 87% (fallthrough)
branch 1 taken 13%
201: 3361: continue;
-: 3362:
29: 3363: VARATT_EXTERNAL_GET_POINTER(toast_pointer, varlena);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
branch 3 taken 100% (fallthrough)
branch 4 taken 0%
branch 5 taken 100% (fallthrough)
branch 6 taken 0%
branch 7 taken 0% (fallthrough)
branch 8 taken 100%
call 9 never executed
branch 10 taken 0% (fallthrough)
branch 11 taken 100%
call 12 never executed
-: 3364:
-: 3365: /*
-: 3366: * Check whether the toast tuple changed, replace if so.
-: 3367: */
29: 3368: ent = (ReorderBufferToastEnt *)
call 0 returned 100%
29: 3369: hash_search(txn->toast_hash,
-: 3370: (void *) &toast_pointer.va_valueid,
-: 3371: HASH_FIND,
-: 3372: NULL);
29: 3373: if (ent == NULL)
branch 0 taken 28% (fallthrough)
branch 1 taken 72%
8: 3374: continue;
-: 3375:
21: 3376: new_datum =
call 0 returned 100%
-: 3377: (struct varlena *) palloc0(INDIRECT_POINTER_SIZE);
-: 3378:
21: 3379: free[natt] = true;
-: 3380:
21: 3381: reconstructed = palloc0(toast_pointer.va_rawsize);
call 0 returned 100%
-: 3382:
21: 3383: ent->reconstructed = reconstructed;
-: 3384:
-: 3385: /* stitch toast tuple back together from its parts */
322: 3386: dlist_foreach(it, &ent->chunks)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 93%
branch 3 taken 7% (fallthrough)
-: 3387: {
-: 3388: bool isnull;
-: 3389: ReorderBufferChange *cchange;
-: 3390: ReorderBufferTupleBuf *ctup;
-: 3391: Pointer chunk;
-: 3392:
301: 3393: cchange = dlist_container(ReorderBufferChange, node, it.cur);
301: 3394: ctup = cchange->data.tp.newtuple;
301: 3395: chunk = DatumGetPointer(
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 0% (fallthrough)
branch 5 taken 100%
branch 6 never executed
branch 7 never executed
branch 8 never executed
branch 9 never executed
branch 10 never executed
branch 11 never executed
branch 12 never executed
branch 13 never executed
call 14 never executed
call 15 never executed
branch 16 never executed
branch 17 never executed
call 18 never executed
-: 3396: fastgetattr(&ctup->tuple, 3, toast_desc, &isnull));
-: 3397:
301: 3398: Assert(!isnull);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
301: 3399: Assert(!VARATT_IS_EXTERNAL(chunk));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
301: 3400: Assert(!VARATT_IS_SHORT(chunk));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3401:
602: 3402: memcpy(VARDATA(reconstructed) + data_done,
301: 3403: VARDATA(chunk),
301: 3404: VARSIZE(chunk) - VARHDRSZ);
301: 3405: data_done += VARSIZE(chunk) - VARHDRSZ;
-: 3406: }
21: 3407: Assert(data_done == toast_pointer.va_extsize);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3408:
-: 3409: /* make sure its marked as compressed or not */
21: 3410: if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
branch 0 taken 14% (fallthrough)
branch 1 taken 86%
3: 3411: SET_VARSIZE_COMPRESSED(reconstructed, data_done + VARHDRSZ);
-: 3412: else
18: 3413: SET_VARSIZE(reconstructed, data_done + VARHDRSZ);
-: 3414:
21: 3415: memset(&redirect_pointer, 0, sizeof(redirect_pointer));
21: 3416: redirect_pointer.pointer = reconstructed;
-: 3417:
21: 3418: SET_VARTAG_EXTERNAL(new_datum, VARTAG_INDIRECT);
21: 3419: memcpy(VARDATA_EXTERNAL(new_datum), &redirect_pointer,
-: 3420: sizeof(redirect_pointer));
-: 3421:
21: 3422: attrs[natt] = PointerGetDatum(new_datum);
-: 3423: }
-: 3424:
-: 3425: /*
-: 3426: * Build tuple in separate memory & copy tuple back into the tuplebuf
-: 3427: * passed to the output plugin. We can't directly heap_fill_tuple() into
-: 3428: * the tuplebuf because attrs[] will point back into the current content.
-: 3429: */
219: 3430: tmphtup = heap_form_tuple(desc, attrs, isnull);
call 0 returned 100%
219: 3431: Assert(newtup->tuple.t_len <= MaxHeapTupleSize);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
219: 3432: Assert(ReorderBufferTupleBufData(newtup) == newtup->tuple.t_data);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3433:
219: 3434: memcpy(newtup->tuple.t_data, tmphtup->t_data, tmphtup->t_len);
219: 3435: newtup->tuple.t_len = tmphtup->t_len;
-: 3436:
-: 3437: /*
-: 3438: * free resources we won't further need, more persistent stuff will be
-: 3439: * free'd in ReorderBufferToastReset().
-: 3440: */
219: 3441: RelationClose(toast_rel);
call 0 returned 100%
219: 3442: pfree(tmphtup);
call 0 returned 100%
698: 3443: for (natt = 0; natt < desc->natts; natt++)
branch 0 taken 69%
branch 1 taken 31% (fallthrough)
-: 3444: {
479: 3445: if (free[natt])
branch 0 taken 4% (fallthrough)
branch 1 taken 96%
21: 3446: pfree(DatumGetPointer(attrs[natt]));
call 0 returned 100%
-: 3447: }
219: 3448: pfree(attrs);
call 0 returned 100%
219: 3449: pfree(free);
call 0 returned 100%
219: 3450: pfree(isnull);
call 0 returned 100%
-: 3451:
219: 3452: MemoryContextSwitchTo(oldcontext);
call 0 returned 100%
-: 3453:
-: 3454: /* now add the change back, with the correct size */
219: 3455: ReorderBufferChangeMemoryUpdate(rb, change, true);
call 0 returned 100%
-: 3456:}
-: 3457:
-: 3458:/*
-: 3459: * Free all resources allocated for toast reconstruction.
-: 3460: */
-: 3461:static void
function ReorderBufferToastReset called 146779 returned 100% blocks executed 95%
146779: 3462:ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 3463:{
-: 3464: HASH_SEQ_STATUS hstat;
-: 3465: ReorderBufferToastEnt *ent;
-: 3466:
146779: 3467: if (txn->toast_hash == NULL)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
293541: 3468: return;
-: 3469:
-: 3470: /* sequentially walk over the hash and free everything */
17: 3471: hash_seq_init(&hstat, txn->toast_hash);
call 0 returned 100%
55: 3472: while ((ent = (ReorderBufferToastEnt *) hash_seq_search(&hstat)) != NULL)
call 0 returned 100%
branch 1 taken 55%
branch 2 taken 45% (fallthrough)
-: 3473: {
-: 3474: dlist_mutable_iter it;
-: 3475:
21: 3476: if (ent->reconstructed != NULL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
21: 3477: pfree(ent->reconstructed);
call 0 returned 100%
-: 3478:
322: 3479: dlist_foreach_modify(it, &ent->chunks)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 93%
branch 3 taken 7% (fallthrough)
-: 3480: {
301: 3481: ReorderBufferChange *change =
301: 3482: dlist_container(ReorderBufferChange, node, it.cur);
-: 3483:
301: 3484: dlist_delete(&change->node);
call 0 returned 100%
301: 3485: ReorderBufferReturnChange(rb, change);
call 0 returned 100%
-: 3486: }
-: 3487: }
-: 3488:
17: 3489: hash_destroy(txn->toast_hash);
call 0 returned 100%
17: 3490: txn->toast_hash = NULL;
-: 3491:}
-: 3492:
-: 3493:
-: 3494:/* ---------------------------------------
-: 3495: * Visibility support for logical decoding
-: 3496: *
-: 3497: *
-: 3498: * Lookup actual cmin/cmax values when using decoding snapshot. We can't
-: 3499: * always rely on stored cmin/cmax values because of two scenarios:
-: 3500: *
-: 3501: * * A tuple got changed multiple times during a single transaction and thus
-: 3502: * has got a combocid. Combocid's are only valid for the duration of a
-: 3503: * single transaction.
-: 3504: * * A tuple with a cmin but no cmax (and thus no combocid) got
-: 3505: * deleted/updated in another transaction than the one which created it
-: 3506: * which we are looking at right now. As only one of cmin, cmax or combocid
-: 3507: * is actually stored in the heap we don't have access to the value we
-: 3508: * need anymore.
-: 3509: *
-: 3510: * To resolve those problems we have a per-transaction hash of (cmin,
-: 3511: * cmax) tuples keyed by (relfilenode, ctid) which contains the actual
-: 3512: * (cmin, cmax) values. That also takes care of combocids by simply
-: 3513: * not caring about them at all. As we have the real cmin/cmax values
-: 3514: * combocids aren't interesting.
-: 3515: *
-: 3516: * As we only care about catalog tuples here the overhead of this
-: 3517: * hashtable should be acceptable.
-: 3518: *
-: 3519: * Heap rewrites complicate this a bit, check rewriteheap.c for
-: 3520: * details.
-: 3521: * -------------------------------------------------------------------------
-: 3522: */
-: 3523:
-: 3524:/* struct for sorting mapping files by LSN efficiently */
-: 3525:typedef struct RewriteMappingFile
-: 3526:{
-: 3527: XLogRecPtr lsn;
-: 3528: char fname[MAXPGPATH];
-: 3529:} RewriteMappingFile;
-: 3530:
-: 3531:#ifdef NOT_USED
-: 3532:static void
-: 3533:DisplayMapping(HTAB *tuplecid_data)
-: 3534:{
-: 3535: HASH_SEQ_STATUS hstat;
-: 3536: ReorderBufferTupleCidEnt *ent;
-: 3537:
-: 3538: hash_seq_init(&hstat, tuplecid_data);
-: 3539: while ((ent = (ReorderBufferTupleCidEnt *) hash_seq_search(&hstat)) != NULL)
-: 3540: {
-: 3541: elog(DEBUG3, "mapping: node: %u/%u/%u tid: %u/%u cmin: %u, cmax: %u",
-: 3542: ent->key.relnode.dbNode,
-: 3543: ent->key.relnode.spcNode,
-: 3544: ent->key.relnode.relNode,
-: 3545: ItemPointerGetBlockNumber(&ent->key.tid),
-: 3546: ItemPointerGetOffsetNumber(&ent->key.tid),
-: 3547: ent->cmin,
-: 3548: ent->cmax
-: 3549: );
-: 3550: }
-: 3551:}
-: 3552:#endif
-: 3553:
-: 3554:/*
-: 3555: * Apply a single mapping file to tuplecid_data.
-: 3556: *
-: 3557: * The mapping file has to have been verified to be a) committed b) for our
-: 3558: * transaction c) applied in LSN order.
-: 3559: */
-: 3560:static void
function ApplyLogicalMappingFile called 22 returned 100% blocks executed 41%
22: 3561:ApplyLogicalMappingFile(HTAB *tuplecid_data, Oid relid, const char *fname)
-: 3562:{
-: 3563: char path[MAXPGPATH];
-: 3564: int fd;
-: 3565: int readBytes;
-: 3566: LogicalRewriteMappingData map;
-: 3567:
22: 3568: sprintf(path, "pg_logical/mappings/%s", fname);
call 0 returned 100%
22: 3569: fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
call 0 returned 100%
22: 3570: if (fd < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3571: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 3572: (errcode_for_file_access(),
-: 3573: errmsg("could not open file \"%s\": %m", path)));
-: 3574:
-: 3575: while (true)
-: 3576: {
-: 3577: ReorderBufferTupleCidKey key;
-: 3578: ReorderBufferTupleCidEnt *ent;
-: 3579: ReorderBufferTupleCidEnt *new_ent;
-: 3580: bool found;
-: 3581:
-: 3582: /* be careful about padding */
141: 3583: memset(&key, 0, sizeof(ReorderBufferTupleCidKey));
-: 3584:
-: 3585: /* read all mappings till the end of the file */
141: 3586: pgstat_report_wait_start(WAIT_EVENT_REORDER_LOGICAL_MAPPING_READ);
call 0 returned 100%
141: 3587: readBytes = read(fd, &map, sizeof(LogicalRewriteMappingData));
call 0 returned 100%
141: 3588: pgstat_report_wait_end();
call 0 returned 100%
-: 3589:
141: 3590: if (readBytes < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3591: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 3592: (errcode_for_file_access(),
-: 3593: errmsg("could not read file \"%s\": %m",
-: 3594: path)));
141: 3595: else if (readBytes == 0) /* EOF */
branch 0 taken 16% (fallthrough)
branch 1 taken 84%
22: 3596: break;
119: 3597: else if (readBytes != sizeof(LogicalRewriteMappingData))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3598: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 3599: (errcode_for_file_access(),
-: 3600: errmsg("could not read from file \"%s\": read %d instead of %d bytes",
-: 3601: path, readBytes,
-: 3602: (int32) sizeof(LogicalRewriteMappingData))));
-: 3603:
119: 3604: key.relnode = map.old_node;
119: 3605: ItemPointerCopy(&map.old_tid,
-: 3606: &key.tid);
-: 3607:
-: 3608:
119: 3609: ent = (ReorderBufferTupleCidEnt *)
call 0 returned 100%
-: 3610: hash_search(tuplecid_data,
-: 3611: (void *) &key,
-: 3612: HASH_FIND,
-: 3613: NULL);
-: 3614:
-: 3615: /* no existing mapping, no need to update */
119: 3616: if (!ent)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3617: continue;
-: 3618:
119: 3619: key.relnode = map.new_node;
119: 3620: ItemPointerCopy(&map.new_tid,
-: 3621: &key.tid);
-: 3622:
119: 3623: new_ent = (ReorderBufferTupleCidEnt *)
call 0 returned 100%
-: 3624: hash_search(tuplecid_data,
-: 3625: (void *) &key,
-: 3626: HASH_ENTER,
-: 3627: &found);
-: 3628:
119: 3629: if (found)
branch 0 taken 5% (fallthrough)
branch 1 taken 95%
-: 3630: {
-: 3631: /*
-: 3632: * Make sure the existing mapping makes sense. We sometime update
-: 3633: * old records that did not yet have a cmax (e.g. pg_class' own
-: 3634: * entry while rewriting it) during rewrites, so allow that.
-: 3635: */
6: 3636: Assert(ent->cmin == InvalidCommandId || ent->cmin == new_ent->cmin);
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
call 4 never executed
6: 3637: Assert(ent->cmax == InvalidCommandId || ent->cmax == new_ent->cmax);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
branch 2 never executed
branch 3 never executed
call 4 never executed
-: 3638: }
-: 3639: else
-: 3640: {
-: 3641: /* update mapping */
113: 3642: new_ent->cmin = ent->cmin;
113: 3643: new_ent->cmax = ent->cmax;
113: 3644: new_ent->combocid = ent->combocid;
-: 3645: }
119: 3646: }
-: 3647:
22: 3648: if (CloseTransientFile(fd) != 0)
call 0 returned 100%
branch 1 taken 0%
branch 2 taken 100%
#####: 3649: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 3650: (errcode_for_file_access(),
-: 3651: errmsg("could not close file \"%s\": %m", path)));
22: 3652:}
-: 3653:
-: 3654:
-: 3655:/*
-: 3656: * Check whether the TransactionId 'xid' is in the pre-sorted array 'xip'.
-: 3657: */
-: 3658:static bool
function TransactionIdInArray called 290 returned 100% blocks executed 100%
290: 3659:TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
-: 3660:{
290: 3661: return bsearch(&xid, xip, num,
call 0 returned 100%
-: 3662: sizeof(TransactionId), xidComparator) != NULL;
-: 3663:}
-: 3664:
-: 3665:/*
-: 3666: * list_sort() comparator for sorting RewriteMappingFiles in LSN order.
-: 3667: */
-: 3668:static int
function file_sort_by_lsn called 17 returned 100% blocks executed 50%
17: 3669:file_sort_by_lsn(const ListCell *a_p, const ListCell *b_p)
-: 3670:{
17: 3671: RewriteMappingFile *a = (RewriteMappingFile *) lfirst(a_p);
17: 3672: RewriteMappingFile *b = (RewriteMappingFile *) lfirst(b_p);
-: 3673:
17: 3674: if (a->lsn < b->lsn)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
17: 3675: return -1;
#####: 3676: else if (a->lsn > b->lsn)
branch 0 never executed
branch 1 never executed
#####: 3677: return 1;
#####: 3678: return 0;
-: 3679:}
-: 3680:
-: 3681:/*
-: 3682: * Apply any existing logical remapping files if there are any targeted at our
-: 3683: * transaction for relid.
-: 3684: */
-: 3685:static void
function UpdateLogicalMappings called 5 returned 100% blocks executed 87%
5: 3686:UpdateLogicalMappings(HTAB *tuplecid_data, Oid relid, Snapshot snapshot)
-: 3687:{
-: 3688: DIR *mapping_dir;
-: 3689: struct dirent *mapping_de;
5: 3690: List *files = NIL;
-: 3691: ListCell *file;
5: 3692: Oid dboid = IsSharedRelation(relid) ? InvalidOid : MyDatabaseId;
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 3693:
5: 3694: mapping_dir = AllocateDir("pg_logical/mappings");
call 0 returned 100%
465: 3695: while ((mapping_de = ReadDir(mapping_dir, "pg_logical/mappings")) != NULL)
call 0 returned 100%
branch 1 taken 99%
branch 2 taken 1% (fallthrough)
-: 3696: {
-: 3697: Oid f_dboid;
-: 3698: Oid f_relid;
-: 3699: TransactionId f_mapped_xid;
-: 3700: TransactionId f_create_xid;
-: 3701: XLogRecPtr f_lsn;
-: 3702: uint32 f_hi,
-: 3703: f_lo;
-: 3704: RewriteMappingFile *f;
-: 3705:
905: 3706: if (strcmp(mapping_de->d_name, ".") == 0 ||
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 1% (fallthrough)
branch 3 taken 99%
450: 3707: strcmp(mapping_de->d_name, "..") == 0)
443: 3708: continue;
-: 3709:
-: 3710: /* Ignore files that aren't ours */
445: 3711: if (strncmp(mapping_de->d_name, "map-", 4) != 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3712: continue;
-: 3713:
445: 3714: if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT,
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 3715: &f_dboid, &f_relid, &f_hi, &f_lo,
-: 3716: &f_mapped_xid, &f_create_xid) != 6)
#####: 3717: elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name);
call 0 never executed
call 1 never executed
call 2 never executed
-: 3718:
445: 3719: f_lsn = ((uint64) f_hi) << 32 | f_lo;
-: 3720:
-: 3721: /* mapping for another database */
445: 3722: if (f_dboid != dboid)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3723: continue;
-: 3724:
-: 3725: /* mapping for another relation */
445: 3726: if (f_relid != relid)
branch 0 taken 10% (fallthrough)
branch 1 taken 90%
45: 3727: continue;
-: 3728:
-: 3729: /* did the creating transaction abort? */
400: 3730: if (!TransactionIdDidCommit(f_create_xid))
call 0 returned 100%
branch 1 taken 28% (fallthrough)
branch 2 taken 73%
110: 3731: continue;
-: 3732:
-: 3733: /* not for our transaction */
290: 3734: if (!TransactionIdInArray(f_mapped_xid, snapshot->subxip, snapshot->subxcnt))
call 0 returned 100%
branch 1 taken 92% (fallthrough)
branch 2 taken 8%
268: 3735: continue;
-: 3736:
-: 3737: /* ok, relevant, queue for apply */
22: 3738: f = palloc(sizeof(RewriteMappingFile));
call 0 returned 100%
22: 3739: f->lsn = f_lsn;
22: 3740: strcpy(f->fname, mapping_de->d_name);
22: 3741: files = lappend(files, f);
call 0 returned 100%
-: 3742: }
5: 3743: FreeDir(mapping_dir);
call 0 returned 100%
-: 3744:
-: 3745: /* sort files so we apply them in LSN order */
5: 3746: list_sort(files, file_sort_by_lsn);
call 0 returned 100%
-: 3747:
27: 3748: foreach(file, files)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 81% (fallthrough)
branch 3 taken 19%
branch 4 taken 81%
branch 5 taken 19% (fallthrough)
-: 3749: {
22: 3750: RewriteMappingFile *f = (RewriteMappingFile *) lfirst(file);
-: 3751:
22: 3752: elog(DEBUG1, "applying mapping: \"%s\" in %u", f->fname,
call 0 returned 100%
call 1 returned 100%
-: 3753: snapshot->subxip[0]);
22: 3754: ApplyLogicalMappingFile(tuplecid_data, relid, f->fname);
call 0 returned 100%
22: 3755: pfree(f);
call 0 returned 100%
-: 3756: }
5: 3757:}
-: 3758:
-: 3759:/*
-: 3760: * Lookup cmin/cmax of a tuple, during logical decoding where we can't rely on
-: 3761: * combocids.
-: 3762: */
-: 3763:bool
function ResolveCminCmaxDuringDecoding called 404 returned 100% blocks executed 75%
404: 3764:ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
-: 3765: Snapshot snapshot,
-: 3766: HeapTuple htup, Buffer buffer,
-: 3767: CommandId *cmin, CommandId *cmax)
-: 3768:{
-: 3769: ReorderBufferTupleCidKey key;
-: 3770: ReorderBufferTupleCidEnt *ent;
-: 3771: ForkNumber forkno;
-: 3772: BlockNumber blockno;
404: 3773: bool updated_mapping = false;
-: 3774:
-: 3775: /* be careful about padding */
404: 3776: memset(&key, 0, sizeof(key));
-: 3777:
404: 3778: Assert(!BufferIsLocal(buffer));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3779:
-: 3780: /*
-: 3781: * get relfilenode from the buffer, no convenient way to access it other
-: 3782: * than that.
-: 3783: */
404: 3784: BufferGetTag(buffer, &key.relnode, &forkno, &blockno);
call 0 returned 100%
-: 3785:
-: 3786: /* tuples can only be in the main fork */
404: 3787: Assert(forkno == MAIN_FORKNUM);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
404: 3788: Assert(blockno == ItemPointerGetBlockNumber(&htup->t_self));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 0% (fallthrough)
branch 5 taken 100%
call 6 never executed
branch 7 taken 0% (fallthrough)
branch 8 taken 100%
call 9 never executed
branch 10 taken 0% (fallthrough)
branch 11 taken 100%
call 12 never executed
-: 3789:
404: 3790: ItemPointerCopy(&htup->t_self,
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3791: &key.tid);
-: 3792:
-: 3793:restart:
409: 3794: ent = (ReorderBufferTupleCidEnt *)
call 0 returned 100%
-: 3795: hash_search(tuplecid_data,
-: 3796: (void *) &key,
-: 3797: HASH_FIND,
-: 3798: NULL);
-: 3799:
-: 3800: /*
-: 3801: * failed to find a mapping, check whether the table was rewritten and
-: 3802: * apply mapping if so, but only do that once - there can be no new
-: 3803: * mappings while we are in here since we have to hold a lock on the
-: 3804: * relation.
-: 3805: */
409: 3806: if (ent == NULL && !updated_mapping)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
-: 3807: {
5: 3808: UpdateLogicalMappings(tuplecid_data, htup->t_tableOid, snapshot);
call 0 returned 100%
-: 3809: /* now check but don't update for a mapping again */
5: 3810: updated_mapping = true;
5: 3811: goto restart;
-: 3812: }
404: 3813: else if (ent == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3814: return false;
-: 3815:
404: 3816: if (cmin)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
404: 3817: *cmin = ent->cmin;
404: 3818: if (cmax)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
404: 3819: *cmax = ent->cmax;
404: 3820: return true;
-: 3821:}
0001-Add-logical_decoding_work_mem-to-limit-ReorderBuffer-patch_coverage/logical.c.gcov 0000664 0001750 0000000 00000174017 13560210457 030453 0 ustar vignesh root -: 0:Source:logical.c
-: 0:Graph:./logical.gcno
-: 0:Data:./logical.gcda
-: 0:Runs:6531
-: 0:Programs:1
-: 1:/*-------------------------------------------------------------------------
-: 2: * logical.c
-: 3: * PostgreSQL logical decoding coordination
-: 4: *
-: 5: * Copyright (c) 2012-2019, PostgreSQL Global Development Group
-: 6: *
-: 7: * IDENTIFICATION
-: 8: * src/backend/replication/logical/logical.c
-: 9: *
-: 10: * NOTES
-: 11: * This file coordinates interaction between the various modules that
-: 12: * together provide logical decoding, primarily by providing so
-: 13: * called LogicalDecodingContexts. The goal is to encapsulate most of the
-: 14: * internal complexity for consumers of logical decoding, so they can
-: 15: * create and consume a changestream with a low amount of code. Builtin
-: 16: * consumers are the walsender and SQL SRF interface, but it's possible to
-: 17: * add further ones without changing core code, e.g. to consume changes in
-: 18: * a bgworker.
-: 19: *
-: 20: * The idea is that a consumer provides three callbacks, one to read WAL,
-: 21: * one to prepare a data write, and a final one for actually writing since
-: 22: * their implementation depends on the type of consumer. Check
-: 23: * logicalfuncs.c for an example implementation of a fairly simple consumer
-: 24: * and an implementation of a WAL reading callback that's suitable for
-: 25: * simple consumers.
-: 26: *-------------------------------------------------------------------------
-: 27: */
-: 28:
-: 29:#include "postgres.h"
-: 30:
-: 31:#include "fmgr.h"
-: 32:#include "miscadmin.h"
-: 33:
-: 34:#include "access/xact.h"
-: 35:#include "access/xlog_internal.h"
-: 36:
-: 37:#include "replication/decode.h"
-: 38:#include "replication/logical.h"
-: 39:#include "replication/reorderbuffer.h"
-: 40:#include "replication/origin.h"
-: 41:#include "replication/snapbuild.h"
-: 42:
-: 43:#include "storage/proc.h"
-: 44:#include "storage/procarray.h"
-: 45:
-: 46:#include "utils/memutils.h"
-: 47:
-: 48:/* data for errcontext callback */
-: 49:typedef struct LogicalErrorCallbackState
-: 50:{
-: 51: LogicalDecodingContext *ctx;
-: 52: const char *callback_name;
-: 53: XLogRecPtr report_location;
-: 54:} LogicalErrorCallbackState;
-: 55:
-: 56:/* wrappers around output plugin callbacks */
-: 57:static void output_plugin_error_callback(void *arg);
-: 58:static void startup_cb_wrapper(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
-: 59: bool is_init);
-: 60:static void shutdown_cb_wrapper(LogicalDecodingContext *ctx);
-: 61:static void begin_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn);
-: 62:static void commit_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 63: XLogRecPtr commit_lsn);
-: 64:static void change_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 65: Relation relation, ReorderBufferChange *change);
-: 66:static void truncate_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 67: int nrelations, Relation relations[], ReorderBufferChange *change);
-: 68:static void message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 69: XLogRecPtr message_lsn, bool transactional,
-: 70: const char *prefix, Size message_size, const char *message);
-: 71:
-: 72:static void LoadOutputPlugin(OutputPluginCallbacks *callbacks, char *plugin);
-: 73:
-: 74:/*
-: 75: * Make sure the current settings & environment are capable of doing logical
-: 76: * decoding.
-: 77: */
-: 78:void
function CheckLogicalDecodingRequirements called 285 returned 100% blocks executed 22%
285: 79:CheckLogicalDecodingRequirements(void)
-: 80:{
285: 81: CheckSlotRequirements();
call 0 returned 100%
-: 82:
-: 83: /*
-: 84: * NB: Adding a new requirement likely means that RestoreSlotFromDisk()
-: 85: * needs the same check.
-: 86: */
-: 87:
285: 88: if (wal_level < WAL_LEVEL_LOGICAL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 89: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 90: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 91: errmsg("logical decoding requires wal_level >= logical")));
-: 92:
285: 93: if (MyDatabaseId == InvalidOid)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 94: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 95: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 96: errmsg("logical decoding requires a database connection")));
-: 97:
-: 98: /* ----
-: 99: * TODO: We got to change that someday soon...
-: 100: *
-: 101: * There's basically three things missing to allow this:
-: 102: * 1) We need to be able to correctly and quickly identify the timeline a
-: 103: * LSN belongs to
-: 104: * 2) We need to force hot_standby_feedback to be enabled at all times so
-: 105: * the primary cannot remove rows we need.
-: 106: * 3) support dropping replication slots referring to a database, in
-: 107: * dbase_redo. There can't be any active ones due to HS recovery
-: 108: * conflicts, so that should be relatively easy.
-: 109: * ----
-: 110: */
285: 111: if (RecoveryInProgress())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 112: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 113: (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-: 114: errmsg("logical decoding cannot be used while in recovery")));
285: 115:}
-: 116:
-: 117:/*
-: 118: * Helper function for CreateInitDecodingContext() and
-: 119: * CreateDecodingContext() performing common tasks.
-: 120: */
-: 121:static LogicalDecodingContext *
function StartupDecodingContext called 277 returned 99% blocks executed 71%
277: 122:StartupDecodingContext(List *output_plugin_options,
-: 123: XLogRecPtr start_lsn,
-: 124: TransactionId xmin_horizon,
-: 125: bool need_full_snapshot,
-: 126: bool fast_forward,
-: 127: XLogPageReadCB read_page,
-: 128: LogicalOutputPluginWriterPrepareWrite prepare_write,
-: 129: LogicalOutputPluginWriterWrite do_write,
-: 130: LogicalOutputPluginWriterUpdateProgress update_progress)
-: 131:{
-: 132: ReplicationSlot *slot;
-: 133: MemoryContext context,
-: 134: old_context;
-: 135: LogicalDecodingContext *ctx;
-: 136:
-: 137: /* shorter lines... */
277: 138: slot = MyReplicationSlot;
-: 139:
277: 140: context = AllocSetContextCreate(CurrentMemoryContext,
call 0 returned 100%
-: 141: "Logical decoding context",
-: 142: ALLOCSET_DEFAULT_SIZES);
277: 143: old_context = MemoryContextSwitchTo(context);
call 0 returned 100%
277: 144: ctx = palloc0(sizeof(LogicalDecodingContext));
call 0 returned 100%
-: 145:
277: 146: ctx->context = context;
-: 147:
-: 148: /*
-: 149: * (re-)load output plugins, so we detect a bad (removed) output plugin
-: 150: * now.
-: 151: */
277: 152: if (!fast_forward)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
275: 153: LoadOutputPlugin(&ctx->callbacks, NameStr(slot->data.plugin));
call 0 returned 99%
-: 154:
-: 155: /*
-: 156: * Now that the slot's xmin has been set, we can announce ourselves as a
-: 157: * logical decoding backend which doesn't need to be checked individually
-: 158: * when computing the xmin horizon because the xmin is enforced via
-: 159: * replication slots.
-: 160: *
-: 161: * We can only do so if we're outside of a transaction (i.e. the case when
-: 162: * streaming changes via walsender), otherwise an already setup
-: 163: * snapshot/xid would end up being ignored. That's not a particularly
-: 164: * bothersome restriction since the SQL interface can't be used for
-: 165: * streaming anyway.
-: 166: */
276: 167: if (!IsTransactionOrTransactionBlock())
call 0 returned 100%
branch 1 taken 16% (fallthrough)
branch 2 taken 84%
-: 168: {
44: 169: LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
call 0 returned 100%
44: 170: MyPgXact->vacuumFlags |= PROC_IN_LOGICAL_DECODING;
44: 171: LWLockRelease(ProcArrayLock);
call 0 returned 100%
-: 172: }
-: 173:
276: 174: ctx->slot = slot;
-: 175:
276: 176: ctx->reader = XLogReaderAllocate(wal_segment_size, NULL, read_page, ctx);
call 0 returned 100%
276: 177: if (!ctx->reader)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 178: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 179: (errcode(ERRCODE_OUT_OF_MEMORY),
-: 180: errmsg("out of memory")));
-: 181:
276: 182: ctx->reorder = ReorderBufferAllocate();
call 0 returned 100%
276: 183: ctx->snapshot_builder =
276: 184: AllocateSnapshotBuilder(ctx->reorder, xmin_horizon, start_lsn,
call 0 returned 100%
-: 185: need_full_snapshot);
-: 186:
276: 187: ctx->reorder->private_data = ctx;
-: 188:
-: 189: /* wrap output plugin callbacks, so we can add error context information */
276: 190: ctx->reorder->begin = begin_cb_wrapper;
276: 191: ctx->reorder->apply_change = change_cb_wrapper;
276: 192: ctx->reorder->apply_truncate = truncate_cb_wrapper;
276: 193: ctx->reorder->commit = commit_cb_wrapper;
276: 194: ctx->reorder->message = message_cb_wrapper;
-: 195:
276: 196: ctx->out = makeStringInfo();
call 0 returned 100%
276: 197: ctx->prepare_write = prepare_write;
276: 198: ctx->write = do_write;
276: 199: ctx->update_progress = update_progress;
-: 200:
276: 201: ctx->output_plugin_options = output_plugin_options;
-: 202:
276: 203: ctx->fast_forward = fast_forward;
-: 204:
276: 205: MemoryContextSwitchTo(old_context);
call 0 returned 100%
-: 206:
276: 207: return ctx;
-: 208:}
-: 209:
-: 210:/*
-: 211: * Create a new decoding context, for a new logical slot.
-: 212: *
-: 213: * plugin -- contains the name of the output plugin
-: 214: * output_plugin_options -- contains options passed to the output plugin
-: 215: * restart_lsn -- if given as invalid, it's this routine's responsibility to
-: 216: * mark WAL as reserved by setting a convenient restart_lsn for the slot.
-: 217: * Otherwise, we set for decoding to start from the given LSN without
-: 218: * marking WAL reserved beforehand. In that scenario, it's up to the
-: 219: * caller to guarantee that WAL remains available.
-: 220: * read_page, prepare_write, do_write, update_progress --
-: 221: * callbacks that perform the use-case dependent, actual, work.
-: 222: *
-: 223: * Needs to be called while in a memory context that's at least as long lived
-: 224: * as the decoding context because further memory contexts will be created
-: 225: * inside it.
-: 226: *
-: 227: * Returns an initialized decoding context after calling the output plugin's
-: 228: * startup function.
-: 229: */
-: 230:LogicalDecodingContext *
function CreateInitDecodingContext called 129 returned 98% blocks executed 64%
129: 231:CreateInitDecodingContext(char *plugin,
-: 232: List *output_plugin_options,
-: 233: bool need_full_snapshot,
-: 234: XLogRecPtr restart_lsn,
-: 235: XLogPageReadCB read_page,
-: 236: LogicalOutputPluginWriterPrepareWrite prepare_write,
-: 237: LogicalOutputPluginWriterWrite do_write,
-: 238: LogicalOutputPluginWriterUpdateProgress update_progress)
-: 239:{
129: 240: TransactionId xmin_horizon = InvalidTransactionId;
-: 241: ReplicationSlot *slot;
-: 242: LogicalDecodingContext *ctx;
-: 243: MemoryContext old_context;
-: 244:
-: 245: /* shorter lines... */
129: 246: slot = MyReplicationSlot;
-: 247:
-: 248: /* first some sanity checks that are unlikely to be violated */
129: 249: if (slot == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 250: elog(ERROR, "cannot perform logical decoding without an acquired slot");
call 0 never executed
call 1 never executed
call 2 never executed
-: 251:
129: 252: if (plugin == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 253: elog(ERROR, "cannot initialize logical decoding without a specified plugin");
call 0 never executed
call 1 never executed
call 2 never executed
-: 254:
-: 255: /* Make sure the passed slot is suitable. These are user facing errors. */
129: 256: if (SlotIsPhysical(slot))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 257: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 258: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 259: errmsg("cannot use physical replication slot for logical decoding")));
-: 260:
129: 261: if (slot->data.database != MyDatabaseId)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 262: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 263: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 264: errmsg("replication slot \"%s\" was not created in this database",
-: 265: NameStr(slot->data.name))));
-: 266:
240: 267: if (IsTransactionState() &&
call 0 returned 100%
branch 1 taken 86% (fallthrough)
branch 2 taken 14%
branch 3 taken 2% (fallthrough)
branch 4 taken 98%
111: 268: GetTopTransactionIdIfAny() != InvalidTransactionId)
call 0 returned 100%
2: 269: ereport(ERROR,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 0%
call 6 never executed
-: 270: (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
-: 271: errmsg("cannot create logical replication slot in transaction that has performed writes")));
-: 272:
-: 273: /* register output plugin name with slot */
127: 274: SpinLockAcquire(&slot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
127: 275: StrNCpy(NameStr(slot->data.plugin), plugin, NAMEDATALEN);
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
127: 276: SpinLockRelease(&slot->mutex);
call 0 returned 100%
-: 277:
127: 278: if (XLogRecPtrIsInvalid(restart_lsn))
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
121: 279: ReplicationSlotReserveWal();
call 0 returned 100%
-: 280: else
-: 281: {
6: 282: SpinLockAcquire(&slot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
6: 283: slot->data.restart_lsn = restart_lsn;
6: 284: SpinLockRelease(&slot->mutex);
call 0 returned 100%
-: 285: }
-: 286:
-: 287: /* ----
-: 288: * This is a bit tricky: We need to determine a safe xmin horizon to start
-: 289: * decoding from, to avoid starting from a running xacts record referring
-: 290: * to xids whose rows have been vacuumed or pruned
-: 291: * already. GetOldestSafeDecodingTransactionId() returns such a value, but
-: 292: * without further interlock its return value might immediately be out of
-: 293: * date.
-: 294: *
-: 295: * So we have to acquire the ProcArrayLock to prevent computation of new
-: 296: * xmin horizons by other backends, get the safe decoding xid, and inform
-: 297: * the slot machinery about the new limit. Once that's done the
-: 298: * ProcArrayLock can be released as the slot machinery now is
-: 299: * protecting against vacuum.
-: 300: *
-: 301: * Note that, temporarily, the data, not just the catalog, xmin has to be
-: 302: * reserved if a data snapshot is to be exported. Otherwise the initial
-: 303: * data snapshot created here is not guaranteed to be valid. After that
-: 304: * the data xmin doesn't need to be managed anymore and the global xmin
-: 305: * should be recomputed. As we are fine with losing the pegged data xmin
-: 306: * after crash - no chance a snapshot would get exported anymore - we can
-: 307: * get away with just setting the slot's
-: 308: * effective_xmin. ReplicationSlotRelease will reset it again.
-: 309: *
-: 310: * ----
-: 311: */
127: 312: LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
call 0 returned 100%
-: 313:
127: 314: xmin_horizon = GetOldestSafeDecodingTransactionId(!need_full_snapshot);
call 0 returned 100%
-: 315:
127: 316: SpinLockAcquire(&slot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
127: 317: slot->effective_catalog_xmin = xmin_horizon;
127: 318: slot->data.catalog_xmin = xmin_horizon;
127: 319: if (need_full_snapshot)
branch 0 taken 29% (fallthrough)
branch 1 taken 71%
37: 320: slot->effective_xmin = xmin_horizon;
127: 321: SpinLockRelease(&slot->mutex);
call 0 returned 100%
-: 322:
127: 323: ReplicationSlotsComputeRequiredXmin(true);
call 0 returned 100%
-: 324:
127: 325: LWLockRelease(ProcArrayLock);
call 0 returned 100%
-: 326:
127: 327: ReplicationSlotMarkDirty();
call 0 returned 100%
127: 328: ReplicationSlotSave();
call 0 returned 100%
-: 329:
127: 330: ctx = StartupDecodingContext(NIL, restart_lsn, xmin_horizon,
call 0 returned 99%
-: 331: need_full_snapshot, false,
-: 332: read_page, prepare_write, do_write,
-: 333: update_progress);
-: 334:
-: 335: /* call output plugin initialization callback */
126: 336: old_context = MemoryContextSwitchTo(ctx->context);
call 0 returned 100%
126: 337: if (ctx->callbacks.startup_cb != NULL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
126: 338: startup_cb_wrapper(ctx, &ctx->options, true);
call 0 returned 100%
126: 339: MemoryContextSwitchTo(old_context);
call 0 returned 100%
-: 340:
126: 341: ctx->reorder->output_rewrites = ctx->options.receive_rewrites;
-: 342:
126: 343: return ctx;
-: 344:}
-: 345:
-: 346:/*
-: 347: * Create a new decoding context, for a logical slot that has previously been
-: 348: * used already.
-: 349: *
-: 350: * start_lsn
-: 351: * The LSN at which to start decoding. If InvalidXLogRecPtr, restart
-: 352: * from the slot's confirmed_flush; otherwise, start from the specified
-: 353: * location (but move it forwards to confirmed_flush if it's older than
-: 354: * that, see below).
-: 355: *
-: 356: * output_plugin_options
-: 357: * options passed to the output plugin.
-: 358: *
-: 359: * fast_forward
-: 360: * bypass the generation of logical changes.
-: 361: *
-: 362: * read_page, prepare_write, do_write, update_progress
-: 363: * callbacks that have to be filled to perform the use-case dependent,
-: 364: * actual work.
-: 365: *
-: 366: * Needs to be called while in a memory context that's at least as long lived
-: 367: * as the decoding context because further memory contexts will be created
-: 368: * inside it.
-: 369: *
-: 370: * Returns an initialized decoding context after calling the output plugin's
-: 371: * startup function.
-: 372: */
-: 373:LogicalDecodingContext *
function CreateDecodingContext called 152 returned 97% blocks executed 74%
152: 374:CreateDecodingContext(XLogRecPtr start_lsn,
-: 375: List *output_plugin_options,
-: 376: bool fast_forward,
-: 377: XLogPageReadCB read_page,
-: 378: LogicalOutputPluginWriterPrepareWrite prepare_write,
-: 379: LogicalOutputPluginWriterWrite do_write,
-: 380: LogicalOutputPluginWriterUpdateProgress update_progress)
-: 381:{
-: 382: LogicalDecodingContext *ctx;
-: 383: ReplicationSlot *slot;
-: 384: MemoryContext old_context;
-: 385:
-: 386: /* shorter lines... */
152: 387: slot = MyReplicationSlot;
-: 388:
-: 389: /* first some sanity checks that are unlikely to be violated */
152: 390: if (slot == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 391: elog(ERROR, "cannot perform logical decoding without an acquired slot");
call 0 never executed
call 1 never executed
call 2 never executed
-: 392:
-: 393: /* make sure the passed slot is suitable, these are user facing errors */
152: 394: if (SlotIsPhysical(slot))
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
1: 395: ereport(ERROR,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 0%
call 6 never executed
-: 396: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 397: (errmsg("cannot use physical replication slot for logical decoding"))));
-: 398:
151: 399: if (slot->data.database != MyDatabaseId)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
1: 400: ereport(ERROR,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 0%
call 6 never executed
-: 401: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 402: (errmsg("replication slot \"%s\" was not created in this database",
-: 403: NameStr(slot->data.name)))));
-: 404:
150: 405: if (start_lsn == InvalidXLogRecPtr)
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
-: 406: {
-: 407: /* continue from last position */
143: 408: start_lsn = slot->data.confirmed_flush;
-: 409: }
7: 410: else if (start_lsn < slot->data.confirmed_flush)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 411: {
-: 412: /*
-: 413: * It might seem like we should error out in this case, but it's
-: 414: * pretty common for a client to acknowledge a LSN it doesn't have to
-: 415: * do anything for, and thus didn't store persistently, because the
-: 416: * xlog records didn't result in anything relevant for logical
-: 417: * decoding. Clients have to be able to do that to support synchronous
-: 418: * replication.
-: 419: */
#####: 420: elog(DEBUG1, "cannot stream from %X/%X, minimum is %X/%X, forwarding",
call 0 never executed
call 1 never executed
-: 421: (uint32) (start_lsn >> 32), (uint32) start_lsn,
-: 422: (uint32) (slot->data.confirmed_flush >> 32),
-: 423: (uint32) slot->data.confirmed_flush);
-: 424:
#####: 425: start_lsn = slot->data.confirmed_flush;
-: 426: }
-: 427:
150: 428: ctx = StartupDecodingContext(output_plugin_options,
call 0 returned 100%
-: 429: start_lsn, InvalidTransactionId, false,
-: 430: fast_forward, read_page, prepare_write,
-: 431: do_write, update_progress);
-: 432:
-: 433: /* call output plugin initialization callback */
150: 434: old_context = MemoryContextSwitchTo(ctx->context);
call 0 returned 100%
150: 435: if (ctx->callbacks.startup_cb != NULL)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
148: 436: startup_cb_wrapper(ctx, &ctx->options, false);
call 0 returned 98%
147: 437: MemoryContextSwitchTo(old_context);
call 0 returned 100%
-: 438:
147: 439: ctx->reorder->output_rewrites = ctx->options.receive_rewrites;
-: 440:
147: 441: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 100%
-: 442: (errmsg("starting logical decoding for slot \"%s\"",
-: 443: NameStr(slot->data.name)),
-: 444: errdetail("Streaming transactions committing after %X/%X, reading WAL from %X/%X.",
-: 445: (uint32) (slot->data.confirmed_flush >> 32),
-: 446: (uint32) slot->data.confirmed_flush,
-: 447: (uint32) (slot->data.restart_lsn >> 32),
-: 448: (uint32) slot->data.restart_lsn)));
-: 449:
147: 450: return ctx;
-: 451:}
-: 452:
-: 453:/*
-: 454: * Returns true if a consistent initial decoding snapshot has been built.
-: 455: */
-: 456:bool
function DecodingContextReady called 141 returned 100% blocks executed 100%
141: 457:DecodingContextReady(LogicalDecodingContext *ctx)
-: 458:{
141: 459: return SnapBuildCurrentState(ctx->snapshot_builder) == SNAPBUILD_CONSISTENT;
call 0 returned 100%
-: 460:}
-: 461:
-: 462:/*
-: 463: * Read from the decoding slot, until it is ready to start extracting changes.
-: 464: */
-: 465:void
function DecodingContextFindStartpoint called 126 returned 100% blocks executed 65%
126: 466:DecodingContextFindStartpoint(LogicalDecodingContext *ctx)
-: 467:{
-: 468: XLogRecPtr startptr;
126: 469: ReplicationSlot *slot = ctx->slot;
-: 470:
-: 471: /* Initialize from where to start reading WAL. */
126: 472: startptr = slot->data.restart_lsn;
-: 473:
126: 474: elog(DEBUG1, "searching for logical decoding starting point, starting at %X/%X",
call 0 returned 100%
call 1 returned 100%
-: 475: (uint32) (slot->data.restart_lsn >> 32),
-: 476: (uint32) slot->data.restart_lsn);
-: 477:
-: 478: /* Wait for a consistent starting point */
-: 479: for (;;)
-: 480: {
-: 481: XLogRecord *record;
141: 482: char *err = NULL;
-: 483:
-: 484: /* the read_page callback waits for new WAL */
141: 485: record = XLogReadRecord(ctx->reader, startptr, &err);
call 0 returned 100%
141: 486: if (err)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 487: elog(ERROR, "%s", err);
call 0 never executed
call 1 never executed
call 2 never executed
141: 488: if (!record)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 489: elog(ERROR, "no record found"); /* shouldn't happen */
call 0 never executed
call 1 never executed
call 2 never executed
-: 490:
141: 491: startptr = InvalidXLogRecPtr;
-: 492:
141: 493: LogicalDecodingProcessRecord(ctx, ctx->reader);
call 0 returned 100%
-: 494:
-: 495: /* only continue till we found a consistent spot */
141: 496: if (DecodingContextReady(ctx))
call 0 returned 100%
branch 1 taken 89% (fallthrough)
branch 2 taken 11%
126: 497: break;
-: 498:
15: 499: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
15: 500: }
-: 501:
126: 502: SpinLockAcquire(&slot->mutex);
call 0 returned 100%
branch 1 taken 0%
branch 2 taken 100%
call 3 never executed
126: 503: slot->data.confirmed_flush = ctx->reader->EndRecPtr;
126: 504: SpinLockRelease(&slot->mutex);
call 0 returned 100%
126: 505:}
-: 506:
-: 507:/*
-: 508: * Free a previously allocated decoding context, invoking the shutdown
-: 509: * callback if necessary.
-: 510: */
-: 511:void
function FreeDecodingContext called 249 returned 100% blocks executed 100%
249: 512:FreeDecodingContext(LogicalDecodingContext *ctx)
-: 513:{
249: 514: if (ctx->callbacks.shutdown_cb != NULL)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
247: 515: shutdown_cb_wrapper(ctx);
call 0 returned 100%
-: 516:
249: 517: ReorderBufferFree(ctx->reorder);
call 0 returned 100%
249: 518: FreeSnapshotBuilder(ctx->snapshot_builder);
call 0 returned 100%
249: 519: XLogReaderFree(ctx->reader);
call 0 returned 100%
249: 520: MemoryContextDelete(ctx->context);
call 0 returned 100%
249: 521:}
-: 522:
-: 523:/*
-: 524: * Prepare a write using the context's output routine.
-: 525: */
-: 526:void
function OutputPluginPrepareWrite called 146573 returned 100% blocks executed 50%
146573: 527:OutputPluginPrepareWrite(struct LogicalDecodingContext *ctx, bool last_write)
-: 528:{
146573: 529: if (!ctx->accept_writes)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 530: elog(ERROR, "writes are only accepted in commit, begin and change callbacks");
call 0 never executed
call 1 never executed
call 2 never executed
-: 531:
146573: 532: ctx->prepare_write(ctx, ctx->write_location, ctx->write_xid, last_write);
call 0 returned 100%
146573: 533: ctx->prepared_write = true;
146573: 534:}
-: 535:
-: 536:/*
-: 537: * Perform a write using the context's output routine.
-: 538: */
-: 539:void
function OutputPluginWrite called 146573 returned 100% blocks executed 50%
146573: 540:OutputPluginWrite(struct LogicalDecodingContext *ctx, bool last_write)
-: 541:{
146573: 542: if (!ctx->prepared_write)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 543: elog(ERROR, "OutputPluginPrepareWrite needs to be called before OutputPluginWrite");
call 0 never executed
call 1 never executed
call 2 never executed
-: 544:
146573: 545: ctx->write(ctx, ctx->write_location, ctx->write_xid, last_write);
call 0 returned 100%
146573: 546: ctx->prepared_write = false;
146573: 547:}
-: 548:
-: 549:/*
-: 550: * Update progress tracking (if supported).
-: 551: */
-: 552:void
function OutputPluginUpdateProgress called 117 returned 100% blocks executed 75%
117: 553:OutputPluginUpdateProgress(struct LogicalDecodingContext *ctx)
-: 554:{
117: 555: if (!ctx->update_progress)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
117: 556: return;
-: 557:
117: 558: ctx->update_progress(ctx, ctx->write_location, ctx->write_xid);
call 0 returned 100%
-: 559:}
-: 560:
-: 561:/*
-: 562: * Load the output plugin, lookup its output plugin init function, and check
-: 563: * that it provides the required callbacks.
-: 564: */
-: 565:static void
function LoadOutputPlugin called 275 returned 99% blocks executed 37%
275: 566:LoadOutputPlugin(OutputPluginCallbacks *callbacks, char *plugin)
-: 567:{
-: 568: LogicalOutputPluginInit plugin_init;
-: 569:
275: 570: plugin_init = (LogicalOutputPluginInit)
call 0 returned 99%
-: 571: load_external_function(plugin, "_PG_output_plugin_init", false, NULL);
-: 572:
274: 573: if (plugin_init == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 574: elog(ERROR, "output plugins have to declare the _PG_output_plugin_init symbol");
call 0 never executed
call 1 never executed
call 2 never executed
-: 575:
-: 576: /* ask the output plugin to fill the callback struct */
274: 577: plugin_init(callbacks);
call 0 returned 100%
-: 578:
274: 579: if (callbacks->begin_cb == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 580: elog(ERROR, "output plugins have to register a begin callback");
call 0 never executed
call 1 never executed
call 2 never executed
274: 581: if (callbacks->change_cb == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 582: elog(ERROR, "output plugins have to register a change callback");
call 0 never executed
call 1 never executed
call 2 never executed
274: 583: if (callbacks->commit_cb == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 584: elog(ERROR, "output plugins have to register a commit callback");
call 0 never executed
call 1 never executed
call 2 never executed
274: 585:}
-: 586:
-: 587:static void
function output_plugin_error_callback called 18 returned 100% blocks executed 100%
18: 588:output_plugin_error_callback(void *arg)
-: 589:{
18: 590: LogicalErrorCallbackState *state = (LogicalErrorCallbackState *) arg;
-: 591:
-: 592: /* not all callbacks have an associated LSN */
18: 593: if (state->report_location != InvalidXLogRecPtr)
branch 0 taken 83% (fallthrough)
branch 1 taken 17%
60: 594: errcontext("slot \"%s\", output plugin \"%s\", in the %s callback, associated LSN %X/%X",
call 0 returned 100%
call 1 returned 100%
15: 595: NameStr(state->ctx->slot->data.name),
15: 596: NameStr(state->ctx->slot->data.plugin),
-: 597: state->callback_name,
15: 598: (uint32) (state->report_location >> 32),
15: 599: (uint32) state->report_location);
-: 600: else
6: 601: errcontext("slot \"%s\", output plugin \"%s\", in the %s callback",
call 0 returned 100%
call 1 returned 100%
3: 602: NameStr(state->ctx->slot->data.name),
3: 603: NameStr(state->ctx->slot->data.plugin),
-: 604: state->callback_name);
18: 605:}
-: 606:
-: 607:static void
function startup_cb_wrapper called 274 returned 99% blocks executed 75%
274: 608:startup_cb_wrapper(LogicalDecodingContext *ctx, OutputPluginOptions *opt, bool is_init)
-: 609:{
-: 610: LogicalErrorCallbackState state;
-: 611: ErrorContextCallback errcallback;
-: 612:
274: 613: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 614:
-: 615: /* Push callback + info on the error context stack */
274: 616: state.ctx = ctx;
274: 617: state.callback_name = "startup";
274: 618: state.report_location = InvalidXLogRecPtr;
274: 619: errcallback.callback = output_plugin_error_callback;
274: 620: errcallback.arg = (void *) &state;
274: 621: errcallback.previous = error_context_stack;
274: 622: error_context_stack = &errcallback;
-: 623:
-: 624: /* set output state */
274: 625: ctx->accept_writes = false;
-: 626:
-: 627: /* do the actual work: call callback */
274: 628: ctx->callbacks.startup_cb(ctx, opt, is_init);
call 0 returned 99%
-: 629:
-: 630: /* Pop the error context stack */
271: 631: error_context_stack = errcallback.previous;
271: 632:}
-: 633:
-: 634:static void
function shutdown_cb_wrapper called 247 returned 100% blocks executed 75%
247: 635:shutdown_cb_wrapper(LogicalDecodingContext *ctx)
-: 636:{
-: 637: LogicalErrorCallbackState state;
-: 638: ErrorContextCallback errcallback;
-: 639:
247: 640: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 641:
-: 642: /* Push callback + info on the error context stack */
247: 643: state.ctx = ctx;
247: 644: state.callback_name = "shutdown";
247: 645: state.report_location = InvalidXLogRecPtr;
247: 646: errcallback.callback = output_plugin_error_callback;
247: 647: errcallback.arg = (void *) &state;
247: 648: errcallback.previous = error_context_stack;
247: 649: error_context_stack = &errcallback;
-: 650:
-: 651: /* set output state */
247: 652: ctx->accept_writes = false;
-: 653:
-: 654: /* do the actual work: call callback */
247: 655: ctx->callbacks.shutdown_cb(ctx);
call 0 returned 100%
-: 656:
-: 657: /* Pop the error context stack */
247: 658: error_context_stack = errcallback.previous;
247: 659:}
-: 660:
-: 661:
-: 662:/*
-: 663: * Callbacks for ReorderBuffer which add in some more information and then call
-: 664: * output_plugin.h plugins.
-: 665: */
-: 666:static void
function begin_cb_wrapper called 445 returned 100% blocks executed 75%
445: 667:begin_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn)
-: 668:{
445: 669: LogicalDecodingContext *ctx = cache->private_data;
-: 670: LogicalErrorCallbackState state;
-: 671: ErrorContextCallback errcallback;
-: 672:
445: 673: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 674:
-: 675: /* Push callback + info on the error context stack */
445: 676: state.ctx = ctx;
445: 677: state.callback_name = "begin";
445: 678: state.report_location = txn->first_lsn;
445: 679: errcallback.callback = output_plugin_error_callback;
445: 680: errcallback.arg = (void *) &state;
445: 681: errcallback.previous = error_context_stack;
445: 682: error_context_stack = &errcallback;
-: 683:
-: 684: /* set output state */
445: 685: ctx->accept_writes = true;
445: 686: ctx->write_xid = txn->xid;
445: 687: ctx->write_location = txn->first_lsn;
-: 688:
-: 689: /* do the actual work: call callback */
445: 690: ctx->callbacks.begin_cb(ctx, txn);
call 0 returned 100%
-: 691:
-: 692: /* Pop the error context stack */
445: 693: error_context_stack = errcallback.previous;
445: 694:}
-: 695:
-: 696:static void
function commit_cb_wrapper called 445 returned 100% blocks executed 75%
445: 697:commit_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 698: XLogRecPtr commit_lsn)
-: 699:{
445: 700: LogicalDecodingContext *ctx = cache->private_data;
-: 701: LogicalErrorCallbackState state;
-: 702: ErrorContextCallback errcallback;
-: 703:
445: 704: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 705:
-: 706: /* Push callback + info on the error context stack */
445: 707: state.ctx = ctx;
445: 708: state.callback_name = "commit";
445: 709: state.report_location = txn->final_lsn; /* beginning of commit record */
445: 710: errcallback.callback = output_plugin_error_callback;
445: 711: errcallback.arg = (void *) &state;
445: 712: errcallback.previous = error_context_stack;
445: 713: error_context_stack = &errcallback;
-: 714:
-: 715: /* set output state */
445: 716: ctx->accept_writes = true;
445: 717: ctx->write_xid = txn->xid;
445: 718: ctx->write_location = txn->end_lsn; /* points to the end of the record */
-: 719:
-: 720: /* do the actual work: call callback */
445: 721: ctx->callbacks.commit_cb(ctx, txn, commit_lsn);
call 0 returned 100%
-: 722:
-: 723: /* Pop the error context stack */
445: 724: error_context_stack = errcallback.previous;
445: 725:}
-: 726:
-: 727:static void
function change_cb_wrapper called 146981 returned 100% blocks executed 75%
146981: 728:change_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 729: Relation relation, ReorderBufferChange *change)
-: 730:{
146981: 731: LogicalDecodingContext *ctx = cache->private_data;
-: 732: LogicalErrorCallbackState state;
-: 733: ErrorContextCallback errcallback;
-: 734:
146981: 735: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 736:
-: 737: /* Push callback + info on the error context stack */
146981: 738: state.ctx = ctx;
146981: 739: state.callback_name = "change";
146981: 740: state.report_location = change->lsn;
146981: 741: errcallback.callback = output_plugin_error_callback;
146981: 742: errcallback.arg = (void *) &state;
146981: 743: errcallback.previous = error_context_stack;
146981: 744: error_context_stack = &errcallback;
-: 745:
-: 746: /* set output state */
146981: 747: ctx->accept_writes = true;
146981: 748: ctx->write_xid = txn->xid;
-: 749:
-: 750: /*
-: 751: * report this change's lsn so replies from clients can give an up2date
-: 752: * answer. This won't ever be enough (and shouldn't be!) to confirm
-: 753: * receipt of this transaction, but it might allow another transaction's
-: 754: * commit to be confirmed with one message.
-: 755: */
146981: 756: ctx->write_location = change->lsn;
-: 757:
146981: 758: ctx->callbacks.change_cb(ctx, txn, relation, change);
call 0 returned 100%
-: 759:
-: 760: /* Pop the error context stack */
146981: 761: error_context_stack = errcallback.previous;
146981: 762:}
-: 763:
-: 764:static void
function truncate_cb_wrapper called 8 returned 100% blocks executed 71%
8: 765:truncate_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 766: int nrelations, Relation relations[], ReorderBufferChange *change)
-: 767:{
8: 768: LogicalDecodingContext *ctx = cache->private_data;
-: 769: LogicalErrorCallbackState state;
-: 770: ErrorContextCallback errcallback;
-: 771:
8: 772: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 773:
8: 774: if (!ctx->callbacks.truncate_cb)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
8: 775: return;
-: 776:
-: 777: /* Push callback + info on the error context stack */
8: 778: state.ctx = ctx;
8: 779: state.callback_name = "truncate";
8: 780: state.report_location = change->lsn;
8: 781: errcallback.callback = output_plugin_error_callback;
8: 782: errcallback.arg = (void *) &state;
8: 783: errcallback.previous = error_context_stack;
8: 784: error_context_stack = &errcallback;
-: 785:
-: 786: /* set output state */
8: 787: ctx->accept_writes = true;
8: 788: ctx->write_xid = txn->xid;
-: 789:
-: 790: /*
-: 791: * report this change's lsn so replies from clients can give an up2date
-: 792: * answer. This won't ever be enough (and shouldn't be!) to confirm
-: 793: * receipt of this transaction, but it might allow another transaction's
-: 794: * commit to be confirmed with one message.
-: 795: */
8: 796: ctx->write_location = change->lsn;
-: 797:
8: 798: ctx->callbacks.truncate_cb(ctx, txn, nrelations, relations, change);
call 0 returned 100%
-: 799:
-: 800: /* Pop the error context stack */
8: 801: error_context_stack = errcallback.previous;
-: 802:}
-: 803:
-: 804:bool
function filter_by_origin_cb_wrapper called 1182743 returned 100% blocks executed 80%
1182743: 805:filter_by_origin_cb_wrapper(LogicalDecodingContext *ctx, RepOriginId origin_id)
-: 806:{
-: 807: LogicalErrorCallbackState state;
-: 808: ErrorContextCallback errcallback;
-: 809: bool ret;
-: 810:
1182743: 811: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 812:
-: 813: /* Push callback + info on the error context stack */
1182743: 814: state.ctx = ctx;
1182743: 815: state.callback_name = "filter_by_origin";
1182743: 816: state.report_location = InvalidXLogRecPtr;
1182743: 817: errcallback.callback = output_plugin_error_callback;
1182743: 818: errcallback.arg = (void *) &state;
1182743: 819: errcallback.previous = error_context_stack;
1182743: 820: error_context_stack = &errcallback;
-: 821:
-: 822: /* set output state */
1182743: 823: ctx->accept_writes = false;
-: 824:
-: 825: /* do the actual work: call callback */
1182743: 826: ret = ctx->callbacks.filter_by_origin_cb(ctx, origin_id);
call 0 returned 100%
-: 827:
-: 828: /* Pop the error context stack */
1182743: 829: error_context_stack = errcallback.previous;
-: 830:
1182743: 831: return ret;
-: 832:}
-: 833:
-: 834:static void
function message_cb_wrapper called 8 returned 100% blocks executed 80%
8: 835:message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 836: XLogRecPtr message_lsn, bool transactional,
-: 837: const char *prefix, Size message_size, const char *message)
-: 838:{
8: 839: LogicalDecodingContext *ctx = cache->private_data;
-: 840: LogicalErrorCallbackState state;
-: 841: ErrorContextCallback errcallback;
-: 842:
8: 843: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 844:
8: 845: if (ctx->callbacks.message_cb == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
8: 846: return;
-: 847:
-: 848: /* Push callback + info on the error context stack */
8: 849: state.ctx = ctx;
8: 850: state.callback_name = "message";
8: 851: state.report_location = message_lsn;
8: 852: errcallback.callback = output_plugin_error_callback;
8: 853: errcallback.arg = (void *) &state;
8: 854: errcallback.previous = error_context_stack;
8: 855: error_context_stack = &errcallback;
-: 856:
-: 857: /* set output state */
8: 858: ctx->accept_writes = true;
8: 859: ctx->write_xid = txn != NULL ? txn->xid : InvalidTransactionId;
branch 0 taken 88% (fallthrough)
branch 1 taken 13%
8: 860: ctx->write_location = message_lsn;
-: 861:
-: 862: /* do the actual work: call callback */
8: 863: ctx->callbacks.message_cb(ctx, txn, message_lsn, transactional, prefix,
call 0 returned 100%
-: 864: message_size, message);
-: 865:
-: 866: /* Pop the error context stack */
8: 867: error_context_stack = errcallback.previous;
-: 868:}
-: 869:
-: 870:/*
-: 871: * Set the required catalog xmin horizon for historic snapshots in the current
-: 872: * replication slot.
-: 873: *
-: 874: * Note that in the most cases, we won't be able to immediately use the xmin
-: 875: * to increase the xmin horizon: we need to wait till the client has confirmed
-: 876: * receiving current_lsn with LogicalConfirmReceivedLocation().
-: 877: */
-: 878:void
function LogicalIncreaseXminForSlot called 80 returned 100% blocks executed 88%
80: 879:LogicalIncreaseXminForSlot(XLogRecPtr current_lsn, TransactionId xmin)
-: 880:{
80: 881: bool updated_xmin = false;
-: 882: ReplicationSlot *slot;
-: 883:
80: 884: slot = MyReplicationSlot;
-: 885:
80: 886: Assert(slot != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 887:
80: 888: SpinLockAcquire(&slot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 889:
-: 890: /*
-: 891: * don't overwrite if we already have a newer xmin. This can happen if we
-: 892: * restart decoding in a slot.
-: 893: */
80: 894: if (TransactionIdPrecedesOrEquals(xmin, slot->data.catalog_xmin))
call 0 returned 100%
branch 1 taken 33% (fallthrough)
branch 2 taken 68%
-: 895: {
-: 896: }
-: 897:
-: 898: /*
-: 899: * If the client has already confirmed up to this lsn, we directly can
-: 900: * mark this as accepted. This can happen if we restart decoding in a
-: 901: * slot.
-: 902: */
26: 903: else if (current_lsn <= slot->data.confirmed_flush)
branch 0 taken 19% (fallthrough)
branch 1 taken 81%
-: 904: {
5: 905: slot->candidate_catalog_xmin = xmin;
5: 906: slot->candidate_xmin_lsn = current_lsn;
-: 907:
-: 908: /* our candidate can directly be used */
5: 909: updated_xmin = true;
-: 910: }
-: 911:
-: 912: /*
-: 913: * Only increase if the previous values have been applied, otherwise we
-: 914: * might never end up updating if the receiver acks too slowly.
-: 915: */
21: 916: else if (slot->candidate_xmin_lsn == InvalidXLogRecPtr)
branch 0 taken 24% (fallthrough)
branch 1 taken 76%
-: 917: {
5: 918: slot->candidate_catalog_xmin = xmin;
5: 919: slot->candidate_xmin_lsn = current_lsn;
-: 920: }
80: 921: SpinLockRelease(&slot->mutex);
call 0 returned 100%
-: 922:
-: 923: /* candidate already valid with the current flush position, apply */
80: 924: if (updated_xmin)
branch 0 taken 6% (fallthrough)
branch 1 taken 94%
5: 925: LogicalConfirmReceivedLocation(slot->data.confirmed_flush);
call 0 returned 100%
80: 926:}
-: 927:
-: 928:/*
-: 929: * Mark the minimal LSN (restart_lsn) we need to read to replay all
-: 930: * transactions that have not yet committed at current_lsn.
-: 931: *
-: 932: * Just like LogicalIncreaseXminForSlot this only takes effect when the
-: 933: * client has confirmed to have received current_lsn.
-: 934: */
-: 935:void
function LogicalIncreaseRestartDecodingForSlot called 64 returned 100% blocks executed 83%
64: 936:LogicalIncreaseRestartDecodingForSlot(XLogRecPtr current_lsn, XLogRecPtr restart_lsn)
-: 937:{
64: 938: bool updated_lsn = false;
-: 939: ReplicationSlot *slot;
-: 940:
64: 941: slot = MyReplicationSlot;
-: 942:
64: 943: Assert(slot != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
64: 944: Assert(restart_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
64: 945: Assert(current_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 946:
64: 947: SpinLockAcquire(&slot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 948:
-: 949: /* don't overwrite if have a newer restart lsn */
64: 950: if (restart_lsn <= slot->data.restart_lsn)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 951: {
-: 952: }
-: 953:
-: 954: /*
-: 955: * We might have already flushed far enough to directly accept this lsn,
-: 956: * in this case there is no need to check for existing candidate LSNs
-: 957: */
64: 958: else if (current_lsn <= slot->data.confirmed_flush)
branch 0 taken 61% (fallthrough)
branch 1 taken 39%
-: 959: {
39: 960: slot->candidate_restart_valid = current_lsn;
39: 961: slot->candidate_restart_lsn = restart_lsn;
-: 962:
-: 963: /* our candidate can directly be used */
39: 964: updated_lsn = true;
-: 965: }
-: 966:
-: 967: /*
-: 968: * Only increase if the previous values have been applied, otherwise we
-: 969: * might never end up updating if the receiver acks too slowly. A missed
-: 970: * value here will just cause some extra effort after reconnecting.
-: 971: */
64: 972: if (slot->candidate_restart_valid == InvalidXLogRecPtr)
branch 0 taken 14% (fallthrough)
branch 1 taken 86%
-: 973: {
9: 974: slot->candidate_restart_valid = current_lsn;
9: 975: slot->candidate_restart_lsn = restart_lsn;
-: 976:
9: 977: elog(DEBUG1, "got new restart lsn %X/%X at %X/%X",
call 0 returned 100%
call 1 returned 100%
-: 978: (uint32) (restart_lsn >> 32), (uint32) restart_lsn,
-: 979: (uint32) (current_lsn >> 32), (uint32) current_lsn);
-: 980: }
-: 981: else
-: 982: {
55: 983: elog(DEBUG1, "failed to increase restart lsn: proposed %X/%X, after %X/%X, current candidate %X/%X, current after %X/%X, flushed up to %X/%X",
call 0 returned 100%
call 1 returned 100%
-: 984: (uint32) (restart_lsn >> 32), (uint32) restart_lsn,
-: 985: (uint32) (current_lsn >> 32), (uint32) current_lsn,
-: 986: (uint32) (slot->candidate_restart_lsn >> 32),
-: 987: (uint32) slot->candidate_restart_lsn,
-: 988: (uint32) (slot->candidate_restart_valid >> 32),
-: 989: (uint32) slot->candidate_restart_valid,
-: 990: (uint32) (slot->data.confirmed_flush >> 32),
-: 991: (uint32) slot->data.confirmed_flush
-: 992: );
-: 993: }
64: 994: SpinLockRelease(&slot->mutex);
call 0 returned 100%
-: 995:
-: 996: /* candidates are already valid with the current flush position, apply */
64: 997: if (updated_lsn)
branch 0 taken 61% (fallthrough)
branch 1 taken 39%
39: 998: LogicalConfirmReceivedLocation(slot->data.confirmed_flush);
call 0 returned 100%
64: 999:}
-: 1000:
-: 1001:/*
-: 1002: * Handle a consumer's confirmation having received all changes up to lsn.
-: 1003: */
-: 1004:void
function LogicalConfirmReceivedLocation called 1090 returned 100% blocks executed 88%
1090: 1005:LogicalConfirmReceivedLocation(XLogRecPtr lsn)
-: 1006:{
1090: 1007: Assert(lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1008:
-: 1009: /* Do an unlocked check for candidate_lsn first. */
2172: 1010: if (MyReplicationSlot->candidate_xmin_lsn != InvalidXLogRecPtr ||
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 4% (fallthrough)
branch 3 taken 96%
1082: 1011: MyReplicationSlot->candidate_restart_valid != InvalidXLogRecPtr)
53: 1012: {
53: 1013: bool updated_xmin = false;
53: 1014: bool updated_restart = false;
-: 1015:
53: 1016: SpinLockAcquire(&MyReplicationSlot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 1017:
53: 1018: MyReplicationSlot->data.confirmed_flush = lsn;
-: 1019:
-: 1020: /* if we're past the location required for bumping xmin, do so */
61: 1021: if (MyReplicationSlot->candidate_xmin_lsn != InvalidXLogRecPtr &&
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
8: 1022: MyReplicationSlot->candidate_xmin_lsn <= lsn)
-: 1023: {
-: 1024: /*
-: 1025: * We have to write the changed xmin to disk *before* we change
-: 1026: * the in-memory value, otherwise after a crash we wouldn't know
-: 1027: * that some catalog tuples might have been removed already.
-: 1028: *
-: 1029: * Ensure that by first writing to ->xmin and only update
-: 1030: * ->effective_xmin once the new state is synced to disk. After a
-: 1031: * crash ->effective_xmin is set to ->xmin.
-: 1032: */
16: 1033: if (TransactionIdIsValid(MyReplicationSlot->candidate_catalog_xmin) &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
8: 1034: MyReplicationSlot->data.catalog_xmin != MyReplicationSlot->candidate_catalog_xmin)
-: 1035: {
8: 1036: MyReplicationSlot->data.catalog_xmin = MyReplicationSlot->candidate_catalog_xmin;
8: 1037: MyReplicationSlot->candidate_catalog_xmin = InvalidTransactionId;
8: 1038: MyReplicationSlot->candidate_xmin_lsn = InvalidXLogRecPtr;
8: 1039: updated_xmin = true;
-: 1040: }
-: 1041: }
-: 1042:
101: 1043: if (MyReplicationSlot->candidate_restart_valid != InvalidXLogRecPtr &&
branch 0 taken 91% (fallthrough)
branch 1 taken 9%
branch 2 taken 98% (fallthrough)
branch 3 taken 2%
48: 1044: MyReplicationSlot->candidate_restart_valid <= lsn)
-: 1045: {
47: 1046: Assert(MyReplicationSlot->candidate_restart_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1047:
47: 1048: MyReplicationSlot->data.restart_lsn = MyReplicationSlot->candidate_restart_lsn;
47: 1049: MyReplicationSlot->candidate_restart_lsn = InvalidXLogRecPtr;
47: 1050: MyReplicationSlot->candidate_restart_valid = InvalidXLogRecPtr;
47: 1051: updated_restart = true;
-: 1052: }
-: 1053:
53: 1054: SpinLockRelease(&MyReplicationSlot->mutex);
call 0 returned 100%
-: 1055:
-: 1056: /* first write new xmin to disk, so we know what's up after a crash */
53: 1057: if (updated_xmin || updated_restart)
branch 0 taken 85% (fallthrough)
branch 1 taken 15%
branch 2 taken 98% (fallthrough)
branch 3 taken 2%
-: 1058: {
52: 1059: ReplicationSlotMarkDirty();
call 0 returned 100%
52: 1060: ReplicationSlotSave();
call 0 returned 100%
52: 1061: elog(DEBUG1, "updated xmin: %u restart: %u", updated_xmin, updated_restart);
call 0 returned 100%
call 1 returned 100%
-: 1062: }
-: 1063:
-: 1064: /*
-: 1065: * Now the new xmin is safely on disk, we can let the global value
-: 1066: * advance. We do not take ProcArrayLock or similar since we only
-: 1067: * advance xmin here and there's not much harm done by a concurrent
-: 1068: * computation missing that.
-: 1069: */
53: 1070: if (updated_xmin)
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
-: 1071: {
8: 1072: SpinLockAcquire(&MyReplicationSlot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
8: 1073: MyReplicationSlot->effective_catalog_xmin = MyReplicationSlot->data.catalog_xmin;
8: 1074: SpinLockRelease(&MyReplicationSlot->mutex);
call 0 returned 100%
-: 1075:
8: 1076: ReplicationSlotsComputeRequiredXmin(false);
call 0 returned 100%
8: 1077: ReplicationSlotsComputeRequiredLSN();
call 0 returned 100%
-: 1078: }
-: 1079: }
-: 1080: else
-: 1081: {
1037: 1082: SpinLockAcquire(&MyReplicationSlot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
1037: 1083: MyReplicationSlot->data.confirmed_flush = lsn;
1037: 1084: SpinLockRelease(&MyReplicationSlot->mutex);
call 0 returned 100%
-: 1085: }
1090: 1086:}
base_code_coverage/ 0000755 0001750 0000000 00000000000 13560456107 014037 5 ustar vignesh root base_code_coverage/reorderbuffer.c.gcov 0000664 0001750 0000000 00000570604 13560201434 020001 0 ustar vignesh root -: 0:Source:reorderbuffer.c
-: 0:Graph:./reorderbuffer.gcno
-: 0:Data:./reorderbuffer.gcda
-: 0:Runs:6535
-: 0:Programs:1
-: 1:/*-------------------------------------------------------------------------
-: 2: *
-: 3: * reorderbuffer.c
-: 4: * PostgreSQL logical replay/reorder buffer management
-: 5: *
-: 6: *
-: 7: * Copyright (c) 2012-2019, PostgreSQL Global Development Group
-: 8: *
-: 9: *
-: 10: * IDENTIFICATION
-: 11: * src/backend/replication/reorderbuffer.c
-: 12: *
-: 13: * NOTES
-: 14: * This module gets handed individual pieces of transactions in the order
-: 15: * they are written to the WAL and is responsible to reassemble them into
-: 16: * toplevel transaction sized pieces. When a transaction is completely
-: 17: * reassembled - signalled by reading the transaction commit record - it
-: 18: * will then call the output plugin (cf. ReorderBufferCommit()) with the
-: 19: * individual changes. The output plugins rely on snapshots built by
-: 20: * snapbuild.c which hands them to us.
-: 21: *
-: 22: * Transactions and subtransactions/savepoints in postgres are not
-: 23: * immediately linked to each other from outside the performing
-: 24: * backend. Only at commit/abort (or special xact_assignment records) they
-: 25: * are linked together. Which means that we will have to splice together a
-: 26: * toplevel transaction from its subtransactions. To do that efficiently we
-: 27: * build a binary heap indexed by the smallest current lsn of the individual
-: 28: * subtransactions' changestreams. As the individual streams are inherently
-: 29: * ordered by LSN - since that is where we build them from - the transaction
-: 30: * can easily be reassembled by always using the subtransaction with the
-: 31: * smallest current LSN from the heap.
-: 32: *
-: 33: * In order to cope with large transactions - which can be several times as
-: 34: * big as the available memory - this module supports spooling the contents
-: 35: * of a large transactions to disk. When the transaction is replayed the
-: 36: * contents of individual (sub-)transactions will be read from disk in
-: 37: * chunks.
-: 38: *
-: 39: * This module also has to deal with reassembling toast records from the
-: 40: * individual chunks stored in WAL. When a new (or initial) version of a
-: 41: * tuple is stored in WAL it will always be preceded by the toast chunks
-: 42: * emitted for the columns stored out of line. Within a single toplevel
-: 43: * transaction there will be no other data carrying records between a row's
-: 44: * toast chunks and the row data itself. See ReorderBufferToast* for
-: 45: * details.
-: 46: *
-: 47: * ReorderBuffer uses two special memory context types - SlabContext for
-: 48: * allocations of fixed-length structures (changes and transactions), and
-: 49: * GenerationContext for the variable-length transaction data (allocated
-: 50: * and freed in groups with similar lifespan).
-: 51: *
-: 52: * -------------------------------------------------------------------------
-: 53: */
-: 54:#include "postgres.h"
-: 55:
-: 56:#include <unistd.h>
-: 57:#include <sys/stat.h>
-: 58:
-: 59:#include "access/detoast.h"
-: 60:#include "access/heapam.h"
-: 61:#include "access/rewriteheap.h"
-: 62:#include "access/transam.h"
-: 63:#include "access/xact.h"
-: 64:#include "access/xlog_internal.h"
-: 65:#include "catalog/catalog.h"
-: 66:#include "lib/binaryheap.h"
-: 67:#include "miscadmin.h"
-: 68:#include "pgstat.h"
-: 69:#include "replication/logical.h"
-: 70:#include "replication/reorderbuffer.h"
-: 71:#include "replication/slot.h"
-: 72:#include "replication/snapbuild.h" /* just for SnapBuildSnapDecRefcount */
-: 73:#include "storage/bufmgr.h"
-: 74:#include "storage/fd.h"
-: 75:#include "storage/sinval.h"
-: 76:#include "utils/builtins.h"
-: 77:#include "utils/combocid.h"
-: 78:#include "utils/memdebug.h"
-: 79:#include "utils/memutils.h"
-: 80:#include "utils/rel.h"
-: 81:#include "utils/relfilenodemap.h"
-: 82:
-: 83:
-: 84:/* entry for a hash table we use to map from xid to our transaction state */
-: 85:typedef struct ReorderBufferTXNByIdEnt
-: 86:{
-: 87: TransactionId xid;
-: 88: ReorderBufferTXN *txn;
-: 89:} ReorderBufferTXNByIdEnt;
-: 90:
-: 91:/* data structures for (relfilenode, ctid) => (cmin, cmax) mapping */
-: 92:typedef struct ReorderBufferTupleCidKey
-: 93:{
-: 94: RelFileNode relnode;
-: 95: ItemPointerData tid;
-: 96:} ReorderBufferTupleCidKey;
-: 97:
-: 98:typedef struct ReorderBufferTupleCidEnt
-: 99:{
-: 100: ReorderBufferTupleCidKey key;
-: 101: CommandId cmin;
-: 102: CommandId cmax;
-: 103: CommandId combocid; /* just for debugging */
-: 104:} ReorderBufferTupleCidEnt;
-: 105:
-: 106:/* k-way in-order change iteration support structures */
-: 107:typedef struct ReorderBufferIterTXNEntry
-: 108:{
-: 109: XLogRecPtr lsn;
-: 110: ReorderBufferChange *change;
-: 111: ReorderBufferTXN *txn;
-: 112: int fd;
-: 113: XLogSegNo segno;
-: 114:} ReorderBufferIterTXNEntry;
-: 115:
-: 116:typedef struct ReorderBufferIterTXNState
-: 117:{
-: 118: binaryheap *heap;
-: 119: Size nr_txns;
-: 120: dlist_head old_change;
-: 121: ReorderBufferIterTXNEntry entries[FLEXIBLE_ARRAY_MEMBER];
-: 122:} ReorderBufferIterTXNState;
-: 123:
-: 124:/* toast datastructures */
-: 125:typedef struct ReorderBufferToastEnt
-: 126:{
-: 127: Oid chunk_id; /* toast_table.chunk_id */
-: 128: int32 last_chunk_seq; /* toast_table.chunk_seq of the last chunk we
-: 129: * have seen */
-: 130: Size num_chunks; /* number of chunks we've already seen */
-: 131: Size size; /* combined size of chunks seen */
-: 132: dlist_head chunks; /* linked list of chunks */
-: 133: struct varlena *reconstructed; /* reconstructed varlena now pointed to in
-: 134: * main tup */
-: 135:} ReorderBufferToastEnt;
-: 136:
-: 137:/* Disk serialization support datastructures */
-: 138:typedef struct ReorderBufferDiskChange
-: 139:{
-: 140: Size size;
-: 141: ReorderBufferChange change;
-: 142: /* data follows */
-: 143:} ReorderBufferDiskChange;
-: 144:
-: 145:/*
-: 146: * Maximum number of changes kept in memory, per transaction. After that,
-: 147: * changes are spooled to disk.
-: 148: *
-: 149: * The current value should be sufficient to decode the entire transaction
-: 150: * without hitting disk in OLTP workloads, while starting to spool to disk in
-: 151: * other workloads reasonably fast.
-: 152: *
-: 153: * At some point in the future it probably makes sense to have a more elaborate
-: 154: * resource management here, but it's not entirely clear what that would look
-: 155: * like.
-: 156: */
-: 157:static const Size max_changes_in_memory = 4096;
-: 158:
-: 159:/* ---------------------------------------
-: 160: * primary reorderbuffer support routines
-: 161: * ---------------------------------------
-: 162: */
-: 163:static ReorderBufferTXN *ReorderBufferGetTXN(ReorderBuffer *rb);
-: 164:static void ReorderBufferReturnTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 165:static ReorderBufferTXN *ReorderBufferTXNByXid(ReorderBuffer *rb,
-: 166: TransactionId xid, bool create, bool *is_new,
-: 167: XLogRecPtr lsn, bool create_as_top);
-: 168:static void ReorderBufferTransferSnapToParent(ReorderBufferTXN *txn,
-: 169: ReorderBufferTXN *subtxn);
-: 170:
-: 171:static void AssertTXNLsnOrder(ReorderBuffer *rb);
-: 172:
-: 173:/* ---------------------------------------
-: 174: * support functions for lsn-order iterating over the ->changes of a
-: 175: * transaction and its subtransactions
-: 176: *
-: 177: * used for iteration over the k-way heap merge of a transaction and its
-: 178: * subtransactions
-: 179: * ---------------------------------------
-: 180: */
-: 181:static ReorderBufferIterTXNState *ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 182:static ReorderBufferChange *ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state);
-: 183:static void ReorderBufferIterTXNFinish(ReorderBuffer *rb,
-: 184: ReorderBufferIterTXNState *state);
-: 185:static void ReorderBufferExecuteInvalidations(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 186:
-: 187:/*
-: 188: * ---------------------------------------
-: 189: * Disk serialization support functions
-: 190: * ---------------------------------------
-: 191: */
-: 192:static void ReorderBufferCheckSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 193:static void ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 194:static void ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 195: int fd, ReorderBufferChange *change);
-: 196:static Size ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 197: int *fd, XLogSegNo *segno);
-: 198:static void ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 199: char *change);
-: 200:static void ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 201:static void ReorderBufferCleanupSerializedTXNs(const char *slotname);
-: 202:static void ReorderBufferSerializedPath(char *path, ReplicationSlot *slot,
-: 203: TransactionId xid, XLogSegNo segno);
-: 204:
-: 205:static void ReorderBufferFreeSnap(ReorderBuffer *rb, Snapshot snap);
-: 206:static Snapshot ReorderBufferCopySnap(ReorderBuffer *rb, Snapshot orig_snap,
-: 207: ReorderBufferTXN *txn, CommandId cid);
-: 208:
-: 209:/* ---------------------------------------
-: 210: * toast reassembly support
-: 211: * ---------------------------------------
-: 212: */
-: 213:static void ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 214:static void ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn);
-: 215:static void ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 216: Relation relation, ReorderBufferChange *change);
-: 217:static void ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 218: Relation relation, ReorderBufferChange *change);
-: 219:
-: 220:
-: 221:/*
-: 222: * Allocate a new ReorderBuffer and clean out any old serialized state from
-: 223: * prior ReorderBuffer instances for the same slot.
-: 224: */
-: 225:ReorderBuffer *
function ReorderBufferAllocate called 277 returned 100% blocks executed 92%
277: 226:ReorderBufferAllocate(void)
-: 227:{
-: 228: ReorderBuffer *buffer;
-: 229: HASHCTL hash_ctl;
-: 230: MemoryContext new_ctx;
-: 231:
277: 232: Assert(MyReplicationSlot != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 233:
-: 234: /* allocate memory in own context, to have better accountability */
277: 235: new_ctx = AllocSetContextCreate(CurrentMemoryContext,
call 0 returned 100%
-: 236: "ReorderBuffer",
-: 237: ALLOCSET_DEFAULT_SIZES);
-: 238:
277: 239: buffer =
call 0 returned 100%
-: 240: (ReorderBuffer *) MemoryContextAlloc(new_ctx, sizeof(ReorderBuffer));
-: 241:
277: 242: memset(&hash_ctl, 0, sizeof(hash_ctl));
-: 243:
277: 244: buffer->context = new_ctx;
-: 245:
277: 246: buffer->change_context = SlabContextCreate(new_ctx,
call 0 returned 100%
-: 247: "Change",
-: 248: SLAB_DEFAULT_BLOCK_SIZE,
-: 249: sizeof(ReorderBufferChange));
-: 250:
277: 251: buffer->txn_context = SlabContextCreate(new_ctx,
call 0 returned 100%
-: 252: "TXN",
-: 253: SLAB_DEFAULT_BLOCK_SIZE,
-: 254: sizeof(ReorderBufferTXN));
-: 255:
277: 256: buffer->tup_context = GenerationContextCreate(new_ctx,
call 0 returned 100%
-: 257: "Tuples",
-: 258: SLAB_LARGE_BLOCK_SIZE);
-: 259:
277: 260: hash_ctl.keysize = sizeof(TransactionId);
277: 261: hash_ctl.entrysize = sizeof(ReorderBufferTXNByIdEnt);
277: 262: hash_ctl.hcxt = buffer->context;
-: 263:
277: 264: buffer->by_txn = hash_create("ReorderBufferByXid", 1000, &hash_ctl,
call 0 returned 100%
-: 265: HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
-: 266:
277: 267: buffer->by_txn_last_xid = InvalidTransactionId;
277: 268: buffer->by_txn_last_txn = NULL;
-: 269:
277: 270: buffer->outbuf = NULL;
277: 271: buffer->outbufsize = 0;
-: 272:
277: 273: buffer->current_restart_decoding_lsn = InvalidXLogRecPtr;
-: 274:
277: 275: dlist_init(&buffer->toplevel_by_lsn);
call 0 returned 100%
277: 276: dlist_init(&buffer->txns_by_base_snapshot_lsn);
call 0 returned 100%
-: 277:
-: 278: /*
-: 279: * Ensure there's no stale data from prior uses of this slot, in case some
-: 280: * prior exit avoided calling ReorderBufferFree. Failure to do this can
-: 281: * produce duplicated txns, and it's very cheap if there's nothing there.
-: 282: */
277: 283: ReorderBufferCleanupSerializedTXNs(NameStr(MyReplicationSlot->data.name));
call 0 returned 100%
-: 284:
277: 285: return buffer;
-: 286:}
-: 287:
-: 288:/*
-: 289: * Free a ReorderBuffer
-: 290: */
-: 291:void
function ReorderBufferFree called 250 returned 100% blocks executed 100%
250: 292:ReorderBufferFree(ReorderBuffer *rb)
-: 293:{
250: 294: MemoryContext context = rb->context;
-: 295:
-: 296: /*
-: 297: * We free separately allocated data by entirely scrapping reorderbuffer's
-: 298: * memory context.
-: 299: */
250: 300: MemoryContextDelete(context);
call 0 returned 100%
-: 301:
-: 302: /* Free disk space used by unconsumed reorder buffers */
250: 303: ReorderBufferCleanupSerializedTXNs(NameStr(MyReplicationSlot->data.name));
call 0 returned 100%
250: 304:}
-: 305:
-: 306:/*
-: 307: * Get an unused, possibly preallocated, ReorderBufferTXN.
-: 308: */
-: 309:static ReorderBufferTXN *
function ReorderBufferGetTXN called 1995 returned 100% blocks executed 100%
1995: 310:ReorderBufferGetTXN(ReorderBuffer *rb)
-: 311:{
-: 312: ReorderBufferTXN *txn;
-: 313:
1995: 314: txn = (ReorderBufferTXN *)
call 0 returned 100%
1995: 315: MemoryContextAlloc(rb->txn_context, sizeof(ReorderBufferTXN));
-: 316:
1995: 317: memset(txn, 0, sizeof(ReorderBufferTXN));
-: 318:
1995: 319: dlist_init(&txn->changes);
call 0 returned 100%
1995: 320: dlist_init(&txn->tuplecids);
call 0 returned 100%
1995: 321: dlist_init(&txn->subtxns);
call 0 returned 100%
-: 322:
1995: 323: return txn;
-: 324:}
-: 325:
-: 326:/*
-: 327: * Free a ReorderBufferTXN.
-: 328: */
-: 329:static void
function ReorderBufferReturnTXN called 1989 returned 100% blocks executed 100%
1989: 330:ReorderBufferReturnTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 331:{
-: 332: /* clean the lookup cache if we were cached (quite likely) */
1989: 333: if (rb->by_txn_last_xid == txn->xid)
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
-: 334: {
1890: 335: rb->by_txn_last_xid = InvalidTransactionId;
1890: 336: rb->by_txn_last_txn = NULL;
-: 337: }
-: 338:
-: 339: /* free data that's contained */
-: 340:
1989: 341: if (txn->tuplecid_hash != NULL)
branch 0 taken 8% (fallthrough)
branch 1 taken 92%
-: 342: {
167: 343: hash_destroy(txn->tuplecid_hash);
call 0 returned 100%
167: 344: txn->tuplecid_hash = NULL;
-: 345: }
-: 346:
1989: 347: if (txn->invalidations)
branch 0 taken 23% (fallthrough)
branch 1 taken 77%
-: 348: {
451: 349: pfree(txn->invalidations);
call 0 returned 100%
451: 350: txn->invalidations = NULL;
-: 351: }
-: 352:
1989: 353: pfree(txn);
call 0 returned 100%
1989: 354:}
-: 355:
-: 356:/*
-: 357: * Get an fresh ReorderBufferChange.
-: 358: */
-: 359:ReorderBufferChange *
function ReorderBufferGetChange called 1357684 returned 100% blocks executed 100%
1357684: 360:ReorderBufferGetChange(ReorderBuffer *rb)
-: 361:{
-: 362: ReorderBufferChange *change;
-: 363:
1357684: 364: change = (ReorderBufferChange *)
call 0 returned 100%
1357684: 365: MemoryContextAlloc(rb->change_context, sizeof(ReorderBufferChange));
-: 366:
1357684: 367: memset(change, 0, sizeof(ReorderBufferChange));
1357684: 368: return change;
-: 369:}
-: 370:
-: 371:/*
-: 372: * Free an ReorderBufferChange.
-: 373: */
-: 374:void
function ReorderBufferReturnChange called 1357680 returned 100% blocks executed 100%
1357680: 375:ReorderBufferReturnChange(ReorderBuffer *rb, ReorderBufferChange *change)
-: 376:{
-: 377: /* free contained data */
1357680: 378: switch (change->action)
branch 0 taken 96%
branch 1 taken 1%
branch 2 taken 1%
branch 3 taken 1%
branch 4 taken 3%
branch 5 taken 0%
-: 379: {
-: 380: case REORDER_BUFFER_CHANGE_INSERT:
-: 381: case REORDER_BUFFER_CHANGE_UPDATE:
-: 382: case REORDER_BUFFER_CHANGE_DELETE:
-: 383: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
1310133: 384: if (change->data.tp.newtuple)
branch 0 taken 90% (fallthrough)
branch 1 taken 10%
-: 385: {
1175364: 386: ReorderBufferReturnTupleBuf(rb, change->data.tp.newtuple);
call 0 returned 100%
1175364: 387: change->data.tp.newtuple = NULL;
-: 388: }
-: 389:
1310133: 390: if (change->data.tp.oldtuple)
branch 0 taken 5% (fallthrough)
branch 1 taken 95%
-: 391: {
66015: 392: ReorderBufferReturnTupleBuf(rb, change->data.tp.oldtuple);
call 0 returned 100%
66015: 393: change->data.tp.oldtuple = NULL;
-: 394: }
1310133: 395: break;
-: 396: case REORDER_BUFFER_CHANGE_MESSAGE:
23: 397: if (change->data.msg.prefix != NULL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
23: 398: pfree(change->data.msg.prefix);
call 0 returned 100%
23: 399: change->data.msg.prefix = NULL;
23: 400: if (change->data.msg.message != NULL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
23: 401: pfree(change->data.msg.message);
call 0 returned 100%
23: 402: change->data.msg.message = NULL;
23: 403: break;
-: 404: case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
481: 405: if (change->data.snapshot)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 406: {
481: 407: ReorderBufferFreeSnap(rb, change->data.snapshot);
call 0 returned 100%
481: 408: change->data.snapshot = NULL;
-: 409: }
481: 410: break;
-: 411: /* no data in addition to the struct itself */
-: 412: case REORDER_BUFFER_CHANGE_TRUNCATE:
8: 413: if (change->data.truncate.relids != NULL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 414: {
8: 415: ReorderBufferReturnRelids(rb, change->data.truncate.relids);
call 0 returned 100%
8: 416: change->data.truncate.relids = NULL;
-: 417: }
8: 418: break;
-: 419: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
-: 420: case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
-: 421: case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
47035: 422: break;
-: 423: }
-: 424:
1357680: 425: pfree(change);
call 0 returned 100%
1357680: 426:}
-: 427:
-: 428:/*
-: 429: * Get a fresh ReorderBufferTupleBuf fitting at least a tuple of size
-: 430: * tuple_len (excluding header overhead).
-: 431: */
-: 432:ReorderBufferTupleBuf *
function ReorderBufferGetTupleBuf called 1241381 returned 100% blocks executed 100%
1241381: 433:ReorderBufferGetTupleBuf(ReorderBuffer *rb, Size tuple_len)
-: 434:{
-: 435: ReorderBufferTupleBuf *tuple;
-: 436: Size alloc_len;
-: 437:
1241381: 438: alloc_len = tuple_len + SizeofHeapTupleHeader;
-: 439:
1241381: 440: tuple = (ReorderBufferTupleBuf *)
call 0 returned 100%
1241381: 441: MemoryContextAlloc(rb->tup_context,
-: 442: sizeof(ReorderBufferTupleBuf) +
-: 443: MAXIMUM_ALIGNOF + alloc_len);
1241381: 444: tuple->alloc_tuple_size = alloc_len;
1241381: 445: tuple->tuple.t_data = ReorderBufferTupleBufData(tuple);
-: 446:
1241381: 447: return tuple;
-: 448:}
-: 449:
-: 450:/*
-: 451: * Free an ReorderBufferTupleBuf.
-: 452: */
-: 453:void
function ReorderBufferReturnTupleBuf called 1241379 returned 100% blocks executed 100%
1241379: 454:ReorderBufferReturnTupleBuf(ReorderBuffer *rb, ReorderBufferTupleBuf *tuple)
-: 455:{
1241379: 456: pfree(tuple);
call 0 returned 100%
1241379: 457:}
-: 458:
-: 459:/*
-: 460: * Get an array for relids of truncated relations.
-: 461: *
-: 462: * We use the global memory context (for the whole reorder buffer), because
-: 463: * none of the existing ones seems like a good match (some are SLAB, so we
-: 464: * can't use those, and tup_context is meant for tuple data, not relids). We
-: 465: * could add yet another context, but it seems like an overkill - TRUNCATE is
-: 466: * not particularly common operation, so it does not seem worth it.
-: 467: */
-: 468:Oid *
function ReorderBufferGetRelids called 8 returned 100% blocks executed 100%
8: 469:ReorderBufferGetRelids(ReorderBuffer *rb, int nrelids)
-: 470:{
-: 471: Oid *relids;
-: 472: Size alloc_len;
-: 473:
8: 474: alloc_len = sizeof(Oid) * nrelids;
-: 475:
8: 476: relids = (Oid *) MemoryContextAlloc(rb->context, alloc_len);
call 0 returned 100%
-: 477:
8: 478: return relids;
-: 479:}
-: 480:
-: 481:/*
-: 482: * Free an array of relids.
-: 483: */
-: 484:void
function ReorderBufferReturnRelids called 8 returned 100% blocks executed 100%
8: 485:ReorderBufferReturnRelids(ReorderBuffer *rb, Oid *relids)
-: 486:{
8: 487: pfree(relids);
call 0 returned 100%
8: 488:}
-: 489:
-: 490:/*
-: 491: * Return the ReorderBufferTXN from the given buffer, specified by Xid.
-: 492: * If create is true, and a transaction doesn't already exist, create it
-: 493: * (with the given LSN, and as top transaction if that's specified);
-: 494: * when this happens, is_new is set to true.
-: 495: */
-: 496:static ReorderBufferTXN *
function ReorderBufferTXNByXid called 4354578 returned 100% blocks executed 88%
4354578: 497:ReorderBufferTXNByXid(ReorderBuffer *rb, TransactionId xid, bool create,
-: 498: bool *is_new, XLogRecPtr lsn, bool create_as_top)
-: 499:{
-: 500: ReorderBufferTXN *txn;
-: 501: ReorderBufferTXNByIdEnt *ent;
-: 502: bool found;
-: 503:
4354578: 504: Assert(TransactionIdIsValid(xid));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 505:
-: 506: /*
-: 507: * Check the one-entry lookup cache first
-: 508: */
8707265: 509: if (TransactionIdIsValid(rb->by_txn_last_xid) &&
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 84% (fallthrough)
branch 3 taken 16%
4352687: 510: rb->by_txn_last_xid == xid)
-: 511: {
3677316: 512: txn = rb->by_txn_last_txn;
-: 513:
3677316: 514: if (txn != NULL)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
-: 515: {
-: 516: /* found it, and it's valid */
3677313: 517: if (is_new)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
1267: 518: *is_new = false;
3677313: 519: return txn;
-: 520: }
-: 521:
-: 522: /*
-: 523: * cached as non-existent, and asked not to create? Then nothing else
-: 524: * to do.
-: 525: */
3: 526: if (!create)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
3: 527: return NULL;
-: 528: /* otherwise fall through to create it */
-: 529: }
-: 530:
-: 531: /*
-: 532: * If the cache wasn't hit or it yielded an "does-not-exist" and we want
-: 533: * to create an entry.
-: 534: */
-: 535:
-: 536: /* search the lookup table */
677262: 537: ent = (ReorderBufferTXNByIdEnt *)
call 0 returned 100%
677262: 538: hash_search(rb->by_txn,
-: 539: (void *) &xid,
-: 540: create ? HASH_ENTER : HASH_FIND,
-: 541: &found);
677262: 542: if (found)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
675038: 543: txn = ent->txn;
2224: 544: else if (create)
branch 0 taken 90% (fallthrough)
branch 1 taken 10%
-: 545: {
-: 546: /* initialize the new entry, if creation was requested */
1995: 547: Assert(ent != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1995: 548: Assert(lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 549:
1995: 550: ent->txn = ReorderBufferGetTXN(rb);
call 0 returned 100%
1995: 551: ent->txn->xid = xid;
1995: 552: txn = ent->txn;
1995: 553: txn->first_lsn = lsn;
1995: 554: txn->restart_decoding_lsn = rb->current_restart_decoding_lsn;
-: 555:
1995: 556: if (create_as_top)
branch 0 taken 69% (fallthrough)
branch 1 taken 31%
-: 557: {
1382: 558: dlist_push_tail(&rb->toplevel_by_lsn, &txn->node);
call 0 returned 100%
1382: 559: AssertTXNLsnOrder(rb);
call 0 returned 100%
-: 560: }
-: 561: }
-: 562: else
229: 563: txn = NULL; /* not found and not asked to create */
-: 564:
-: 565: /* update cache */
677262: 566: rb->by_txn_last_xid = xid;
677262: 567: rb->by_txn_last_txn = txn;
-: 568:
677262: 569: if (is_new)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
1516: 570: *is_new = !found;
-: 571:
677262: 572: Assert(!create || txn != NULL);
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
call 4 never executed
677262: 573: return txn;
-: 574:}
-: 575:
-: 576:/*
-: 577: * Queue a change into a transaction so it can be replayed upon commit.
-: 578: */
-: 579:void
function ReorderBufferQueueChange called 1197196 returned 100% blocks executed 83%
1197196: 580:ReorderBufferQueueChange(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn,
-: 581: ReorderBufferChange *change)
-: 582:{
-: 583: ReorderBufferTXN *txn;
-: 584:
1197196: 585: txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
call 0 returned 100%
-: 586:
1197196: 587: change->lsn = lsn;
1197196: 588: Assert(InvalidXLogRecPtr != lsn);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1197196: 589: dlist_push_tail(&txn->changes, &change->node);
call 0 returned 100%
1197196: 590: txn->nentries++;
1197196: 591: txn->nentries_mem++;
-: 592:
1197196: 593: ReorderBufferCheckSerializeTXN(rb, txn);
call 0 returned 100%
1197196: 594:}
-: 595:
-: 596:/*
-: 597: * Queue message into a transaction so it can be processed upon commit.
-: 598: */
-: 599:void
function ReorderBufferQueueMessage called 25 returned 100% blocks executed 82%
25: 600:ReorderBufferQueueMessage(ReorderBuffer *rb, TransactionId xid,
-: 601: Snapshot snapshot, XLogRecPtr lsn,
-: 602: bool transactional, const char *prefix,
-: 603: Size message_size, const char *message)
-: 604:{
25: 605: if (transactional)
branch 0 taken 88% (fallthrough)
branch 1 taken 12%
-: 606: {
-: 607: MemoryContext oldcontext;
-: 608: ReorderBufferChange *change;
-: 609:
22: 610: Assert(xid != InvalidTransactionId);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 611:
22: 612: oldcontext = MemoryContextSwitchTo(rb->context);
call 0 returned 100%
-: 613:
22: 614: change = ReorderBufferGetChange(rb);
call 0 returned 100%
22: 615: change->action = REORDER_BUFFER_CHANGE_MESSAGE;
22: 616: change->data.msg.prefix = pstrdup(prefix);
call 0 returned 100%
22: 617: change->data.msg.message_size = message_size;
22: 618: change->data.msg.message = palloc(message_size);
call 0 returned 100%
22: 619: memcpy(change->data.msg.message, message, message_size);
-: 620:
22: 621: ReorderBufferQueueChange(rb, xid, lsn, change);
call 0 returned 100%
-: 622:
22: 623: MemoryContextSwitchTo(oldcontext);
call 0 returned 100%
-: 624: }
-: 625: else
-: 626: {
3: 627: ReorderBufferTXN *txn = NULL;
3: 628: volatile Snapshot snapshot_now = snapshot;
-: 629:
3: 630: if (xid != InvalidTransactionId)
branch 0 taken 67% (fallthrough)
branch 1 taken 33%
2: 631: txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
call 0 returned 100%
-: 632:
-: 633: /* setup snapshot to allow catalog access */
3: 634: SetupHistoricSnapshot(snapshot_now, NULL);
call 0 returned 100%
3: 635: PG_TRY();
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
-: 636: {
3: 637: rb->message(rb, txn, lsn, false, prefix, message_size, message);
call 0 returned 100%
-: 638:
3: 639: TeardownHistoricSnapshot(false);
call 0 returned 100%
-: 640: }
#####: 641: PG_CATCH();
-: 642: {
#####: 643: TeardownHistoricSnapshot(true);
call 0 never executed
#####: 644: PG_RE_THROW();
call 0 never executed
-: 645: }
3: 646: PG_END_TRY();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 647: }
25: 648:}
-: 649:
-: 650:/*
-: 651: * AssertTXNLsnOrder
-: 652: * Verify LSN ordering of transaction lists in the reorderbuffer
-: 653: *
-: 654: * Other LSN-related invariants are checked too.
-: 655: *
-: 656: * No-op if assertions are not in use.
-: 657: */
-: 658:static void
function AssertTXNLsnOrder called 3528 returned 100% blocks executed 66%
3528: 659:AssertTXNLsnOrder(ReorderBuffer *rb)
-: 660:{
-: 661:#ifdef USE_ASSERT_CHECKING
-: 662: dlist_iter iter;
3528: 663: XLogRecPtr prev_first_lsn = InvalidXLogRecPtr;
3528: 664: XLogRecPtr prev_base_snap_lsn = InvalidXLogRecPtr;
-: 665:
7290: 666: dlist_foreach(iter, &rb->toplevel_by_lsn)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 52%
branch 3 taken 48% (fallthrough)
-: 667: {
3762: 668: ReorderBufferTXN *cur_txn = dlist_container(ReorderBufferTXN, node,
-: 669: iter.cur);
-: 670:
-: 671: /* start LSN must be set */
3762: 672: Assert(cur_txn->first_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 673:
-: 674: /* If there is an end LSN, it must be higher than start LSN */
3762: 675: if (cur_txn->end_lsn != InvalidXLogRecPtr)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 676: Assert(cur_txn->first_lsn <= cur_txn->end_lsn);
branch 0 never executed
branch 1 never executed
call 2 never executed
-: 677:
-: 678: /* Current initial LSN must be strictly higher than previous */
3762: 679: if (prev_first_lsn != InvalidXLogRecPtr)
branch 0 taken 10% (fallthrough)
branch 1 taken 90%
369: 680: Assert(prev_first_lsn < cur_txn->first_lsn);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 681:
-: 682: /* known-as-subtxn txns must not be listed */
3762: 683: Assert(!cur_txn->is_known_as_subxact);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 684:
3762: 685: prev_first_lsn = cur_txn->first_lsn;
-: 686: }
-: 687:
5255: 688: dlist_foreach(iter, &rb->txns_by_base_snapshot_lsn)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 33%
branch 3 taken 67% (fallthrough)
-: 689: {
1727: 690: ReorderBufferTXN *cur_txn = dlist_container(ReorderBufferTXN,
-: 691: base_snapshot_node,
-: 692: iter.cur);
-: 693:
-: 694: /* base snapshot (and its LSN) must be set */
1727: 695: Assert(cur_txn->base_snapshot != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1727: 696: Assert(cur_txn->base_snapshot_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 697:
-: 698: /* current LSN must be strictly higher than previous */
1727: 699: if (prev_base_snap_lsn != InvalidXLogRecPtr)
branch 0 taken 12% (fallthrough)
branch 1 taken 88%
211: 700: Assert(prev_base_snap_lsn < cur_txn->base_snapshot_lsn);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 701:
-: 702: /* known-as-subtxn txns must not be listed */
1727: 703: Assert(!cur_txn->is_known_as_subxact);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 704:
1727: 705: prev_base_snap_lsn = cur_txn->base_snapshot_lsn;
-: 706: }
-: 707:#endif
3528: 708:}
-: 709:
-: 710:/*
-: 711: * ReorderBufferGetOldestTXN
-: 712: * Return oldest transaction in reorderbuffer
-: 713: */
-: 714:ReorderBufferTXN *
function ReorderBufferGetOldestTXN called 74 returned 100% blocks executed 82%
74: 715:ReorderBufferGetOldestTXN(ReorderBuffer *rb)
-: 716:{
-: 717: ReorderBufferTXN *txn;
-: 718:
74: 719: AssertTXNLsnOrder(rb);
call 0 returned 100%
-: 720:
74: 721: if (dlist_is_empty(&rb->toplevel_by_lsn))
call 0 returned 100%
branch 1 taken 88% (fallthrough)
branch 2 taken 12%
65: 722: return NULL;
-: 723:
9: 724: txn = dlist_head_element(ReorderBufferTXN, node, &rb->toplevel_by_lsn);
call 0 returned 100%
-: 725:
9: 726: Assert(!txn->is_known_as_subxact);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
9: 727: Assert(txn->first_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
9: 728: return txn;
-: 729:}
-: 730:
-: 731:/*
-: 732: * ReorderBufferGetOldestXmin
-: 733: * Return oldest Xmin in reorderbuffer
-: 734: *
-: 735: * Returns oldest possibly running Xid from the point of view of snapshots
-: 736: * used in the transactions kept by reorderbuffer, or InvalidTransactionId if
-: 737: * there are none.
-: 738: *
-: 739: * Since snapshots are assigned monotonically, this equals the Xmin of the
-: 740: * base snapshot with minimal base_snapshot_lsn.
-: 741: */
-: 742:TransactionId
function ReorderBufferGetOldestXmin called 81 returned 100% blocks executed 100%
81: 743:ReorderBufferGetOldestXmin(ReorderBuffer *rb)
-: 744:{
-: 745: ReorderBufferTXN *txn;
-: 746:
81: 747: AssertTXNLsnOrder(rb);
call 0 returned 100%
-: 748:
81: 749: if (dlist_is_empty(&rb->txns_by_base_snapshot_lsn))
call 0 returned 100%
branch 1 taken 89% (fallthrough)
branch 2 taken 11%
72: 750: return InvalidTransactionId;
-: 751:
9: 752: txn = dlist_head_element(ReorderBufferTXN, base_snapshot_node,
call 0 returned 100%
-: 753: &rb->txns_by_base_snapshot_lsn);
9: 754: return txn->base_snapshot->xmin;
-: 755:}
-: 756:
-: 757:void
function ReorderBufferSetRestartPoint called 81 returned 100% blocks executed 100%
81: 758:ReorderBufferSetRestartPoint(ReorderBuffer *rb, XLogRecPtr ptr)
-: 759:{
81: 760: rb->current_restart_decoding_lsn = ptr;
81: 761:}
-: 762:
-: 763:/*
-: 764: * ReorderBufferAssignChild
-: 765: *
-: 766: * Make note that we know that subxid is a subtransaction of xid, seen as of
-: 767: * the given lsn.
-: 768: */
-: 769:void
function ReorderBufferAssignChild called 712 returned 100% blocks executed 78%
712: 770:ReorderBufferAssignChild(ReorderBuffer *rb, TransactionId xid,
-: 771: TransactionId subxid, XLogRecPtr lsn)
-: 772:{
-: 773: ReorderBufferTXN *txn;
-: 774: ReorderBufferTXN *subtxn;
-: 775: bool new_top;
-: 776: bool new_sub;
-: 777:
712: 778: txn = ReorderBufferTXNByXid(rb, xid, true, &new_top, lsn, true);
call 0 returned 100%
712: 779: subtxn = ReorderBufferTXNByXid(rb, subxid, true, &new_sub, lsn, false);
call 0 returned 100%
-: 780:
712: 781: if (new_top && !new_sub)
branch 0 taken 14% (fallthrough)
branch 1 taken 86%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 782: elog(ERROR, "subtransaction logged without previous top-level txn record");
call 0 never executed
call 1 never executed
call 2 never executed
-: 783:
712: 784: if (!new_sub)
branch 0 taken 14% (fallthrough)
branch 1 taken 86%
-: 785: {
99: 786: if (subtxn->is_known_as_subxact)
branch 0 taken 81% (fallthrough)
branch 1 taken 19%
-: 787: {
-: 788: /* already associated, nothing to do */
792: 789: return;
-: 790: }
-: 791: else
-: 792: {
-: 793: /*
-: 794: * We already saw this transaction, but initially added it to the
-: 795: * list of top-level txns. Now that we know it's not top-level,
-: 796: * remove it from there.
-: 797: */
19: 798: dlist_delete(&subtxn->node);
call 0 returned 100%
-: 799: }
-: 800: }
-: 801:
632: 802: subtxn->is_known_as_subxact = true;
632: 803: subtxn->toplevel_xid = xid;
632: 804: Assert(subtxn->nsubtxns == 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 805:
-: 806: /* add to subtransaction list */
632: 807: dlist_push_tail(&txn->subtxns, &subtxn->node);
call 0 returned 100%
632: 808: txn->nsubtxns++;
-: 809:
-: 810: /* Possibly transfer the subtxn's snapshot to its top-level txn. */
632: 811: ReorderBufferTransferSnapToParent(txn, subtxn);
call 0 returned 100%
-: 812:
-: 813: /* Verify LSN-ordering invariant */
632: 814: AssertTXNLsnOrder(rb);
call 0 returned 100%
-: 815:}
-: 816:
-: 817:/*
-: 818: * ReorderBufferTransferSnapToParent
-: 819: * Transfer base snapshot from subtxn to top-level txn, if needed
-: 820: *
-: 821: * This is done if the top-level txn doesn't have a base snapshot, or if the
-: 822: * subtxn's base snapshot has an earlier LSN than the top-level txn's base
-: 823: * snapshot's LSN. This can happen if there are no changes in the toplevel
-: 824: * txn but there are some in the subtxn, or the first change in subtxn has
-: 825: * earlier LSN than first change in the top-level txn and we learned about
-: 826: * their kinship only now.
-: 827: *
-: 828: * The subtransaction's snapshot is cleared regardless of the transfer
-: 829: * happening, since it's not needed anymore in either case.
-: 830: *
-: 831: * We do this as soon as we become aware of their kinship, to avoid queueing
-: 832: * extra snapshots to txns known-as-subtxns -- only top-level txns will
-: 833: * receive further snapshots.
-: 834: */
-: 835:static void
function ReorderBufferTransferSnapToParent called 632 returned 100% blocks executed 93%
632: 836:ReorderBufferTransferSnapToParent(ReorderBufferTXN *txn,
-: 837: ReorderBufferTXN *subtxn)
-: 838:{
632: 839: Assert(subtxn->toplevel_xid == txn->xid);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 840:
632: 841: if (subtxn->base_snapshot != NULL)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
-: 842: {
34: 843: if (txn->base_snapshot == NULL ||
branch 0 taken 89% (fallthrough)
branch 1 taken 11%
branch 2 taken 6% (fallthrough)
branch 3 taken 94%
16: 844: subtxn->base_snapshot_lsn < txn->base_snapshot_lsn)
-: 845: {
-: 846: /*
-: 847: * If the toplevel transaction already has a base snapshot but
-: 848: * it's newer than the subxact's, purge it.
-: 849: */
3: 850: if (txn->base_snapshot != NULL)
branch 0 taken 33% (fallthrough)
branch 1 taken 67%
-: 851: {
1: 852: SnapBuildSnapDecRefcount(txn->base_snapshot);
call 0 returned 100%
1: 853: dlist_delete(&txn->base_snapshot_node);
call 0 returned 100%
-: 854: }
-: 855:
-: 856: /*
-: 857: * The snapshot is now the top transaction's; transfer it, and
-: 858: * adjust the list position of the top transaction in the list by
-: 859: * moving it to where the subtransaction is.
-: 860: */
3: 861: txn->base_snapshot = subtxn->base_snapshot;
3: 862: txn->base_snapshot_lsn = subtxn->base_snapshot_lsn;
3: 863: dlist_insert_before(&subtxn->base_snapshot_node,
call 0 returned 100%
-: 864: &txn->base_snapshot_node);
-: 865:
-: 866: /*
-: 867: * The subtransaction doesn't have a snapshot anymore (so it
-: 868: * mustn't be in the list.)
-: 869: */
3: 870: subtxn->base_snapshot = NULL;
3: 871: subtxn->base_snapshot_lsn = InvalidXLogRecPtr;
3: 872: dlist_delete(&subtxn->base_snapshot_node);
call 0 returned 100%
-: 873: }
-: 874: else
-: 875: {
-: 876: /* Base snap of toplevel is fine, so subxact's is not needed */
15: 877: SnapBuildSnapDecRefcount(subtxn->base_snapshot);
call 0 returned 100%
15: 878: dlist_delete(&subtxn->base_snapshot_node);
call 0 returned 100%
15: 879: subtxn->base_snapshot = NULL;
15: 880: subtxn->base_snapshot_lsn = InvalidXLogRecPtr;
-: 881: }
-: 882: }
632: 883:}
-: 884:
-: 885:/*
-: 886: * Associate a subtransaction with its toplevel transaction at commit
-: 887: * time. There may be no further changes added after this.
-: 888: */
-: 889:void
function ReorderBufferCommitChild called 114 returned 100% blocks executed 100%
114: 890:ReorderBufferCommitChild(ReorderBuffer *rb, TransactionId xid,
-: 891: TransactionId subxid, XLogRecPtr commit_lsn,
-: 892: XLogRecPtr end_lsn)
-: 893:{
-: 894: ReorderBufferTXN *subtxn;
-: 895:
114: 896: subtxn = ReorderBufferTXNByXid(rb, subxid, false, NULL,
call 0 returned 100%
-: 897: InvalidXLogRecPtr, false);
-: 898:
-: 899: /*
-: 900: * No need to do anything if that subtxn didn't contain any changes
-: 901: */
114: 902: if (!subtxn)
branch 0 taken 13% (fallthrough)
branch 1 taken 87%
129: 903: return;
-: 904:
99: 905: subtxn->final_lsn = commit_lsn;
99: 906: subtxn->end_lsn = end_lsn;
-: 907:
-: 908: /*
-: 909: * Assign this subxact as a child of the toplevel xact (no-op if already
-: 910: * done.)
-: 911: */
99: 912: ReorderBufferAssignChild(rb, xid, subxid, InvalidXLogRecPtr);
call 0 returned 100%
-: 913:}
-: 914:
-: 915:
-: 916:/*
-: 917: * Support for efficiently iterating over a transaction's and its
-: 918: * subtransactions' changes.
-: 919: *
-: 920: * We do by doing a k-way merge between transactions/subtransactions. For that
-: 921: * we model the current heads of the different transactions as a binary heap
-: 922: * so we easily know which (sub-)transaction has the change with the smallest
-: 923: * lsn next.
-: 924: *
-: 925: * We assume the changes in individual transactions are already sorted by LSN.
-: 926: */
-: 927:
-: 928:/*
-: 929: * Binary heap comparison function.
-: 930: */
-: 931:static int
function ReorderBufferIterCompare called 50069 returned 100% blocks executed 100%
50069: 932:ReorderBufferIterCompare(Datum a, Datum b, void *arg)
-: 933:{
50069: 934: ReorderBufferIterTXNState *state = (ReorderBufferIterTXNState *) arg;
50069: 935: XLogRecPtr pos_a = state->entries[DatumGetInt32(a)].lsn;
50069: 936: XLogRecPtr pos_b = state->entries[DatumGetInt32(b)].lsn;
-: 937:
50069: 938: if (pos_a < pos_b)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
50058: 939: return 1;
11: 940: else if (pos_a == pos_b)
branch 0 taken 9% (fallthrough)
branch 1 taken 91%
1: 941: return 0;
10: 942: return -1;
-: 943:}
-: 944:
-: 945:/*
-: 946: * Allocate & initialize an iterator which iterates in lsn order over a
-: 947: * transaction and all its subtransactions.
-: 948: */
-: 949:static ReorderBufferIterTXNState *
function ReorderBufferIterTXNInit called 445 returned 100% blocks executed 95%
445: 950:ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 951:{
445: 952: Size nr_txns = 0;
-: 953: ReorderBufferIterTXNState *state;
-: 954: dlist_iter cur_txn_i;
-: 955: int32 off;
-: 956:
-: 957: /*
-: 958: * Calculate the size of our heap: one element for every transaction that
-: 959: * contains changes. (Besides the transactions already in the reorder
-: 960: * buffer, we count the one we were directly passed.)
-: 961: */
445: 962: if (txn->nentries > 0)
branch 0 taken 97% (fallthrough)
branch 1 taken 3%
433: 963: nr_txns++;
-: 964:
544: 965: dlist_foreach(cur_txn_i, &txn->subtxns)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 18%
branch 3 taken 82% (fallthrough)
-: 966: {
-: 967: ReorderBufferTXN *cur_txn;
-: 968:
99: 969: cur_txn = dlist_container(ReorderBufferTXN, node, cur_txn_i.cur);
-: 970:
99: 971: if (cur_txn->nentries > 0)
branch 0 taken 32% (fallthrough)
branch 1 taken 68%
32: 972: nr_txns++;
-: 973: }
-: 974:
-: 975: /*
-: 976: * TODO: Consider adding fastpath for the rather common nr_txns=1 case, no
-: 977: * need to allocate/build a heap then.
-: 978: */
-: 979:
-: 980: /* allocate iteration state */
445: 981: state = (ReorderBufferIterTXNState *)
call 0 returned 100%
445: 982: MemoryContextAllocZero(rb->context,
-: 983: sizeof(ReorderBufferIterTXNState) +
445: 984: sizeof(ReorderBufferIterTXNEntry) * nr_txns);
-: 985:
445: 986: state->nr_txns = nr_txns;
445: 987: dlist_init(&state->old_change);
call 0 returned 100%
-: 988:
910: 989: for (off = 0; off < state->nr_txns; off++)
branch 0 taken 51%
branch 1 taken 49% (fallthrough)
-: 990: {
465: 991: state->entries[off].fd = -1;
465: 992: state->entries[off].segno = 0;
-: 993: }
-: 994:
-: 995: /* allocate heap */
445: 996: state->heap = binaryheap_allocate(state->nr_txns,
call 0 returned 100%
-: 997: ReorderBufferIterCompare,
-: 998: state);
-: 999:
-: 1000: /*
-: 1001: * Now insert items into the binary heap, in an unordered fashion. (We
-: 1002: * will run a heap assembly step at the end; this is more efficient.)
-: 1003: */
-: 1004:
445: 1005: off = 0;
-: 1006:
-: 1007: /* add toplevel transaction if it contains changes */
445: 1008: if (txn->nentries > 0)
branch 0 taken 97% (fallthrough)
branch 1 taken 3%
-: 1009: {
-: 1010: ReorderBufferChange *cur_change;
-: 1011:
433: 1012: if (txn->serialized)
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
-: 1013: {
-: 1014: /* serialize remaining changes */
9: 1015: ReorderBufferSerializeTXN(rb, txn);
call 0 returned 100%
9: 1016: ReorderBufferRestoreChanges(rb, txn, &state->entries[off].fd,
call 0 returned 100%
-: 1017: &state->entries[off].segno);
-: 1018: }
-: 1019:
433: 1020: cur_change = dlist_head_element(ReorderBufferChange, node,
call 0 returned 100%
-: 1021: &txn->changes);
-: 1022:
433: 1023: state->entries[off].lsn = cur_change->lsn;
433: 1024: state->entries[off].change = cur_change;
433: 1025: state->entries[off].txn = txn;
-: 1026:
433: 1027: binaryheap_add_unordered(state->heap, Int32GetDatum(off++));
call 0 returned 100%
-: 1028: }
-: 1029:
-: 1030: /* add subtransactions if they contain changes */
544: 1031: dlist_foreach(cur_txn_i, &txn->subtxns)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 18%
branch 3 taken 82% (fallthrough)
-: 1032: {
-: 1033: ReorderBufferTXN *cur_txn;
-: 1034:
99: 1035: cur_txn = dlist_container(ReorderBufferTXN, node, cur_txn_i.cur);
-: 1036:
99: 1037: if (cur_txn->nentries > 0)
branch 0 taken 32% (fallthrough)
branch 1 taken 68%
-: 1038: {
-: 1039: ReorderBufferChange *cur_change;
-: 1040:
32: 1041: if (cur_txn->serialized)
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
-: 1042: {
-: 1043: /* serialize remaining changes */
16: 1044: ReorderBufferSerializeTXN(rb, cur_txn);
call 0 returned 100%
16: 1045: ReorderBufferRestoreChanges(rb, cur_txn,
call 0 returned 100%
-: 1046: &state->entries[off].fd,
-: 1047: &state->entries[off].segno);
-: 1048: }
32: 1049: cur_change = dlist_head_element(ReorderBufferChange, node,
call 0 returned 100%
-: 1050: &cur_txn->changes);
-: 1051:
32: 1052: state->entries[off].lsn = cur_change->lsn;
32: 1053: state->entries[off].change = cur_change;
32: 1054: state->entries[off].txn = cur_txn;
-: 1055:
32: 1056: binaryheap_add_unordered(state->heap, Int32GetDatum(off++));
call 0 returned 100%
-: 1057: }
-: 1058: }
-: 1059:
-: 1060: /* assemble a valid binary heap */
445: 1061: binaryheap_build(state->heap);
call 0 returned 100%
-: 1062:
445: 1063: return state;
-: 1064:}
-: 1065:
-: 1066:/*
-: 1067: * Return the next change when iterating over a transaction and its
-: 1068: * subtransactions.
-: 1069: *
-: 1070: * Returns NULL when no further changes exist.
-: 1071: */
-: 1072:static ReorderBufferChange *
function ReorderBufferIterTXNNext called 157321 returned 100% blocks executed 93%
157321: 1073:ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state)
-: 1074:{
-: 1075: ReorderBufferChange *change;
-: 1076: ReorderBufferIterTXNEntry *entry;
-: 1077: int32 off;
-: 1078:
-: 1079: /* nothing there anymore */
157321: 1080: if (state->heap->bh_size == 0)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
445: 1081: return NULL;
-: 1082:
156876: 1083: off = DatumGetInt32(binaryheap_first(state->heap));
call 0 returned 100%
156876: 1084: entry = &state->entries[off];
-: 1085:
-: 1086: /* free memory we might have "leaked" in the previous *Next call */
156876: 1087: if (!dlist_is_empty(&state->old_change))
call 0 returned 100%
branch 1 taken 1% (fallthrough)
branch 2 taken 99%
-: 1088: {
39: 1089: change = dlist_container(ReorderBufferChange, node,
call 0 returned 100%
-: 1090: dlist_pop_head_node(&state->old_change));
39: 1091: ReorderBufferReturnChange(rb, change);
call 0 returned 100%
39: 1092: Assert(dlist_is_empty(&state->old_change));
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 1093: }
-: 1094:
156876: 1095: change = entry->change;
-: 1096:
-: 1097: /*
-: 1098: * update heap with information about which transaction has the next
-: 1099: * relevant change in LSN order
-: 1100: */
-: 1101:
-: 1102: /* there are in-memory changes */
156876: 1103: if (dlist_has_next(&entry->txn->changes, &entry->change->node))
call 0 returned 100%
branch 1 taken 99% (fallthrough)
branch 2 taken 1%
-: 1104: {
156381: 1105: dlist_node *next = dlist_next_node(&entry->txn->changes, &change->node);
call 0 returned 100%
156381: 1106: ReorderBufferChange *next_change =
156381: 1107: dlist_container(ReorderBufferChange, node, next);
-: 1108:
-: 1109: /* txn stays the same */
156381: 1110: state->entries[off].lsn = next_change->lsn;
156381: 1111: state->entries[off].change = next_change;
-: 1112:
156381: 1113: binaryheap_replace_first(state->heap, Int32GetDatum(off));
call 0 returned 100%
156381: 1114: return change;
-: 1115: }
-: 1116:
-: 1117: /* try to load changes from disk */
495: 1118: if (entry->txn->nentries != entry->txn->nentries_mem)
branch 0 taken 11% (fallthrough)
branch 1 taken 89%
-: 1119: {
-: 1120: /*
-: 1121: * Ugly: restoring changes will reuse *Change records, thus delete the
-: 1122: * current one from the per-tx list and only free in the next call.
-: 1123: */
54: 1124: dlist_delete(&change->node);
call 0 returned 100%
54: 1125: dlist_push_tail(&state->old_change, &change->node);
call 0 returned 100%
-: 1126:
54: 1127: if (ReorderBufferRestoreChanges(rb, entry->txn, &entry->fd,
call 0 returned 100%
branch 1 taken 56% (fallthrough)
branch 2 taken 44%
-: 1128: &state->entries[off].segno))
-: 1129: {
-: 1130: /* successfully restored changes from disk */
30: 1131: ReorderBufferChange *next_change =
call 0 returned 100%
30: 1132: dlist_head_element(ReorderBufferChange, node,
-: 1133: &entry->txn->changes);
-: 1134:
30: 1135: elog(DEBUG2, "restored %u/%u changes from disk",
call 0 returned 100%
call 1 returned 100%
-: 1136: (uint32) entry->txn->nentries_mem,
-: 1137: (uint32) entry->txn->nentries);
-: 1138:
30: 1139: Assert(entry->txn->nentries_mem);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1140: /* txn stays the same */
30: 1141: state->entries[off].lsn = next_change->lsn;
30: 1142: state->entries[off].change = next_change;
30: 1143: binaryheap_replace_first(state->heap, Int32GetDatum(off));
call 0 returned 100%
-: 1144:
30: 1145: return change;
-: 1146: }
-: 1147: }
-: 1148:
-: 1149: /* ok, no changes there anymore, remove */
465: 1150: binaryheap_remove_first(state->heap);
call 0 returned 100%
-: 1151:
465: 1152: return change;
-: 1153:}
-: 1154:
-: 1155:/*
-: 1156: * Deallocate the iterator
-: 1157: */
-: 1158:static void
function ReorderBufferIterTXNFinish called 445 returned 100% blocks executed 87%
445: 1159:ReorderBufferIterTXNFinish(ReorderBuffer *rb,
-: 1160: ReorderBufferIterTXNState *state)
-: 1161:{
-: 1162: int32 off;
-: 1163:
910: 1164: for (off = 0; off < state->nr_txns; off++)
branch 0 taken 51%
branch 1 taken 49% (fallthrough)
-: 1165: {
465: 1166: if (state->entries[off].fd != -1)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1167: CloseTransientFile(state->entries[off].fd);
call 0 never executed
-: 1168: }
-: 1169:
-: 1170: /* free memory we might have "leaked" in the last *Next call */
445: 1171: if (!dlist_is_empty(&state->old_change))
call 0 returned 100%
branch 1 taken 3% (fallthrough)
branch 2 taken 97%
-: 1172: {
-: 1173: ReorderBufferChange *change;
-: 1174:
14: 1175: change = dlist_container(ReorderBufferChange, node,
call 0 returned 100%
-: 1176: dlist_pop_head_node(&state->old_change));
14: 1177: ReorderBufferReturnChange(rb, change);
call 0 returned 100%
14: 1178: Assert(dlist_is_empty(&state->old_change));
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 1179: }
-: 1180:
445: 1181: binaryheap_free(state->heap);
call 0 returned 100%
445: 1182: pfree(state);
call 0 returned 100%
445: 1183:}
-: 1184:
-: 1185:/*
-: 1186: * Cleanup the contents of a transaction, usually after the transaction
-: 1187: * committed or aborted.
-: 1188: */
-: 1189:static void
function ReorderBufferCleanupTXN called 1989 returned 100% blocks executed 82%
1989: 1190:ReorderBufferCleanupTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 1191:{
-: 1192: bool found;
-: 1193: dlist_mutable_iter iter;
-: 1194:
-: 1195: /* cleanup subtransactions & their changes */
2088: 1196: dlist_foreach_modify(iter, &txn->subtxns)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 5%
branch 3 taken 95% (fallthrough)
-: 1197: {
-: 1198: ReorderBufferTXN *subtxn;
-: 1199:
99: 1200: subtxn = dlist_container(ReorderBufferTXN, node, iter.cur);
-: 1201:
-: 1202: /*
-: 1203: * Subtransactions are always associated to the toplevel TXN, even if
-: 1204: * they originally were happening inside another subtxn, so we won't
-: 1205: * ever recurse more than one level deep here.
-: 1206: */
99: 1207: Assert(subtxn->is_known_as_subxact);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
99: 1208: Assert(subtxn->nsubtxns == 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1209:
99: 1210: ReorderBufferCleanupTXN(rb, subtxn);
call 0 returned 100%
-: 1211: }
-: 1212:
-: 1213: /* cleanup changes in the toplevel txn */
161530: 1214: dlist_foreach_modify(iter, &txn->changes)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 99%
branch 3 taken 1% (fallthrough)
-: 1215: {
-: 1216: ReorderBufferChange *change;
-: 1217:
159541: 1218: change = dlist_container(ReorderBufferChange, node, iter.cur);
-: 1219:
159541: 1220: ReorderBufferReturnChange(rb, change);
call 0 returned 100%
-: 1221: }
-: 1222:
-: 1223: /*
-: 1224: * Cleanup the tuplecids we stored for decoding catalog snapshot access.
-: 1225: * They are always stored in the toplevel transaction.
-: 1226: */
15619: 1227: dlist_foreach_modify(iter, &txn->tuplecids)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 87%
branch 3 taken 13% (fallthrough)
-: 1228: {
-: 1229: ReorderBufferChange *change;
-: 1230:
13630: 1231: change = dlist_container(ReorderBufferChange, node, iter.cur);
13630: 1232: Assert(change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
13630: 1233: ReorderBufferReturnChange(rb, change);
call 0 returned 100%
-: 1234: }
-: 1235:
-: 1236: /*
-: 1237: * Cleanup the base snapshot, if set.
-: 1238: */
1989: 1239: if (txn->base_snapshot != NULL)
branch 0 taken 67% (fallthrough)
branch 1 taken 33%
-: 1240: {
1341: 1241: SnapBuildSnapDecRefcount(txn->base_snapshot);
call 0 returned 100%
1341: 1242: dlist_delete(&txn->base_snapshot_node);
call 0 returned 100%
-: 1243: }
-: 1244:
-: 1245: /*
-: 1246: * Remove TXN from its containing list.
-: 1247: *
-: 1248: * Note: if txn->is_known_as_subxact, we are deleting the TXN from its
-: 1249: * parent's list of known subxacts; this leaves the parent's nsubxacts
-: 1250: * count too high, but we don't care. Otherwise, we are deleting the TXN
-: 1251: * from the LSN-ordered list of toplevel TXNs.
-: 1252: */
1989: 1253: dlist_delete(&txn->node);
call 0 returned 100%
-: 1254:
-: 1255: /* now remove reference from buffer */
1989: 1256: hash_search(rb->by_txn,
call 0 returned 100%
1989: 1257: (void *) &txn->xid,
-: 1258: HASH_REMOVE,
-: 1259: &found);
1989: 1260: Assert(found);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1261:
-: 1262: /* remove entries spilled to disk */
1989: 1263: if (txn->serialized)
branch 0 taken 9% (fallthrough)
branch 1 taken 91%
184: 1264: ReorderBufferRestoreCleanup(rb, txn);
call 0 returned 100%
-: 1265:
-: 1266: /* deallocate */
1989: 1267: ReorderBufferReturnTXN(rb, txn);
call 0 returned 100%
1989: 1268:}
-: 1269:
-: 1270:/*
-: 1271: * Build a hash with a (relfilenode, ctid) -> (cmin, cmax) mapping for use by
-: 1272: * HeapTupleSatisfiesHistoricMVCC.
-: 1273: */
-: 1274:static void
function ReorderBufferBuildTupleCidHash called 445 returned 100% blocks executed 81%
445: 1275:ReorderBufferBuildTupleCidHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 1276:{
-: 1277: dlist_iter iter;
-: 1278: HASHCTL hash_ctl;
-: 1279:
445: 1280: if (!txn->has_catalog_changes || dlist_is_empty(&txn->tuplecids))
branch 0 taken 38% (fallthrough)
branch 1 taken 62%
call 2 returned 100%
branch 3 taken 2% (fallthrough)
branch 4 taken 98%
723: 1281: return;
-: 1282:
167: 1283: memset(&hash_ctl, 0, sizeof(hash_ctl));
-: 1284:
167: 1285: hash_ctl.keysize = sizeof(ReorderBufferTupleCidKey);
167: 1286: hash_ctl.entrysize = sizeof(ReorderBufferTupleCidEnt);
167: 1287: hash_ctl.hcxt = rb->context;
-: 1288:
-: 1289: /*
-: 1290: * create the hash with the exact number of to-be-stored tuplecids from
-: 1291: * the start
-: 1292: */
167: 1293: txn->tuplecid_hash =
167: 1294: hash_create("ReorderBufferTupleCid", txn->ntuplecids, &hash_ctl,
call 0 returned 100%
-: 1295: HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
-: 1296:
5671: 1297: dlist_foreach(iter, &txn->tuplecids)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 97%
branch 3 taken 3% (fallthrough)
-: 1298: {
-: 1299: ReorderBufferTupleCidKey key;
-: 1300: ReorderBufferTupleCidEnt *ent;
-: 1301: bool found;
-: 1302: ReorderBufferChange *change;
-: 1303:
5504: 1304: change = dlist_container(ReorderBufferChange, node, iter.cur);
-: 1305:
5504: 1306: Assert(change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1307:
-: 1308: /* be careful about padding */
5504: 1309: memset(&key, 0, sizeof(ReorderBufferTupleCidKey));
-: 1310:
5504: 1311: key.relnode = change->data.tuplecid.node;
-: 1312:
5504: 1313: ItemPointerCopy(&change->data.tuplecid.tid,
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1314: &key.tid);
-: 1315:
5504: 1316: ent = (ReorderBufferTupleCidEnt *)
call 0 returned 100%
5504: 1317: hash_search(txn->tuplecid_hash,
-: 1318: (void *) &key,
-: 1319: HASH_ENTER | HASH_FIND,
-: 1320: &found);
5504: 1321: if (!found)
branch 0 taken 74% (fallthrough)
branch 1 taken 26%
-: 1322: {
4055: 1323: ent->cmin = change->data.tuplecid.cmin;
4055: 1324: ent->cmax = change->data.tuplecid.cmax;
4055: 1325: ent->combocid = change->data.tuplecid.combocid;
-: 1326: }
-: 1327: else
-: 1328: {
-: 1329: /*
-: 1330: * Maybe we already saw this tuple before in this transaction, but
-: 1331: * if so it must have the same cmin.
-: 1332: */
1449: 1333: Assert(ent->cmin == change->data.tuplecid.cmin);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1334:
-: 1335: /*
-: 1336: * cmax may be initially invalid, but once set it can only grow,
-: 1337: * and never become invalid again.
-: 1338: */
1449: 1339: Assert((ent->cmax == InvalidCommandId) ||
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 0% (fallthrough)
branch 5 taken 100%
call 6 never executed
-: 1340: ((change->data.tuplecid.cmax != InvalidCommandId) &&
-: 1341: (change->data.tuplecid.cmax > ent->cmax)));
1449: 1342: ent->cmax = change->data.tuplecid.cmax;
-: 1343: }
-: 1344: }
-: 1345:}
-: 1346:
-: 1347:/*
-: 1348: * Copy a provided snapshot so we can modify it privately. This is needed so
-: 1349: * that catalog modifying transactions can look into intermediate catalog
-: 1350: * states.
-: 1351: */
-: 1352:static Snapshot
function ReorderBufferCopySnap called 335 returned 100% blocks executed 90%
335: 1353:ReorderBufferCopySnap(ReorderBuffer *rb, Snapshot orig_snap,
-: 1354: ReorderBufferTXN *txn, CommandId cid)
-: 1355:{
-: 1356: Snapshot snap;
-: 1357: dlist_iter iter;
335: 1358: int i = 0;
-: 1359: Size size;
-: 1360:
335: 1361: size = sizeof(SnapshotData) +
670: 1362: sizeof(TransactionId) * orig_snap->xcnt +
335: 1363: sizeof(TransactionId) * (txn->nsubtxns + 1);
-: 1364:
335: 1365: snap = MemoryContextAllocZero(rb->context, size);
call 0 returned 100%
335: 1366: memcpy(snap, orig_snap, sizeof(SnapshotData));
-: 1367:
335: 1368: snap->copied = true;
335: 1369: snap->active_count = 1; /* mark as active so nobody frees it */
335: 1370: snap->regd_count = 0;
335: 1371: snap->xip = (TransactionId *) (snap + 1);
-: 1372:
335: 1373: memcpy(snap->xip, orig_snap->xip, sizeof(TransactionId) * snap->xcnt);
-: 1374:
-: 1375: /*
-: 1376: * snap->subxip contains all txids that belong to our transaction which we
-: 1377: * need to check via cmin/cmax. That's why we store the toplevel
-: 1378: * transaction in there as well.
-: 1379: */
335: 1380: snap->subxip = snap->xip + snap->xcnt;
335: 1381: snap->subxip[i++] = txn->xid;
-: 1382:
-: 1383: /*
-: 1384: * subxcnt isn't decreased when subtransactions abort, so count manually.
-: 1385: * Since it's an upper boundary it is safe to use it for the allocation
-: 1386: * above.
-: 1387: */
335: 1388: snap->subxcnt = 1;
-: 1389:
338: 1390: dlist_foreach(iter, &txn->subtxns)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 1%
branch 3 taken 99% (fallthrough)
-: 1391: {
-: 1392: ReorderBufferTXN *sub_txn;
-: 1393:
3: 1394: sub_txn = dlist_container(ReorderBufferTXN, node, iter.cur);
3: 1395: snap->subxip[i++] = sub_txn->xid;
3: 1396: snap->subxcnt++;
-: 1397: }
-: 1398:
-: 1399: /* sort so we can bsearch() later */
335: 1400: qsort(snap->subxip, snap->subxcnt, sizeof(TransactionId), xidComparator);
call 0 returned 100%
-: 1401:
-: 1402: /* store the specified current CommandId */
335: 1403: snap->curcid = cid;
-: 1404:
335: 1405: return snap;
-: 1406:}
-: 1407:
-: 1408:/*
-: 1409: * Free a previously ReorderBufferCopySnap'ed snapshot
-: 1410: */
-: 1411:static void
function ReorderBufferFreeSnap called 816 returned 100% blocks executed 100%
816: 1412:ReorderBufferFreeSnap(ReorderBuffer *rb, Snapshot snap)
-: 1413:{
816: 1414: if (snap->copied)
branch 0 taken 41% (fallthrough)
branch 1 taken 59%
337: 1415: pfree(snap);
call 0 returned 100%
-: 1416: else
479: 1417: SnapBuildSnapDecRefcount(snap);
call 0 returned 100%
816: 1418:}
-: 1419:
-: 1420:/*
-: 1421: * Perform the replay of a transaction and its non-aborted subtransactions.
-: 1422: *
-: 1423: * Subtransactions previously have to be processed by
-: 1424: * ReorderBufferCommitChild(), even if previously assigned to the toplevel
-: 1425: * transaction with ReorderBufferAssignChild.
-: 1426: *
-: 1427: * We currently can only decode a transaction's contents when its commit
-: 1428: * record is read because that's the only place where we know about cache
-: 1429: * invalidations. Thus, once a toplevel commit is read, we iterate over the top
-: 1430: * and subtransactions (using a k-way merge) and replay the changes in lsn
-: 1431: * order.
-: 1432: */
-: 1433:void
function ReorderBufferCommit called 447 returned 100% blocks executed 71%
447: 1434:ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
-: 1435: XLogRecPtr commit_lsn, XLogRecPtr end_lsn,
-: 1436: TimestampTz commit_time,
-: 1437: RepOriginId origin_id, XLogRecPtr origin_lsn)
-: 1438:{
-: 1439: ReorderBufferTXN *txn;
-: 1440: volatile Snapshot snapshot_now;
447: 1441: volatile CommandId command_id = FirstCommandId;
-: 1442: bool using_subtxn;
447: 1443: ReorderBufferIterTXNState *volatile iterstate = NULL;
-: 1444:
447: 1445: txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
call 0 returned 100%
-: 1446: false);
-: 1447:
-: 1448: /* unknown transaction, nothing to replay */
447: 1449: if (txn == NULL)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
1: 1450: return;
-: 1451:
446: 1452: txn->final_lsn = commit_lsn;
446: 1453: txn->end_lsn = end_lsn;
446: 1454: txn->commit_time = commit_time;
446: 1455: txn->origin_id = origin_id;
446: 1456: txn->origin_lsn = origin_lsn;
-: 1457:
-: 1458: /*
-: 1459: * If this transaction has no snapshot, it didn't make any changes to the
-: 1460: * database, so there's nothing to decode. Note that
-: 1461: * ReorderBufferCommitChild will have transferred any snapshots from
-: 1462: * subtransactions if there were any.
-: 1463: */
446: 1464: if (txn->base_snapshot == NULL)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 1465: {
1: 1466: Assert(txn->ninvalidations == 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1: 1467: ReorderBufferCleanupTXN(rb, txn);
call 0 returned 100%
1: 1468: return;
-: 1469: }
-: 1470:
445: 1471: snapshot_now = txn->base_snapshot;
-: 1472:
-: 1473: /* build data to be able to lookup the CommandIds of catalog tuples */
445: 1474: ReorderBufferBuildTupleCidHash(rb, txn);
call 0 returned 100%
-: 1475:
-: 1476: /* setup the initial snapshot */
445: 1477: SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash);
call 0 returned 100%
-: 1478:
-: 1479: /*
-: 1480: * Decoding needs access to syscaches et al., which in turn use
-: 1481: * heavyweight locks and such. Thus we need to have enough state around to
-: 1482: * keep track of those. The easiest way is to simply use a transaction
-: 1483: * internally. That also allows us to easily enforce that nothing writes
-: 1484: * to the database by checking for xid assignments.
-: 1485: *
-: 1486: * When we're called via the SQL SRF there's already a transaction
-: 1487: * started, so start an explicit subtransaction there.
-: 1488: */
445: 1489: using_subtxn = IsTransactionOrTransactionBlock();
call 0 returned 100%
-: 1490:
445: 1491: PG_TRY();
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
-: 1492: {
-: 1493: ReorderBufferChange *change;
445: 1494: ReorderBufferChange *specinsert = NULL;
-: 1495:
445: 1496: if (using_subtxn)
branch 0 taken 73% (fallthrough)
branch 1 taken 27%
325: 1497: BeginInternalSubTransaction("replay");
call 0 returned 100%
-: 1498: else
120: 1499: StartTransactionCommand();
call 0 returned 100%
-: 1500:
445: 1501: rb->begin(rb, txn);
call 0 returned 100%
-: 1502:
445: 1503: iterstate = ReorderBufferIterTXNInit(rb, txn);
call 0 returned 100%
157766: 1504: while ((change = ReorderBufferIterTXNNext(rb, iterstate)) != NULL)
call 0 returned 100%
branch 1 taken 99%
branch 2 taken 1% (fallthrough)
-: 1505: {
156876: 1506: Relation relation = NULL;
-: 1507: Oid reloid;
-: 1508:
156876: 1509: switch (change->action)
branch 0 taken 1%
branch 1 taken 94%
branch 2 taken 1%
branch 3 taken 1%
branch 4 taken 1%
branch 5 taken 1%
branch 6 taken 3%
branch 7 taken 0%
branch 8 taken 0%
-: 1510: {
-: 1511: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
-: 1512:
-: 1513: /*
-: 1514: * Confirmation for speculative insertion arrived. Simply
-: 1515: * use as a normal record. It'll be cleaned up at the end
-: 1516: * of INSERT processing.
-: 1517: */
1782: 1518: if (specinsert == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1519: elog(ERROR, "invalid ordering of speculative insertion changes");
call 0 never executed
call 1 never executed
call 2 never executed
1782: 1520: Assert(specinsert->data.tp.oldtuple == NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1782: 1521: change = specinsert;
1782: 1522: change->action = REORDER_BUFFER_CHANGE_INSERT;
-: 1523:
-: 1524: /* intentionally fall through */
-: 1525: case REORDER_BUFFER_CHANGE_INSERT:
-: 1526: case REORDER_BUFFER_CHANGE_UPDATE:
-: 1527: case REORDER_BUFFER_CHANGE_DELETE:
149494: 1528: Assert(snapshot_now);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1529:
149494: 1530: reloid = RelidByRelfilenode(change->data.tp.relnode.spcNode,
call 0 returned 100%
-: 1531: change->data.tp.relnode.relNode);
-: 1532:
-: 1533: /*
-: 1534: * Mapped catalog tuple without data, emitted while
-: 1535: * catalog table was in the process of being rewritten. We
-: 1536: * can fail to look up the relfilenode, because the
-: 1537: * relmapper has no "historic" view, in contrast to normal
-: 1538: * the normal catalog during decoding. Thus repeated
-: 1539: * rewrites can cause a lookup failure. That's OK because
-: 1540: * we do not decode catalog changes anyway. Normally such
-: 1541: * tuples would be skipped over below, but we can't
-: 1542: * identify whether the table should be logically logged
-: 1543: * without mapping the relfilenode to the oid.
-: 1544: */
149571: 1545: if (reloid == InvalidOid &&
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
154: 1546: change->data.tp.newtuple == NULL &&
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
77: 1547: change->data.tp.oldtuple == NULL)
-: 1548: goto change_done;
149417: 1549: else if (reloid == InvalidOid)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1550: elog(ERROR, "could not map filenode \"%s\" to relation OID",
call 0 never executed
call 1 never executed
call 2 never executed
call 3 never executed
-: 1551: relpathperm(change->data.tp.relnode,
-: 1552: MAIN_FORKNUM));
-: 1553:
149417: 1554: relation = RelationIdGetRelation(reloid);
call 0 returned 100%
-: 1555:
149417: 1556: if (!RelationIsValid(relation))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1557: elog(ERROR, "could not open relation with OID %u (for filenode \"%s\")",
call 0 never executed
call 1 never executed
call 2 never executed
call 3 never executed
-: 1558: reloid,
-: 1559: relpathperm(change->data.tp.relnode,
-: 1560: MAIN_FORKNUM));
-: 1561:
149417: 1562: if (!RelationIsLogicallyLogged(relation))
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
call 4 returned 100%
branch 5 taken 99% (fallthrough)
branch 6 taken 1%
-: 1563: goto change_done;
-: 1564:
-: 1565: /*
-: 1566: * Ignore temporary heaps created during DDL unless the
-: 1567: * plugin has asked for them.
-: 1568: */
147530: 1569: if (relation->rd_rel->relrewrite && !rb->output_rewrites)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
branch 2 taken 95% (fallthrough)
branch 3 taken 5%
20: 1570: goto change_done;
-: 1571:
-: 1572: /*
-: 1573: * For now ignore sequence changes entirely. Most of the
-: 1574: * time they don't log changes using records we
-: 1575: * understand, so it doesn't make sense to handle the few
-: 1576: * cases we do.
-: 1577: */
147510: 1578: if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1579: goto change_done;
-: 1580:
-: 1581: /* user-triggered change */
147510: 1582: if (!IsToastRelation(relation))
call 0 returned 100%
branch 1 taken 99% (fallthrough)
branch 2 taken 1%
-: 1583: {
146981: 1584: ReorderBufferToastReplace(rb, txn, relation, change);
call 0 returned 100%
146981: 1585: rb->apply_change(rb, txn, relation, change);
call 0 returned 100%
-: 1586:
-: 1587: /*
-: 1588: * Only clear reassembled toast chunks if we're sure
-: 1589: * they're not required anymore. The creator of the
-: 1590: * tuple tells us.
-: 1591: */
146981: 1592: if (change->data.tp.clear_toast_afterwards)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
146779: 1593: ReorderBufferToastReset(rb, txn);
call 0 returned 100%
-: 1594: }
-: 1595: /* we're not interested in toast deletions */
529: 1596: else if (change->action == REORDER_BUFFER_CHANGE_INSERT)
branch 0 taken 57% (fallthrough)
branch 1 taken 43%
-: 1597: {
-: 1598: /*
-: 1599: * Need to reassemble the full toasted Datum in
-: 1600: * memory, to ensure the chunks don't get reused till
-: 1601: * we're done remove it from the list of this
-: 1602: * transaction's changes. Otherwise it will get
-: 1603: * freed/reused while restoring spooled data from
-: 1604: * disk.
-: 1605: */
301: 1606: Assert(change->data.tp.newtuple != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1607:
301: 1608: dlist_delete(&change->node);
call 0 returned 100%
301: 1609: ReorderBufferToastAppendChunk(rb, txn, relation,
call 0 returned 100%
-: 1610: change);
-: 1611: }
-: 1612:
-: 1613: change_done:
-: 1614:
-: 1615: /*
-: 1616: * Either speculative insertion was confirmed, or it was
-: 1617: * unsuccessful and the record isn't needed anymore.
-: 1618: */
149494: 1619: if (specinsert != NULL)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 1620: {
1782: 1621: ReorderBufferReturnChange(rb, specinsert);
call 0 returned 100%
1782: 1622: specinsert = NULL;
-: 1623: }
-: 1624:
149494: 1625: if (relation != NULL)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
-: 1626: {
149417: 1627: RelationClose(relation);
call 0 returned 100%
149417: 1628: relation = NULL;
-: 1629: }
149494: 1630: break;
-: 1631:
-: 1632: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
-: 1633:
-: 1634: /*
-: 1635: * Speculative insertions are dealt with by delaying the
-: 1636: * processing of the insert until the confirmation record
-: 1637: * arrives. For that we simply unlink the record from the
-: 1638: * chain, so it does not get freed/reused while restoring
-: 1639: * spooled data from disk.
-: 1640: *
-: 1641: * This is safe in the face of concurrent catalog changes
-: 1642: * because the relevant relation can't be changed between
-: 1643: * speculative insertion and confirmation due to
-: 1644: * CheckTableNotInUse() and locking.
-: 1645: */
-: 1646:
-: 1647: /* clear out a pending (and thus failed) speculation */
1782: 1648: if (specinsert != NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1649: {
#####: 1650: ReorderBufferReturnChange(rb, specinsert);
call 0 never executed
#####: 1651: specinsert = NULL;
-: 1652: }
-: 1653:
-: 1654: /* and memorize the pending insertion */
1782: 1655: dlist_delete(&change->node);
call 0 returned 100%
1782: 1656: specinsert = change;
1782: 1657: break;
-: 1658:
-: 1659: case REORDER_BUFFER_CHANGE_TRUNCATE:
-: 1660: {
-: 1661: int i;
8: 1662: int nrelids = change->data.truncate.nrelids;
8: 1663: int nrelations = 0;
-: 1664: Relation *relations;
-: 1665:
8: 1666: relations = palloc0(nrelids * sizeof(Relation));
call 0 returned 100%
18: 1667: for (i = 0; i < nrelids; i++)
branch 0 taken 56%
branch 1 taken 44% (fallthrough)
-: 1668: {
10: 1669: Oid relid = change->data.truncate.relids[i];
-: 1670: Relation relation;
-: 1671:
10: 1672: relation = RelationIdGetRelation(relid);
call 0 returned 100%
-: 1673:
10: 1674: if (!RelationIsValid(relation))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1675: elog(ERROR, "could not open relation with OID %u", relid);
call 0 never executed
call 1 never executed
call 2 never executed
-: 1676:
10: 1677: if (!RelationIsLogicallyLogged(relation))
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
call 4 returned 100%
branch 5 taken 0% (fallthrough)
branch 6 taken 100%
#####: 1678: continue;
-: 1679:
10: 1680: relations[nrelations++] = relation;
-: 1681: }
-: 1682:
8: 1683: rb->apply_truncate(rb, txn, nrelations, relations, change);
call 0 returned 100%
-: 1684:
18: 1685: for (i = 0; i < nrelations; i++)
branch 0 taken 56%
branch 1 taken 44% (fallthrough)
10: 1686: RelationClose(relations[i]);
call 0 returned 100%
-: 1687:
8: 1688: break;
-: 1689: }
-: 1690:
-: 1691: case REORDER_BUFFER_CHANGE_MESSAGE:
15: 1692: rb->message(rb, txn, change->lsn, true,
call 0 returned 100%
5: 1693: change->data.msg.prefix,
-: 1694: change->data.msg.message_size,
5: 1695: change->data.msg.message);
5: 1696: break;
-: 1697:
-: 1698: case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
-: 1699: /* get rid of the old */
186: 1700: TeardownHistoricSnapshot(false);
call 0 returned 100%
-: 1701:
186: 1702: if (snapshot_now->copied)
branch 0 taken 90% (fallthrough)
branch 1 taken 10%
-: 1703: {
168: 1704: ReorderBufferFreeSnap(rb, snapshot_now);
call 0 returned 100%
168: 1705: snapshot_now =
168: 1706: ReorderBufferCopySnap(rb, change->data.snapshot,
call 0 returned 100%
-: 1707: txn, command_id);
-: 1708: }
-: 1709:
-: 1710: /*
-: 1711: * Restored from disk, need to be careful not to double
-: 1712: * free. We could introduce refcounting for that, but for
-: 1713: * now this seems infrequent enough not to care.
-: 1714: */
18: 1715: else if (change->data.snapshot->copied)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1716: {
#####: 1717: snapshot_now =
#####: 1718: ReorderBufferCopySnap(rb, change->data.snapshot,
call 0 never executed
-: 1719: txn, command_id);
-: 1720: }
-: 1721: else
-: 1722: {
18: 1723: snapshot_now = change->data.snapshot;
-: 1724: }
-: 1725:
-: 1726:
-: 1727: /* and continue with the new one */
186: 1728: SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash);
call 0 returned 100%
186: 1729: break;
-: 1730:
-: 1731: case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
5401: 1732: Assert(change->data.command_id != InvalidCommandId);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1733:
5401: 1734: if (command_id < change->data.command_id)
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
-: 1735: {
785: 1736: command_id = change->data.command_id;
-: 1737:
785: 1738: if (!snapshot_now->copied)
branch 0 taken 21% (fallthrough)
branch 1 taken 79%
-: 1739: {
-: 1740: /* we don't use the global one anymore */
167: 1741: snapshot_now = ReorderBufferCopySnap(rb, snapshot_now,
call 0 returned 100%
-: 1742: txn, command_id);
-: 1743: }
-: 1744:
785: 1745: snapshot_now->curcid = command_id;
-: 1746:
785: 1747: TeardownHistoricSnapshot(false);
call 0 returned 100%
785: 1748: SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash);
call 0 returned 100%
-: 1749:
-: 1750: /*
-: 1751: * Every time the CommandId is incremented, we could
-: 1752: * see new catalog contents, so execute all
-: 1753: * invalidations.
-: 1754: */
785: 1755: ReorderBufferExecuteInvalidations(rb, txn);
call 0 returned 100%
-: 1756: }
-: 1757:
5401: 1758: break;
-: 1759:
-: 1760: case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
#####: 1761: elog(ERROR, "tuplecid value in changequeue");
call 0 never executed
call 1 never executed
call 2 never executed
-: 1762: break;
-: 1763: }
-: 1764: }
-: 1765:
-: 1766: /*
-: 1767: * There's a speculative insertion remaining, just clean in up, it
-: 1768: * can't have been successful, otherwise we'd gotten a confirmation
-: 1769: * record.
-: 1770: */
445: 1771: if (specinsert)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1772: {
#####: 1773: ReorderBufferReturnChange(rb, specinsert);
call 0 never executed
#####: 1774: specinsert = NULL;
-: 1775: }
-: 1776:
-: 1777: /* clean up the iterator */
445: 1778: ReorderBufferIterTXNFinish(rb, iterstate);
call 0 returned 100%
445: 1779: iterstate = NULL;
-: 1780:
-: 1781: /* call commit callback */
445: 1782: rb->commit(rb, txn, commit_lsn);
call 0 returned 100%
-: 1783:
-: 1784: /* this is just a sanity check against bad output plugin behaviour */
445: 1785: if (GetCurrentTransactionIdIfAny() != InvalidTransactionId)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 1786: elog(ERROR, "output plugin used XID %u",
call 0 never executed
call 1 never executed
call 2 never executed
call 3 never executed
-: 1787: GetCurrentTransactionId());
-: 1788:
-: 1789: /* cleanup */
445: 1790: TeardownHistoricSnapshot(false);
call 0 returned 100%
-: 1791:
-: 1792: /*
-: 1793: * Aborting the current (sub-)transaction as a whole has the right
-: 1794: * semantics. We want all locks acquired in here to be released, not
-: 1795: * reassigned to the parent and we do not want any database access
-: 1796: * have persistent effects.
-: 1797: */
445: 1798: AbortCurrentTransaction();
call 0 returned 100%
-: 1799:
-: 1800: /* make sure there's no cache pollution */
445: 1801: ReorderBufferExecuteInvalidations(rb, txn);
call 0 returned 100%
-: 1802:
445: 1803: if (using_subtxn)
branch 0 taken 73% (fallthrough)
branch 1 taken 27%
325: 1804: RollbackAndReleaseCurrentSubTransaction();
call 0 returned 100%
-: 1805:
445: 1806: if (snapshot_now->copied)
branch 0 taken 38% (fallthrough)
branch 1 taken 62%
167: 1807: ReorderBufferFreeSnap(rb, snapshot_now);
call 0 returned 100%
-: 1808:
-: 1809: /* remove potential on-disk data, and deallocate */
445: 1810: ReorderBufferCleanupTXN(rb, txn);
call 0 returned 100%
-: 1811: }
#####: 1812: PG_CATCH();
-: 1813: {
-: 1814: /* TODO: Encapsulate cleanup from the PG_TRY and PG_CATCH blocks */
#####: 1815: if (iterstate)
branch 0 never executed
branch 1 never executed
#####: 1816: ReorderBufferIterTXNFinish(rb, iterstate);
call 0 never executed
-: 1817:
#####: 1818: TeardownHistoricSnapshot(true);
call 0 never executed
-: 1819:
-: 1820: /*
-: 1821: * Force cache invalidation to happen outside of a valid transaction
-: 1822: * to prevent catalog access as we just caught an error.
-: 1823: */
#####: 1824: AbortCurrentTransaction();
call 0 never executed
-: 1825:
-: 1826: /* make sure there's no cache pollution */
#####: 1827: ReorderBufferExecuteInvalidations(rb, txn);
call 0 never executed
-: 1828:
#####: 1829: if (using_subtxn)
branch 0 never executed
branch 1 never executed
#####: 1830: RollbackAndReleaseCurrentSubTransaction();
call 0 never executed
-: 1831:
#####: 1832: if (snapshot_now->copied)
branch 0 never executed
branch 1 never executed
#####: 1833: ReorderBufferFreeSnap(rb, snapshot_now);
call 0 never executed
-: 1834:
-: 1835: /* remove potential on-disk data, and deallocate */
#####: 1836: ReorderBufferCleanupTXN(rb, txn);
call 0 never executed
-: 1837:
#####: 1838: PG_RE_THROW();
call 0 never executed
-: 1839: }
445: 1840: PG_END_TRY();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1841:}
-: 1842:
-: 1843:/*
-: 1844: * Abort a transaction that possibly has previous changes. Needs to be first
-: 1845: * called for subtransactions and then for the toplevel xid.
-: 1846: *
-: 1847: * NB: Transactions handled here have to have actively aborted (i.e. have
-: 1848: * produced an abort record). Implicitly aborted transactions are handled via
-: 1849: * ReorderBufferAbortOld(); transactions we're just not interested in, but
-: 1850: * which have committed are handled in ReorderBufferForget().
-: 1851: *
-: 1852: * This function purges this transaction and its contents from memory and
-: 1853: * disk.
-: 1854: */
-: 1855:void
function ReorderBufferAbort called 23 returned 100% blocks executed 80%
23: 1856:ReorderBufferAbort(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn)
-: 1857:{
-: 1858: ReorderBufferTXN *txn;
-: 1859:
23: 1860: txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
call 0 returned 100%
-: 1861: false);
-: 1862:
-: 1863: /* unknown, nothing to remove */
23: 1864: if (txn == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
23: 1865: return;
-: 1866:
-: 1867: /* cosmetic... */
23: 1868: txn->final_lsn = lsn;
-: 1869:
-: 1870: /* remove potential on-disk data, and deallocate */
23: 1871: ReorderBufferCleanupTXN(rb, txn);
call 0 returned 100%
-: 1872:}
-: 1873:
-: 1874:/*
-: 1875: * Abort all transactions that aren't actually running anymore because the
-: 1876: * server restarted.
-: 1877: *
-: 1878: * NB: These really have to be transactions that have aborted due to a server
-: 1879: * crash/immediate restart, as we don't deal with invalidations here.
-: 1880: */
-: 1881:void
function ReorderBufferAbortOld called 352 returned 100% blocks executed 79%
352: 1882:ReorderBufferAbortOld(ReorderBuffer *rb, TransactionId oldestRunningXid)
-: 1883:{
-: 1884: dlist_mutable_iter it;
-: 1885:
-: 1886: /*
-: 1887: * Iterate through all (potential) toplevel TXNs and abort all that are
-: 1888: * older than what possibly can be running. Once we've found the first
-: 1889: * that is alive we stop, there might be some that acquired an xid earlier
-: 1890: * but started writing later, but it's unlikely and they will be cleaned
-: 1891: * up in a later call to this function.
-: 1892: */
354: 1893: dlist_foreach_modify(it, &rb->toplevel_by_lsn)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 3%
branch 3 taken 97% (fallthrough)
-: 1894: {
-: 1895: ReorderBufferTXN *txn;
-: 1896:
11: 1897: txn = dlist_container(ReorderBufferTXN, node, it.cur);
-: 1898:
11: 1899: if (TransactionIdPrecedes(txn->xid, oldestRunningXid))
call 0 returned 100%
branch 1 taken 18% (fallthrough)
branch 2 taken 82%
-: 1900: {
-: 1901: /*
-: 1902: * We set final_lsn on a transaction when we decode its commit or
-: 1903: * abort record, but we never see those records for crashed
-: 1904: * transactions. To ensure cleanup of these transactions, set
-: 1905: * final_lsn to that of their last change; this causes
-: 1906: * ReorderBufferRestoreCleanup to do the right thing.
-: 1907: */
2: 1908: if (txn->serialized && txn->final_lsn == 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
branch 2 never executed
branch 3 never executed
-: 1909: {
#####: 1910: ReorderBufferChange *last =
call 0 never executed
#####: 1911: dlist_tail_element(ReorderBufferChange, node, &txn->changes);
-: 1912:
#####: 1913: txn->final_lsn = last->lsn;
-: 1914: }
-: 1915:
2: 1916: elog(DEBUG2, "aborting old transaction %u", txn->xid);
call 0 returned 100%
call 1 returned 100%
-: 1917:
-: 1918: /* remove potential on-disk data, and deallocate this tx */
2: 1919: ReorderBufferCleanupTXN(rb, txn);
call 0 returned 100%
-: 1920: }
-: 1921: else
18: 1922: return;
-: 1923: }
-: 1924:}
-: 1925:
-: 1926:/*
-: 1927: * Forget the contents of a transaction if we aren't interested in its
-: 1928: * contents. Needs to be first called for subtransactions and then for the
-: 1929: * toplevel xid.
-: 1930: *
-: 1931: * This is significantly different to ReorderBufferAbort() because
-: 1932: * transactions that have committed need to be treated differently from aborted
-: 1933: * ones since they may have modified the catalog.
-: 1934: *
-: 1935: * Note that this is only allowed to be called in the moment a transaction
-: 1936: * commit has just been read, not earlier; otherwise later records referring
-: 1937: * to this xid might re-create the transaction incompletely.
-: 1938: */
-: 1939:void
function ReorderBufferForget called 1519 returned 100% blocks executed 90%
1519: 1940:ReorderBufferForget(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn)
-: 1941:{
-: 1942: ReorderBufferTXN *txn;
-: 1943:
1519: 1944: txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
call 0 returned 100%
-: 1945: false);
-: 1946:
-: 1947: /* unknown, nothing to forget */
1519: 1948: if (txn == NULL)
branch 0 taken 7% (fallthrough)
branch 1 taken 93%
1619: 1949: return;
-: 1950:
-: 1951: /* cosmetic... */
1419: 1952: txn->final_lsn = lsn;
-: 1953:
-: 1954: /*
-: 1955: * Process cache invalidation messages if there are any. Even if we're not
-: 1956: * interested in the transaction's contents, it could have manipulated the
-: 1957: * catalog and we need to update the caches according to that.
-: 1958: */
1419: 1959: if (txn->base_snapshot != NULL && txn->ninvalidations > 0)
branch 0 taken 62% (fallthrough)
branch 1 taken 38%
branch 2 taken 32% (fallthrough)
branch 3 taken 68%
283: 1960: ReorderBufferImmediateInvalidation(rb, txn->ninvalidations,
call 0 returned 100%
-: 1961: txn->invalidations);
-: 1962: else
1136: 1963: Assert(txn->ninvalidations == 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1964:
-: 1965: /* remove potential on-disk data, and deallocate */
1419: 1966: ReorderBufferCleanupTXN(rb, txn);
call 0 returned 100%
-: 1967:}
-: 1968:
-: 1969:/*
-: 1970: * Execute invalidations happening outside the context of a decoded
-: 1971: * transaction. That currently happens either for xid-less commits
-: 1972: * (cf. RecordTransactionCommit()) or for invalidations in uninteresting
-: 1973: * transactions (via ReorderBufferForget()).
-: 1974: */
-: 1975:void
function ReorderBufferImmediateInvalidation called 283 returned 100% blocks executed 100%
283: 1976:ReorderBufferImmediateInvalidation(ReorderBuffer *rb, uint32 ninvalidations,
-: 1977: SharedInvalidationMessage *invalidations)
-: 1978:{
283: 1979: bool use_subtxn = IsTransactionOrTransactionBlock();
call 0 returned 100%
-: 1980: int i;
-: 1981:
283: 1982: if (use_subtxn)
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
268: 1983: BeginInternalSubTransaction("replay");
call 0 returned 100%
-: 1984:
-: 1985: /*
-: 1986: * Force invalidations to happen outside of a valid transaction - that way
-: 1987: * entries will just be marked as invalid without accessing the catalog.
-: 1988: * That's advantageous because we don't need to setup the full state
-: 1989: * necessary for catalog access.
-: 1990: */
283: 1991: if (use_subtxn)
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
268: 1992: AbortCurrentTransaction();
call 0 returned 100%
-: 1993:
13593: 1994: for (i = 0; i < ninvalidations; i++)
branch 0 taken 98%
branch 1 taken 2% (fallthrough)
13310: 1995: LocalExecuteInvalidationMessage(&invalidations[i]);
call 0 returned 100%
-: 1996:
283: 1997: if (use_subtxn)
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
268: 1998: RollbackAndReleaseCurrentSubTransaction();
call 0 returned 100%
283: 1999:}
-: 2000:
-: 2001:/*
-: 2002: * Tell reorderbuffer about an xid seen in the WAL stream. Has to be called at
-: 2003: * least once for every xid in XLogRecord->xl_xid (other places in records
-: 2004: * may, but do not have to be passed through here).
-: 2005: *
-: 2006: * Reorderbuffer keeps some datastructures about transactions in LSN order,
-: 2007: * for efficiency. To do that it has to know about when transactions are seen
-: 2008: * first in the WAL. As many types of records are not actually interesting for
-: 2009: * logical decoding, they do not necessarily pass though here.
-: 2010: */
-: 2011:void
function ReorderBufferProcessXid called 1593810 returned 100% blocks executed 100%
1593810: 2012:ReorderBufferProcessXid(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn)
-: 2013:{
-: 2014: /* many records won't have an xid assigned, centralize check here */
1593810: 2015: if (xid != InvalidTransactionId)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
1593129: 2016: ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
call 0 returned 100%
1593810: 2017:}
-: 2018:
-: 2019:/*
-: 2020: * Add a new snapshot to this transaction that may only used after lsn 'lsn'
-: 2021: * because the previous snapshot doesn't describe the catalog correctly for
-: 2022: * following rows.
-: 2023: */
-: 2024:void
function ReorderBufferAddSnapshot called 481 returned 100% blocks executed 100%
481: 2025:ReorderBufferAddSnapshot(ReorderBuffer *rb, TransactionId xid,
-: 2026: XLogRecPtr lsn, Snapshot snap)
-: 2027:{
481: 2028: ReorderBufferChange *change = ReorderBufferGetChange(rb);
call 0 returned 100%
-: 2029:
481: 2030: change->data.snapshot = snap;
481: 2031: change->action = REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT;
-: 2032:
481: 2033: ReorderBufferQueueChange(rb, xid, lsn, change);
call 0 returned 100%
481: 2034:}
-: 2035:
-: 2036:/*
-: 2037: * Set up the transaction's base snapshot.
-: 2038: *
-: 2039: * If we know that xid is a subtransaction, set the base snapshot on the
-: 2040: * top-level transaction instead.
-: 2041: */
-: 2042:void
function ReorderBufferSetBaseSnapshot called 1359 returned 100% blocks executed 80%
1359: 2043:ReorderBufferSetBaseSnapshot(ReorderBuffer *rb, TransactionId xid,
-: 2044: XLogRecPtr lsn, Snapshot snap)
-: 2045:{
-: 2046: ReorderBufferTXN *txn;
-: 2047: bool is_new;
-: 2048:
1359: 2049: AssertArg(snap != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2050:
-: 2051: /*
-: 2052: * Fetch the transaction to operate on. If we know it's a subtransaction,
-: 2053: * operate on its top-level transaction instead.
-: 2054: */
1359: 2055: txn = ReorderBufferTXNByXid(rb, xid, true, &is_new, lsn, true);
call 0 returned 100%
1359: 2056: if (txn->is_known_as_subxact)
branch 0 taken 7% (fallthrough)
branch 1 taken 93%
92: 2057: txn = ReorderBufferTXNByXid(rb, txn->toplevel_xid, false,
call 0 returned 100%
-: 2058: NULL, InvalidXLogRecPtr, false);
1359: 2059: Assert(txn->base_snapshot == NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2060:
1359: 2061: txn->base_snapshot = snap;
1359: 2062: txn->base_snapshot_lsn = lsn;
1359: 2063: dlist_push_tail(&rb->txns_by_base_snapshot_lsn, &txn->base_snapshot_node);
call 0 returned 100%
-: 2064:
1359: 2065: AssertTXNLsnOrder(rb);
call 0 returned 100%
1359: 2066:}
-: 2067:
-: 2068:/*
-: 2069: * Access the catalog with this CommandId at this point in the changestream.
-: 2070: *
-: 2071: * May only be called for command ids > 1
-: 2072: */
-: 2073:void
function ReorderBufferAddNewCommandId called 13630 returned 100% blocks executed 100%
13630: 2074:ReorderBufferAddNewCommandId(ReorderBuffer *rb, TransactionId xid,
-: 2075: XLogRecPtr lsn, CommandId cid)
-: 2076:{
13630: 2077: ReorderBufferChange *change = ReorderBufferGetChange(rb);
call 0 returned 100%
-: 2078:
13630: 2079: change->data.command_id = cid;
13630: 2080: change->action = REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID;
-: 2081:
13630: 2082: ReorderBufferQueueChange(rb, xid, lsn, change);
call 0 returned 100%
13630: 2083:}
-: 2084:
-: 2085:
-: 2086:/*
-: 2087: * Add new (relfilenode, tid) -> (cmin, cmax) mappings.
-: 2088: */
-: 2089:void
function ReorderBufferAddNewTupleCids called 13630 returned 100% blocks executed 100%
13630: 2090:ReorderBufferAddNewTupleCids(ReorderBuffer *rb, TransactionId xid,
-: 2091: XLogRecPtr lsn, RelFileNode node,
-: 2092: ItemPointerData tid, CommandId cmin,
-: 2093: CommandId cmax, CommandId combocid)
-: 2094:{
13630: 2095: ReorderBufferChange *change = ReorderBufferGetChange(rb);
call 0 returned 100%
-: 2096: ReorderBufferTXN *txn;
-: 2097:
13630: 2098: txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
call 0 returned 100%
-: 2099:
13630: 2100: change->data.tuplecid.node = node;
13630: 2101: change->data.tuplecid.tid = tid;
13630: 2102: change->data.tuplecid.cmin = cmin;
13630: 2103: change->data.tuplecid.cmax = cmax;
13630: 2104: change->data.tuplecid.combocid = combocid;
13630: 2105: change->lsn = lsn;
13630: 2106: change->action = REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID;
-: 2107:
13630: 2108: dlist_push_tail(&txn->tuplecids, &change->node);
call 0 returned 100%
13630: 2109: txn->ntuplecids++;
13630: 2110:}
-: 2111:
-: 2112:/*
-: 2113: * Setup the invalidation of the toplevel transaction.
-: 2114: *
-: 2115: * This needs to be done before ReorderBufferCommit is called!
-: 2116: */
-: 2117:void
function ReorderBufferAddInvalidations called 451 returned 100% blocks executed 56%
451: 2118:ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid,
-: 2119: XLogRecPtr lsn, Size nmsgs,
-: 2120: SharedInvalidationMessage *msgs)
-: 2121:{
-: 2122: ReorderBufferTXN *txn;
-: 2123:
451: 2124: txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
call 0 returned 100%
-: 2125:
451: 2126: if (txn->ninvalidations != 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2127: elog(ERROR, "only ever add one set of invalidations");
call 0 never executed
call 1 never executed
call 2 never executed
-: 2128:
451: 2129: Assert(nmsgs > 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2130:
451: 2131: txn->ninvalidations = nmsgs;
451: 2132: txn->invalidations = (SharedInvalidationMessage *)
451: 2133: MemoryContextAlloc(rb->context,
call 0 returned 100%
-: 2134: sizeof(SharedInvalidationMessage) * nmsgs);
451: 2135: memcpy(txn->invalidations, msgs,
-: 2136: sizeof(SharedInvalidationMessage) * nmsgs);
451: 2137:}
-: 2138:
-: 2139:/*
-: 2140: * Apply all invalidations we know. Possibly we only need parts at this point
-: 2141: * in the changestream but we don't know which those are.
-: 2142: */
-: 2143:static void
function ReorderBufferExecuteInvalidations called 1230 returned 100% blocks executed 100%
1230: 2144:ReorderBufferExecuteInvalidations(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 2145:{
-: 2146: int i;
-: 2147:
107454: 2148: for (i = 0; i < txn->ninvalidations; i++)
branch 0 taken 99%
branch 1 taken 1% (fallthrough)
106224: 2149: LocalExecuteInvalidationMessage(&txn->invalidations[i]);
call 0 returned 100%
1230: 2150:}
-: 2151:
-: 2152:/*
-: 2153: * Mark a transaction as containing catalog changes
-: 2154: */
-: 2155:void
function ReorderBufferXidSetCatalogChanges called 14681 returned 100% blocks executed 100%
14681: 2156:ReorderBufferXidSetCatalogChanges(ReorderBuffer *rb, TransactionId xid,
-: 2157: XLogRecPtr lsn)
-: 2158:{
-: 2159: ReorderBufferTXN *txn;
-: 2160:
14681: 2161: txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
call 0 returned 100%
-: 2162:
14681: 2163: txn->has_catalog_changes = true;
14681: 2164:}
-: 2165:
-: 2166:/*
-: 2167: * Query whether a transaction is already *known* to contain catalog
-: 2168: * changes. This can be wrong until directly before the commit!
-: 2169: */
-: 2170:bool
function ReorderBufferXidHasCatalogChanges called 2080 returned 100% blocks executed 100%
2080: 2171:ReorderBufferXidHasCatalogChanges(ReorderBuffer *rb, TransactionId xid)
-: 2172:{
-: 2173: ReorderBufferTXN *txn;
-: 2174:
2080: 2175: txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
call 0 returned 100%
-: 2176: false);
2080: 2177: if (txn == NULL)
branch 0 taken 6% (fallthrough)
branch 1 taken 94%
116: 2178: return false;
-: 2179:
1964: 2180: return txn->has_catalog_changes;
-: 2181:}
-: 2182:
-: 2183:/*
-: 2184: * ReorderBufferXidHasBaseSnapshot
-: 2185: * Have we already set the base snapshot for the given txn/subtxn?
-: 2186: */
-: 2187:bool
function ReorderBufferXidHasBaseSnapshot called 1192910 returned 100% blocks executed 86%
1192910: 2188:ReorderBufferXidHasBaseSnapshot(ReorderBuffer *rb, TransactionId xid)
-: 2189:{
-: 2190: ReorderBufferTXN *txn;
-: 2191:
1192910: 2192: txn = ReorderBufferTXNByXid(rb, xid, false,
call 0 returned 100%
-: 2193: NULL, InvalidXLogRecPtr, false);
-: 2194:
-: 2195: /* transaction isn't known yet, ergo no snapshot */
1192910: 2196: if (txn == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2197: return false;
-: 2198:
-: 2199: /* a known subtxn? operate on top-level txn instead */
1192910: 2200: if (txn->is_known_as_subxact)
branch 0 taken 28% (fallthrough)
branch 1 taken 72%
335521: 2201: txn = ReorderBufferTXNByXid(rb, txn->toplevel_xid, false,
call 0 returned 100%
-: 2202: NULL, InvalidXLogRecPtr, false);
-: 2203:
1192910: 2204: return txn->base_snapshot != NULL;
-: 2205:}
-: 2206:
-: 2207:
-: 2208:/*
-: 2209: * ---------------------------------------
-: 2210: * Disk serialization support
-: 2211: * ---------------------------------------
-: 2212: */
-: 2213:
-: 2214:/*
-: 2215: * Ensure the IO buffer is >= sz.
-: 2216: */
-: 2217:static void
function ReorderBufferSerializeReserve called 2357676 returned 100% blocks executed 100%
2357676: 2218:ReorderBufferSerializeReserve(ReorderBuffer *rb, Size sz)
-: 2219:{
2357676: 2220: if (!rb->outbufsize)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 2221: {
27: 2222: rb->outbuf = MemoryContextAlloc(rb->context, sz);
call 0 returned 100%
27: 2223: rb->outbufsize = sz;
-: 2224: }
2357649: 2225: else if (rb->outbufsize < sz)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 2226: {
247: 2227: rb->outbuf = repalloc(rb->outbuf, sz);
call 0 returned 100%
247: 2228: rb->outbufsize = sz;
-: 2229: }
2357676: 2230:}
-: 2231:
-: 2232:/*
-: 2233: * Check whether the transaction tx should spill its data to disk.
-: 2234: */
-: 2235:static void
function ReorderBufferCheckSerializeTXN called 1197196 returned 100% blocks executed 80%
1197196: 2236:ReorderBufferCheckSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 2237:{
-: 2238: /*
-: 2239: * TODO: improve accounting so we cheaply can take subtransactions into
-: 2240: * account here.
-: 2241: */
1197196: 2242: if (txn->nentries_mem >= max_changes_in_memory)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 2243: {
243: 2244: ReorderBufferSerializeTXN(rb, txn);
call 0 returned 100%
243: 2245: Assert(txn->nentries_mem == 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2246: }
1197196: 2247:}
-: 2248:
-: 2249:/*
-: 2250: * Spill data of a large transaction (and its subtransactions) to disk.
-: 2251: */
-: 2252:static void
function ReorderBufferSerializeTXN called 294 returned 100% blocks executed 71%
294: 2253:ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 2254:{
-: 2255: dlist_iter subtxn_i;
-: 2256: dlist_mutable_iter change_i;
294: 2257: int fd = -1;
294: 2258: XLogSegNo curOpenSegNo = 0;
294: 2259: Size spilled = 0;
-: 2260:
294: 2261: elog(DEBUG2, "spill %u changes in XID %u to disk",
call 0 returned 100%
call 1 returned 100%
-: 2262: (uint32) txn->nentries_mem, txn->xid);
-: 2263:
-: 2264: /* do the same to all child TXs */
320: 2265: dlist_foreach(subtxn_i, &txn->subtxns)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 8%
branch 3 taken 92% (fallthrough)
-: 2266: {
-: 2267: ReorderBufferTXN *subtxn;
-: 2268:
26: 2269: subtxn = dlist_container(ReorderBufferTXN, node, subtxn_i.cur);
26: 2270: ReorderBufferSerializeTXN(rb, subtxn);
call 0 returned 100%
-: 2271: }
-: 2272:
-: 2273: /* serialize changestream */
1037680: 2274: dlist_foreach_modify(change_i, &txn->changes)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 99%
branch 3 taken 1% (fallthrough)
-: 2275: {
-: 2276: ReorderBufferChange *change;
-: 2277:
1037386: 2278: change = dlist_container(ReorderBufferChange, node, change_i.cur);
-: 2279:
-: 2280: /*
-: 2281: * store in segment in which it belongs by start lsn, don't split over
-: 2282: * multiple segments tho
-: 2283: */
2074484: 2284: if (fd == -1 ||
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
1037098: 2285: !XLByteInSeg(change->lsn, curOpenSegNo, wal_segment_size))
-: 2286: {
-: 2287: char path[MAXPGPATH];
-: 2288:
288: 2289: if (fd != -1)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2290: CloseTransientFile(fd);
call 0 never executed
-: 2291:
288: 2292: XLByteToSeg(change->lsn, curOpenSegNo, wal_segment_size);
-: 2293:
-: 2294: /*
-: 2295: * No need to care about TLIs here, only used during a single run,
-: 2296: * so each LSN only maps to a specific WAL record.
-: 2297: */
288: 2298: ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid,
call 0 returned 100%
-: 2299: curOpenSegNo);
-: 2300:
-: 2301: /* open segment, create it if necessary */
288: 2302: fd = OpenTransientFile(path,
call 0 returned 100%
-: 2303: O_CREAT | O_WRONLY | O_APPEND | PG_BINARY);
-: 2304:
288: 2305: if (fd < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2306: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2307: (errcode_for_file_access(),
-: 2308: errmsg("could not open file \"%s\": %m", path)));
-: 2309: }
-: 2310:
1037386: 2311: ReorderBufferSerializeChange(rb, txn, fd, change);
call 0 returned 100%
1037386: 2312: dlist_delete(&change->node);
call 0 returned 100%
1037386: 2313: ReorderBufferReturnChange(rb, change);
call 0 returned 100%
-: 2314:
1037386: 2315: spilled++;
-: 2316: }
-: 2317:
294: 2318: Assert(spilled == txn->nentries_mem);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
294: 2319: Assert(dlist_is_empty(&txn->changes));
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
294: 2320: txn->nentries_mem = 0;
294: 2321: txn->serialized = true;
-: 2322:
294: 2323: if (fd != -1)
branch 0 taken 98% (fallthrough)
branch 1 taken 2%
288: 2324: CloseTransientFile(fd);
call 0 returned 100%
294: 2325:}
-: 2326:
-: 2327:/*
-: 2328: * Serialize individual change to disk.
-: 2329: */
-: 2330:static void
function ReorderBufferSerializeChange called 1037386 returned 100% blocks executed 62%
1037386: 2331:ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 2332: int fd, ReorderBufferChange *change)
-: 2333:{
-: 2334: ReorderBufferDiskChange *ondisk;
1037386: 2335: Size sz = sizeof(ReorderBufferDiskChange);
-: 2336:
1037386: 2337: ReorderBufferSerializeReserve(rb, sz);
call 0 returned 100%
-: 2338:
1037386: 2339: ondisk = (ReorderBufferDiskChange *) rb->outbuf;
1037386: 2340: memcpy(&ondisk->change, change, sizeof(ReorderBufferChange));
-: 2341:
1037386: 2342: switch (change->action)
branch 0 taken 99%
branch 1 taken 1%
branch 2 taken 1%
branch 3 taken 0%
branch 4 taken 1%
branch 5 taken 0%
-: 2343: {
-: 2344: /* fall through these, they're all similar enough */
-: 2345: case REORDER_BUFFER_CHANGE_INSERT:
-: 2346: case REORDER_BUFFER_CHANGE_UPDATE:
-: 2347: case REORDER_BUFFER_CHANGE_DELETE:
-: 2348: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
-: 2349: {
-: 2350: char *data;
-: 2351: ReorderBufferTupleBuf *oldtup,
-: 2352: *newtup;
1026535: 2353: Size oldlen = 0;
1026535: 2354: Size newlen = 0;
-: 2355:
1026535: 2356: oldtup = change->data.tp.oldtuple;
1026535: 2357: newtup = change->data.tp.newtuple;
-: 2358:
1026535: 2359: if (oldtup)
branch 0 taken 6% (fallthrough)
branch 1 taken 94%
-: 2360: {
60023: 2361: sz += sizeof(HeapTupleData);
60023: 2362: oldlen = oldtup->tuple.t_len;
60023: 2363: sz += oldlen;
-: 2364: }
-: 2365:
1026535: 2366: if (newtup)
branch 0 taken 91% (fallthrough)
branch 1 taken 9%
-: 2367: {
929544: 2368: sz += sizeof(HeapTupleData);
929544: 2369: newlen = newtup->tuple.t_len;
929544: 2370: sz += newlen;
-: 2371: }
-: 2372:
-: 2373: /* make sure we have enough space */
1026535: 2374: ReorderBufferSerializeReserve(rb, sz);
call 0 returned 100%
-: 2375:
1026535: 2376: data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
-: 2377: /* might have been reallocated above */
1026535: 2378: ondisk = (ReorderBufferDiskChange *) rb->outbuf;
-: 2379:
1026535: 2380: if (oldlen)
branch 0 taken 6% (fallthrough)
branch 1 taken 94%
-: 2381: {
60023: 2382: memcpy(data, &oldtup->tuple, sizeof(HeapTupleData));
60023: 2383: data += sizeof(HeapTupleData);
-: 2384:
60023: 2385: memcpy(data, oldtup->tuple.t_data, oldlen);
60023: 2386: data += oldlen;
-: 2387: }
-: 2388:
1026535: 2389: if (newlen)
branch 0 taken 91% (fallthrough)
branch 1 taken 9%
-: 2390: {
929544: 2391: memcpy(data, &newtup->tuple, sizeof(HeapTupleData));
929544: 2392: data += sizeof(HeapTupleData);
-: 2393:
929544: 2394: memcpy(data, newtup->tuple.t_data, newlen);
929544: 2395: data += newlen;
-: 2396: }
1026535: 2397: break;
-: 2398: }
-: 2399: case REORDER_BUFFER_CHANGE_MESSAGE:
-: 2400: {
-: 2401: char *data;
12: 2402: Size prefix_size = strlen(change->data.msg.prefix) + 1;
-: 2403:
12: 2404: sz += prefix_size + change->data.msg.message_size +
-: 2405: sizeof(Size) + sizeof(Size);
12: 2406: ReorderBufferSerializeReserve(rb, sz);
call 0 returned 100%
-: 2407:
12: 2408: data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
-: 2409:
-: 2410: /* might have been reallocated above */
12: 2411: ondisk = (ReorderBufferDiskChange *) rb->outbuf;
-: 2412:
-: 2413: /* write the prefix including the size */
12: 2414: memcpy(data, &prefix_size, sizeof(Size));
12: 2415: data += sizeof(Size);
12: 2416: memcpy(data, change->data.msg.prefix,
-: 2417: prefix_size);
12: 2418: data += prefix_size;
-: 2419:
-: 2420: /* write the message including the size */
12: 2421: memcpy(data, &change->data.msg.message_size, sizeof(Size));
12: 2422: data += sizeof(Size);
12: 2423: memcpy(data, change->data.msg.message,
-: 2424: change->data.msg.message_size);
12: 2425: data += change->data.msg.message_size;
-: 2426:
12: 2427: break;
-: 2428: }
-: 2429: case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
-: 2430: {
-: 2431: Snapshot snap;
-: 2432: char *data;
-: 2433:
2: 2434: snap = change->data.snapshot;
-: 2435:
2: 2436: sz += sizeof(SnapshotData) +
4: 2437: sizeof(TransactionId) * snap->xcnt +
2: 2438: sizeof(TransactionId) * snap->subxcnt
-: 2439: ;
-: 2440:
-: 2441: /* make sure we have enough space */
2: 2442: ReorderBufferSerializeReserve(rb, sz);
call 0 returned 100%
2: 2443: data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
-: 2444: /* might have been reallocated above */
2: 2445: ondisk = (ReorderBufferDiskChange *) rb->outbuf;
-: 2446:
2: 2447: memcpy(data, snap, sizeof(SnapshotData));
2: 2448: data += sizeof(SnapshotData);
-: 2449:
2: 2450: if (snap->xcnt)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 2451: {
2: 2452: memcpy(data, snap->xip,
2: 2453: sizeof(TransactionId) * snap->xcnt);
2: 2454: data += sizeof(TransactionId) * snap->xcnt;
-: 2455: }
-: 2456:
2: 2457: if (snap->subxcnt)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 2458: {
#####: 2459: memcpy(data, snap->subxip,
#####: 2460: sizeof(TransactionId) * snap->subxcnt);
#####: 2461: data += sizeof(TransactionId) * snap->subxcnt;
-: 2462: }
2: 2463: break;
-: 2464: }
-: 2465: case REORDER_BUFFER_CHANGE_TRUNCATE:
-: 2466: {
-: 2467: Size size;
-: 2468: char *data;
-: 2469:
-: 2470: /* account for the OIDs of truncated relations */
#####: 2471: size = sizeof(Oid) * change->data.truncate.nrelids;
#####: 2472: sz += size;
-: 2473:
-: 2474: /* make sure we have enough space */
#####: 2475: ReorderBufferSerializeReserve(rb, sz);
call 0 never executed
-: 2476:
#####: 2477: data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
-: 2478: /* might have been reallocated above */
#####: 2479: ondisk = (ReorderBufferDiskChange *) rb->outbuf;
-: 2480:
#####: 2481: memcpy(data, change->data.truncate.relids, size);
#####: 2482: data += size;
-: 2483:
#####: 2484: break;
-: 2485: }
-: 2486: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
-: 2487: case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
-: 2488: case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
-: 2489: /* ReorderBufferChange contains everything important */
10837: 2490: break;
-: 2491: }
-: 2492:
1037386: 2493: ondisk->size = sz;
-: 2494:
1037386: 2495: errno = 0;
call 0 returned 100%
1037386: 2496: pgstat_report_wait_start(WAIT_EVENT_REORDER_BUFFER_WRITE);
call 0 returned 100%
1037386: 2497: if (write(fd, rb->outbuf, ondisk->size) != ondisk->size)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 2498: {
#####: 2499: int save_errno = errno;
call 0 never executed
-: 2500:
#####: 2501: CloseTransientFile(fd);
call 0 never executed
-: 2502:
-: 2503: /* if write didn't set errno, assume problem is no disk space */
#####: 2504: errno = save_errno ? save_errno : ENOSPC;
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 2505: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2506: (errcode_for_file_access(),
-: 2507: errmsg("could not write to data file for XID %u: %m",
-: 2508: txn->xid)));
-: 2509: }
1037386: 2510: pgstat_report_wait_end();
call 0 returned 100%
-: 2511:
1037386: 2512: Assert(ondisk->change.action == change->action);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1037386: 2513:}
-: 2514:
-: 2515:/*
-: 2516: * Restore a number of changes spilled to disk back into memory.
-: 2517: */
-: 2518:static Size
function ReorderBufferRestoreChanges called 79 returned 100% blocks executed 48%
79: 2519:ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 2520: int *fd, XLogSegNo *segno)
-: 2521:{
79: 2522: Size restored = 0;
-: 2523: XLogSegNo last_segno;
-: 2524: dlist_mutable_iter cleanup_iter;
-: 2525:
79: 2526: Assert(txn->first_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
79: 2527: Assert(txn->final_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2528:
-: 2529: /* free current entries, so we have memory for more */
145066: 2530: dlist_foreach_modify(cleanup_iter, &txn->changes)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 99%
branch 3 taken 1% (fallthrough)
-: 2531: {
144987: 2532: ReorderBufferChange *cleanup =
144987: 2533: dlist_container(ReorderBufferChange, node, cleanup_iter.cur);
-: 2534:
144987: 2535: dlist_delete(&cleanup->node);
call 0 returned 100%
144987: 2536: ReorderBufferReturnChange(rb, cleanup);
call 0 returned 100%
-: 2537: }
79: 2538: txn->nentries_mem = 0;
79: 2539: Assert(dlist_is_empty(&txn->changes));
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 2540:
79: 2541: XLByteToSeg(txn->final_lsn, last_segno, wal_segment_size);
-: 2542:
147041: 2543: while (restored < max_changes_in_memory && *segno <= last_segno)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 99%
branch 3 taken 1% (fallthrough)
-: 2544: {
-: 2545: int readBytes;
-: 2546: ReorderBufferDiskChange *ondisk;
-: 2547:
146883: 2548: if (*fd == -1)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 2549: {
-: 2550: char path[MAXPGPATH];
-: 2551:
-: 2552: /* first time in */
25: 2553: if (*segno == 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
25: 2554: XLByteToSeg(txn->first_lsn, *segno, wal_segment_size);
-: 2555:
25: 2556: Assert(*segno != 0 || dlist_is_empty(&txn->changes));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
branch 3 never executed
branch 4 never executed
call 5 never executed
-: 2557:
-: 2558: /*
-: 2559: * No need to care about TLIs here, only used during a single run,
-: 2560: * so each LSN only maps to a specific WAL record.
-: 2561: */
25: 2562: ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid,
call 0 returned 100%
-: 2563: *segno);
-: 2564:
25: 2565: *fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
call 0 returned 100%
25: 2566: if (*fd < 0 && errno == ENOENT)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
branch 3 never executed
branch 4 never executed
-: 2567: {
#####: 2568: *fd = -1;
#####: 2569: (*segno)++;
#####: 2570: continue;
-: 2571: }
25: 2572: else if (*fd < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2573: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2574: (errcode_for_file_access(),
-: 2575: errmsg("could not open file \"%s\": %m",
-: 2576: path)));
-: 2577: }
-: 2578:
-: 2579: /*
-: 2580: * Read the statically sized part of a change which has information
-: 2581: * about the total size. If we couldn't read a record, we're at the
-: 2582: * end of this file.
-: 2583: */
146883: 2584: ReorderBufferSerializeReserve(rb, sizeof(ReorderBufferDiskChange));
call 0 returned 100%
146883: 2585: pgstat_report_wait_start(WAIT_EVENT_REORDER_BUFFER_READ);
call 0 returned 100%
146883: 2586: readBytes = read(*fd, rb->outbuf, sizeof(ReorderBufferDiskChange));
call 0 returned 100%
146883: 2587: pgstat_report_wait_end();
call 0 returned 100%
-: 2588:
-: 2589: /* eof */
146883: 2590: if (readBytes == 0)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 2591: {
25: 2592: CloseTransientFile(*fd);
call 0 returned 100%
25: 2593: *fd = -1;
25: 2594: (*segno)++;
25: 2595: continue;
-: 2596: }
146858: 2597: else if (readBytes < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2598: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2599: (errcode_for_file_access(),
-: 2600: errmsg("could not read from reorderbuffer spill file: %m")));
146858: 2601: else if (readBytes != sizeof(ReorderBufferDiskChange))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2602: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2603: (errcode_for_file_access(),
-: 2604: errmsg("could not read from reorderbuffer spill file: read %d instead of %u bytes",
-: 2605: readBytes,
-: 2606: (uint32) sizeof(ReorderBufferDiskChange))));
-: 2607:
146858: 2608: ondisk = (ReorderBufferDiskChange *) rb->outbuf;
-: 2609:
146858: 2610: ReorderBufferSerializeReserve(rb,
call 0 returned 100%
146858: 2611: sizeof(ReorderBufferDiskChange) + ondisk->size);
146858: 2612: ondisk = (ReorderBufferDiskChange *) rb->outbuf;
-: 2613:
146858: 2614: pgstat_report_wait_start(WAIT_EVENT_REORDER_BUFFER_READ);
call 0 returned 100%
146858: 2615: readBytes = read(*fd, rb->outbuf + sizeof(ReorderBufferDiskChange),
call 0 returned 100%
146858: 2616: ondisk->size - sizeof(ReorderBufferDiskChange));
146858: 2617: pgstat_report_wait_end();
call 0 returned 100%
-: 2618:
146858: 2619: if (readBytes < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2620: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2621: (errcode_for_file_access(),
-: 2622: errmsg("could not read from reorderbuffer spill file: %m")));
146858: 2623: else if (readBytes != ondisk->size - sizeof(ReorderBufferDiskChange))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2624: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2625: (errcode_for_file_access(),
-: 2626: errmsg("could not read from reorderbuffer spill file: read %d instead of %u bytes",
-: 2627: readBytes,
-: 2628: (uint32) (ondisk->size - sizeof(ReorderBufferDiskChange)))));
-: 2629:
-: 2630: /*
-: 2631: * ok, read a full change from disk, now restore it into proper
-: 2632: * in-memory format
-: 2633: */
146858: 2634: ReorderBufferRestoreChange(rb, txn, rb->outbuf);
call 0 returned 100%
146858: 2635: restored++;
-: 2636: }
-: 2637:
79: 2638: return restored;
-: 2639:}
-: 2640:
-: 2641:/*
-: 2642: * Convert change from its on-disk format to in-memory format and queue it onto
-: 2643: * the TXN's ->changes list.
-: 2644: *
-: 2645: * Note: although "data" is declared char*, at entry it points to a
-: 2646: * maxalign'd buffer, making it safe in most of this function to assume
-: 2647: * that the pointed-to data is suitably aligned for direct access.
-: 2648: */
-: 2649:static void
function ReorderBufferRestoreChange called 146858 returned 100% blocks executed 86%
146858: 2650:ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 2651: char *data)
-: 2652:{
-: 2653: ReorderBufferDiskChange *ondisk;
-: 2654: ReorderBufferChange *change;
-: 2655:
146858: 2656: ondisk = (ReorderBufferDiskChange *) data;
-: 2657:
146858: 2658: change = ReorderBufferGetChange(rb);
call 0 returned 100%
-: 2659:
-: 2660: /* copy static part */
146858: 2661: memcpy(change, &ondisk->change, sizeof(ReorderBufferChange));
-: 2662:
146858: 2663: data += sizeof(ReorderBufferDiskChange);
-: 2664:
-: 2665: /* restore individual stuff */
146858: 2666: switch (change->action)
branch 0 taken 99%
branch 1 taken 1%
branch 2 taken 1%
branch 3 taken 0%
branch 4 taken 1%
branch 5 taken 0%
-: 2667: {
-: 2668: /* fall through these, they're all similar enough */
-: 2669: case REORDER_BUFFER_CHANGE_INSERT:
-: 2670: case REORDER_BUFFER_CHANGE_UPDATE:
-: 2671: case REORDER_BUFFER_CHANGE_DELETE:
-: 2672: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
144980: 2673: if (change->data.tp.oldtuple)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
-: 2674: {
5004: 2675: uint32 tuplelen = ((HeapTuple) data)->t_len;
-: 2676:
5004: 2677: change->data.tp.oldtuple =
5004: 2678: ReorderBufferGetTupleBuf(rb, tuplelen - SizeofHeapTupleHeader);
call 0 returned 100%
-: 2679:
-: 2680: /* restore ->tuple */
5004: 2681: memcpy(&change->data.tp.oldtuple->tuple, data,
-: 2682: sizeof(HeapTupleData));
5004: 2683: data += sizeof(HeapTupleData);
-: 2684:
-: 2685: /* reset t_data pointer into the new tuplebuf */
10008: 2686: change->data.tp.oldtuple->tuple.t_data =
5004: 2687: ReorderBufferTupleBufData(change->data.tp.oldtuple);
-: 2688:
-: 2689: /* restore tuple data itself */
5004: 2690: memcpy(change->data.tp.oldtuple->tuple.t_data, data, tuplelen);
5004: 2691: data += tuplelen;
-: 2692: }
-: 2693:
144980: 2694: if (change->data.tp.newtuple)
branch 0 taken 93% (fallthrough)
branch 1 taken 7%
-: 2695: {
-: 2696: /* here, data might not be suitably aligned! */
-: 2697: uint32 tuplelen;
-: 2698:
134760: 2699: memcpy(&tuplelen, data + offsetof(HeapTupleData, t_len),
-: 2700: sizeof(uint32));
-: 2701:
134760: 2702: change->data.tp.newtuple =
134760: 2703: ReorderBufferGetTupleBuf(rb, tuplelen - SizeofHeapTupleHeader);
call 0 returned 100%
-: 2704:
-: 2705: /* restore ->tuple */
134760: 2706: memcpy(&change->data.tp.newtuple->tuple, data,
-: 2707: sizeof(HeapTupleData));
134760: 2708: data += sizeof(HeapTupleData);
-: 2709:
-: 2710: /* reset t_data pointer into the new tuplebuf */
269520: 2711: change->data.tp.newtuple->tuple.t_data =
134760: 2712: ReorderBufferTupleBufData(change->data.tp.newtuple);
-: 2713:
-: 2714: /* restore tuple data itself */
134760: 2715: memcpy(change->data.tp.newtuple->tuple.t_data, data, tuplelen);
134760: 2716: data += tuplelen;
-: 2717: }
-: 2718:
144980: 2719: break;
-: 2720: case REORDER_BUFFER_CHANGE_MESSAGE:
-: 2721: {
-: 2722: Size prefix_size;
-: 2723:
-: 2724: /* read prefix */
1: 2725: memcpy(&prefix_size, data, sizeof(Size));
1: 2726: data += sizeof(Size);
1: 2727: change->data.msg.prefix = MemoryContextAlloc(rb->context,
call 0 returned 100%
-: 2728: prefix_size);
1: 2729: memcpy(change->data.msg.prefix, data, prefix_size);
1: 2730: Assert(change->data.msg.prefix[prefix_size - 1] == '\0');
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1: 2731: data += prefix_size;
-: 2732:
-: 2733: /* read the message */
1: 2734: memcpy(&change->data.msg.message_size, data, sizeof(Size));
1: 2735: data += sizeof(Size);
1: 2736: change->data.msg.message = MemoryContextAlloc(rb->context,
call 0 returned 100%
-: 2737: change->data.msg.message_size);
1: 2738: memcpy(change->data.msg.message, data,
-: 2739: change->data.msg.message_size);
1: 2740: data += change->data.msg.message_size;
-: 2741:
1: 2742: break;
-: 2743: }
-: 2744: case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
-: 2745: {
-: 2746: Snapshot oldsnap;
-: 2747: Snapshot newsnap;
-: 2748: Size size;
-: 2749:
2: 2750: oldsnap = (Snapshot) data;
-: 2751:
2: 2752: size = sizeof(SnapshotData) +
4: 2753: sizeof(TransactionId) * oldsnap->xcnt +
2: 2754: sizeof(TransactionId) * (oldsnap->subxcnt + 0);
-: 2755:
2: 2756: change->data.snapshot = MemoryContextAllocZero(rb->context, size);
call 0 returned 100%
-: 2757:
2: 2758: newsnap = change->data.snapshot;
-: 2759:
2: 2760: memcpy(newsnap, data, size);
2: 2761: newsnap->xip = (TransactionId *)
-: 2762: (((char *) newsnap) + sizeof(SnapshotData));
2: 2763: newsnap->subxip = newsnap->xip + newsnap->xcnt;
2: 2764: newsnap->copied = true;
2: 2765: break;
-: 2766: }
-: 2767: /* the base struct contains all the data, easy peasy */
-: 2768: case REORDER_BUFFER_CHANGE_TRUNCATE:
-: 2769: {
-: 2770: Oid *relids;
-: 2771:
#####: 2772: relids = ReorderBufferGetRelids(rb,
call 0 never executed
#####: 2773: change->data.truncate.nrelids);
#####: 2774: memcpy(relids, data, change->data.truncate.nrelids * sizeof(Oid));
#####: 2775: change->data.truncate.relids = relids;
-: 2776:
#####: 2777: break;
-: 2778: }
-: 2779: case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
-: 2780: case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
-: 2781: case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
1875: 2782: break;
-: 2783: }
-: 2784:
146858: 2785: dlist_push_tail(&txn->changes, &change->node);
call 0 returned 100%
146858: 2786: txn->nentries_mem++;
146858: 2787:}
-: 2788:
-: 2789:/*
-: 2790: * Remove all on-disk stored for the passed in transaction.
-: 2791: */
-: 2792:static void
function ReorderBufferRestoreCleanup called 184 returned 100% blocks executed 45%
184: 2793:ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 2794:{
-: 2795: XLogSegNo first;
-: 2796: XLogSegNo cur;
-: 2797: XLogSegNo last;
-: 2798:
184: 2799: Assert(txn->first_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
184: 2800: Assert(txn->final_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2801:
184: 2802: XLByteToSeg(txn->first_lsn, first, wal_segment_size);
184: 2803: XLByteToSeg(txn->final_lsn, last, wal_segment_size);
-: 2804:
-: 2805: /* iterate over all possible filenames, and delete them */
368: 2806: for (cur = first; cur <= last; cur++)
branch 0 taken 50%
branch 1 taken 50% (fallthrough)
-: 2807: {
-: 2808: char path[MAXPGPATH];
-: 2809:
184: 2810: ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid, cur);
call 0 returned 100%
184: 2811: if (unlink(path) != 0 && errno != ENOENT)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
branch 4 never executed
branch 5 never executed
#####: 2812: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2813: (errcode_for_file_access(),
-: 2814: errmsg("could not remove file \"%s\": %m", path)));
-: 2815: }
184: 2816:}
-: 2817:
-: 2818:/*
-: 2819: * Remove any leftover serialized reorder buffers from a slot directory after a
-: 2820: * prior crash or decoding session exit.
-: 2821: */
-: 2822:static void
function ReorderBufferCleanupSerializedTXNs called 537 returned 100% blocks executed 52%
537: 2823:ReorderBufferCleanupSerializedTXNs(const char *slotname)
-: 2824:{
-: 2825: DIR *spill_dir;
-: 2826: struct dirent *spill_de;
-: 2827: struct stat statbuf;
-: 2828: char path[MAXPGPATH * 2 + 12];
-: 2829:
537: 2830: sprintf(path, "pg_replslot/%s", slotname);
call 0 returned 100%
-: 2831:
-: 2832: /* we're only handling directories here, skip if it's not ours */
537: 2833: if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
branch 3 taken 0% (fallthrough)
branch 4 taken 100%
537: 2834: return;
-: 2835:
537: 2836: spill_dir = AllocateDir(path);
call 0 returned 100%
537: 2837: while ((spill_de = ReadDirExtended(spill_dir, path, INFO)) != NULL)
call 0 returned 100%
branch 1 taken 75%
branch 2 taken 25% (fallthrough)
-: 2838: {
-: 2839: /* only look at names that can be ours */
1611: 2840: if (strncmp(spill_de->d_name, "xid", 3) == 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 2841: {
#####: 2842: snprintf(path, sizeof(path),
call 0 never executed
-: 2843: "pg_replslot/%s/%s", slotname,
#####: 2844: spill_de->d_name);
-: 2845:
#####: 2846: if (unlink(path) != 0)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 2847: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2848: (errcode_for_file_access(),
-: 2849: errmsg("could not remove file \"%s\" during removal of pg_replslot/%s/xid*: %m",
-: 2850: path, slotname)));
-: 2851: }
-: 2852: }
537: 2853: FreeDir(spill_dir);
call 0 returned 100%
-: 2854:}
-: 2855:
-: 2856:/*
-: 2857: * Given a replication slot, transaction ID and segment number, fill in the
-: 2858: * corresponding spill file into 'path', which is a caller-owned buffer of size
-: 2859: * at least MAXPGPATH.
-: 2860: */
-: 2861:static void
function ReorderBufferSerializedPath called 497 returned 100% blocks executed 100%
497: 2862:ReorderBufferSerializedPath(char *path, ReplicationSlot *slot, TransactionId xid,
-: 2863: XLogSegNo segno)
-: 2864:{
-: 2865: XLogRecPtr recptr;
-: 2866:
497: 2867: XLogSegNoOffsetToRecPtr(segno, 0, wal_segment_size, recptr);
-: 2868:
1491: 2869: snprintf(path, MAXPGPATH, "pg_replslot/%s/xid-%u-lsn-%X-%X.spill",
call 0 returned 100%
497: 2870: NameStr(MyReplicationSlot->data.name),
-: 2871: xid,
497: 2872: (uint32) (recptr >> 32), (uint32) recptr);
497: 2873:}
-: 2874:
-: 2875:/*
-: 2876: * Delete all data spilled to disk after we've restarted/crashed. It will be
-: 2877: * recreated when the respective slots are reused.
-: 2878: */
-: 2879:void
function StartupReorderBuffer called 546 returned 100% blocks executed 92%
546: 2880:StartupReorderBuffer(void)
-: 2881:{
-: 2882: DIR *logical_dir;
-: 2883: struct dirent *logical_de;
-: 2884:
546: 2885: logical_dir = AllocateDir("pg_replslot");
call 0 returned 100%
2194: 2886: while ((logical_de = ReadDir(logical_dir, "pg_replslot")) != NULL)
call 0 returned 100%
branch 1 taken 67%
branch 2 taken 33% (fallthrough)
-: 2887: {
1658: 2888: if (strcmp(logical_de->d_name, ".") == 0 ||
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
branch 2 taken 98% (fallthrough)
branch 3 taken 2%
556: 2889: strcmp(logical_de->d_name, "..") == 0)
1092: 2890: continue;
-: 2891:
-: 2892: /* if it cannot be a slot, skip the directory */
10: 2893: if (!ReplicationSlotValidateName(logical_de->d_name, DEBUG2))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 2894: continue;
-: 2895:
-: 2896: /*
-: 2897: * ok, has to be a surviving logical slot, iterate and delete
-: 2898: * everything starting with xid-*
-: 2899: */
10: 2900: ReorderBufferCleanupSerializedTXNs(logical_de->d_name);
call 0 returned 100%
-: 2901: }
546: 2902: FreeDir(logical_dir);
call 0 returned 100%
546: 2903:}
-: 2904:
-: 2905:/* ---------------------------------------
-: 2906: * toast reassembly support
-: 2907: * ---------------------------------------
-: 2908: */
-: 2909:
-: 2910:/*
-: 2911: * Initialize per tuple toast reconstruction support.
-: 2912: */
-: 2913:static void
function ReorderBufferToastInitHash called 17 returned 100% blocks executed 75%
17: 2914:ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 2915:{
-: 2916: HASHCTL hash_ctl;
-: 2917:
17: 2918: Assert(txn->toast_hash == NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2919:
17: 2920: memset(&hash_ctl, 0, sizeof(hash_ctl));
17: 2921: hash_ctl.keysize = sizeof(Oid);
17: 2922: hash_ctl.entrysize = sizeof(ReorderBufferToastEnt);
17: 2923: hash_ctl.hcxt = rb->context;
17: 2924: txn->toast_hash = hash_create("ReorderBufferToastHash", 5, &hash_ctl,
call 0 returned 100%
-: 2925: HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
17: 2926:}
-: 2927:
-: 2928:/*
-: 2929: * Per toast-chunk handling for toast reconstruction
-: 2930: *
-: 2931: * Appends a toast chunk so we can reconstruct it when the tuple "owning" the
-: 2932: * toasted Datum comes along.
-: 2933: */
-: 2934:static void
function ReorderBufferToastAppendChunk called 301 returned 100% blocks executed 45%
301: 2935:ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 2936: Relation relation, ReorderBufferChange *change)
-: 2937:{
-: 2938: ReorderBufferToastEnt *ent;
-: 2939: ReorderBufferTupleBuf *newtup;
-: 2940: bool found;
-: 2941: int32 chunksize;
-: 2942: bool isnull;
-: 2943: Pointer chunk;
301: 2944: TupleDesc desc = RelationGetDescr(relation);
-: 2945: Oid chunk_id;
-: 2946: int32 chunk_seq;
-: 2947:
301: 2948: if (txn->toast_hash == NULL)
branch 0 taken 6% (fallthrough)
branch 1 taken 94%
17: 2949: ReorderBufferToastInitHash(rb, txn);
call 0 returned 100%
-: 2950:
301: 2951: Assert(IsToastRelation(relation));
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 2952:
301: 2953: newtup = change->data.tp.newtuple;
301: 2954: chunk_id = DatumGetObjectId(fastgetattr(&newtup->tuple, 1, desc, &isnull));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 100% (fallthrough)
branch 5 taken 0%
branch 6 taken 0% (fallthrough)
branch 7 taken 100%
branch 8 taken 100% (fallthrough)
branch 9 taken 0%
branch 10 never executed
branch 11 never executed
branch 12 never executed
branch 13 never executed
call 14 never executed
call 15 never executed
branch 16 never executed
branch 17 never executed
call 18 never executed
301: 2955: Assert(!isnull);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
301: 2956: chunk_seq = DatumGetInt32(fastgetattr(&newtup->tuple, 2, desc, &isnull));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 97% (fallthrough)
branch 3 taken 3%
branch 4 taken 100% (fallthrough)
branch 5 taken 0%
branch 6 taken 0% (fallthrough)
branch 7 taken 100%
branch 8 taken 100% (fallthrough)
branch 9 taken 0%
branch 10 never executed
branch 11 never executed
branch 12 never executed
branch 13 never executed
call 14 never executed
call 15 returned 100%
branch 16 never executed
branch 17 never executed
call 18 never executed
301: 2957: Assert(!isnull);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2958:
301: 2959: ent = (ReorderBufferToastEnt *)
call 0 returned 100%
301: 2960: hash_search(txn->toast_hash,
-: 2961: (void *) &chunk_id,
-: 2962: HASH_ENTER,
-: 2963: &found);
-: 2964:
301: 2965: if (!found)
branch 0 taken 7% (fallthrough)
branch 1 taken 93%
-: 2966: {
21: 2967: Assert(ent->chunk_id == chunk_id);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
21: 2968: ent->num_chunks = 0;
21: 2969: ent->last_chunk_seq = 0;
21: 2970: ent->size = 0;
21: 2971: ent->reconstructed = NULL;
21: 2972: dlist_init(&ent->chunks);
call 0 returned 100%
-: 2973:
21: 2974: if (chunk_seq != 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2975: elog(ERROR, "got sequence entry %d for toast chunk %u instead of seq 0",
call 0 never executed
call 1 never executed
call 2 never executed
-: 2976: chunk_seq, chunk_id);
-: 2977: }
280: 2978: else if (found && chunk_seq != ent->last_chunk_seq + 1)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 2979: elog(ERROR, "got sequence entry %d for toast chunk %u instead of seq %d",
call 0 never executed
call 1 never executed
call 2 never executed
-: 2980: chunk_seq, chunk_id, ent->last_chunk_seq + 1);
-: 2981:
301: 2982: chunk = DatumGetPointer(fastgetattr(&newtup->tuple, 3, desc, &isnull));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 97% (fallthrough)
branch 3 taken 3%
branch 4 taken 0% (fallthrough)
branch 5 taken 100%
branch 6 never executed
branch 7 never executed
branch 8 never executed
branch 9 never executed
branch 10 never executed
branch 11 never executed
branch 12 never executed
branch 13 never executed
call 14 never executed
call 15 returned 100%
branch 16 never executed
branch 17 never executed
call 18 never executed
301: 2983: Assert(!isnull);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2984:
-: 2985: /* calculate size so we can allocate the right size at once later */
301: 2986: if (!VARATT_IS_EXTENDED(chunk))
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
301: 2987: chunksize = VARSIZE(chunk) - VARHDRSZ;
#####: 2988: else if (VARATT_IS_SHORT(chunk))
branch 0 never executed
branch 1 never executed
-: 2989: /* could happen due to heap_form_tuple doing its thing */
#####: 2990: chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
-: 2991: else
#####: 2992: elog(ERROR, "unexpected type of toast chunk");
call 0 never executed
call 1 never executed
call 2 never executed
-: 2993:
301: 2994: ent->size += chunksize;
301: 2995: ent->last_chunk_seq = chunk_seq;
301: 2996: ent->num_chunks++;
301: 2997: dlist_push_tail(&ent->chunks, &change->node);
call 0 returned 100%
301: 2998:}
-: 2999:
-: 3000:/*
-: 3001: * Rejigger change->newtuple to point to in-memory toast tuples instead to
-: 3002: * on-disk toast tuples that may not longer exist (think DROP TABLE or VACUUM).
-: 3003: *
-: 3004: * We cannot replace unchanged toast tuples though, so those will still point
-: 3005: * to on-disk toast data.
-: 3006: */
-: 3007:static void
function ReorderBufferToastReplace called 146981 returned 100% blocks executed 65%
146981: 3008:ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
-: 3009: Relation relation, ReorderBufferChange *change)
-: 3010:{
-: 3011: TupleDesc desc;
-: 3012: int natt;
-: 3013: Datum *attrs;
-: 3014: bool *isnull;
-: 3015: bool *free;
-: 3016: HeapTuple tmphtup;
-: 3017: Relation toast_rel;
-: 3018: TupleDesc toast_desc;
-: 3019: MemoryContext oldcontext;
-: 3020: ReorderBufferTupleBuf *newtup;
-: 3021:
-: 3022: /* no toast tuples changed */
146981: 3023: if (txn->toast_hash == NULL)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
293743: 3024: return;
-: 3025:
219: 3026: oldcontext = MemoryContextSwitchTo(rb->context);
call 0 returned 100%
-: 3027:
-: 3028: /* we should only have toast tuples in an INSERT or UPDATE */
219: 3029: Assert(change->data.tp.newtuple);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3030:
219: 3031: desc = RelationGetDescr(relation);
-: 3032:
219: 3033: toast_rel = RelationIdGetRelation(relation->rd_rel->reltoastrelid);
call 0 returned 100%
219: 3034: if (!RelationIsValid(toast_rel))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3035: elog(ERROR, "could not open relation with OID %u",
call 0 never executed
call 1 never executed
call 2 never executed
-: 3036: relation->rd_rel->reltoastrelid);
-: 3037:
219: 3038: toast_desc = RelationGetDescr(toast_rel);
-: 3039:
-: 3040: /* should we allocate from stack instead? */
219: 3041: attrs = palloc0(sizeof(Datum) * desc->natts);
call 0 returned 100%
219: 3042: isnull = palloc0(sizeof(bool) * desc->natts);
call 0 returned 100%
219: 3043: free = palloc0(sizeof(bool) * desc->natts);
call 0 returned 100%
-: 3044:
219: 3045: newtup = change->data.tp.newtuple;
-: 3046:
219: 3047: heap_deform_tuple(&newtup->tuple, desc, attrs, isnull);
call 0 returned 100%
-: 3048:
698: 3049: for (natt = 0; natt < desc->natts; natt++)
branch 0 taken 69%
branch 1 taken 31% (fallthrough)
-: 3050: {
479: 3051: Form_pg_attribute attr = TupleDescAttr(desc, natt);
-: 3052: ReorderBufferToastEnt *ent;
-: 3053: struct varlena *varlena;
-: 3054:
-: 3055: /* va_rawsize is the size of the original datum -- including header */
-: 3056: struct varatt_external toast_pointer;
-: 3057: struct varatt_indirect redirect_pointer;
479: 3058: struct varlena *new_datum = NULL;
-: 3059: struct varlena *reconstructed;
-: 3060: dlist_iter it;
479: 3061: Size data_done = 0;
-: 3062:
-: 3063: /* system columns aren't toasted */
479: 3064: if (attr->attnum < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
458: 3065: continue;
-: 3066:
479: 3067: if (attr->attisdropped)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3068: continue;
-: 3069:
-: 3070: /* not a varlena datatype */
479: 3071: if (attr->attlen != -1)
branch 0 taken 49% (fallthrough)
branch 1 taken 51%
237: 3072: continue;
-: 3073:
-: 3074: /* no data */
242: 3075: if (isnull[natt])
branch 0 taken 5% (fallthrough)
branch 1 taken 95%
12: 3076: continue;
-: 3077:
-: 3078: /* ok, we know we have a toast datum */
230: 3079: varlena = (struct varlena *) DatumGetPointer(attrs[natt]);
-: 3080:
-: 3081: /* no need to do anything if the tuple isn't external */
230: 3082: if (!VARATT_IS_EXTERNAL(varlena))
branch 0 taken 87% (fallthrough)
branch 1 taken 13%
201: 3083: continue;
-: 3084:
29: 3085: VARATT_EXTERNAL_GET_POINTER(toast_pointer, varlena);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
branch 3 taken 100% (fallthrough)
branch 4 taken 0%
branch 5 taken 100% (fallthrough)
branch 6 taken 0%
branch 7 taken 0% (fallthrough)
branch 8 taken 100%
call 9 never executed
branch 10 taken 0% (fallthrough)
branch 11 taken 100%
call 12 never executed
-: 3086:
-: 3087: /*
-: 3088: * Check whether the toast tuple changed, replace if so.
-: 3089: */
29: 3090: ent = (ReorderBufferToastEnt *)
call 0 returned 100%
29: 3091: hash_search(txn->toast_hash,
-: 3092: (void *) &toast_pointer.va_valueid,
-: 3093: HASH_FIND,
-: 3094: NULL);
29: 3095: if (ent == NULL)
branch 0 taken 28% (fallthrough)
branch 1 taken 72%
8: 3096: continue;
-: 3097:
21: 3098: new_datum =
call 0 returned 100%
-: 3099: (struct varlena *) palloc0(INDIRECT_POINTER_SIZE);
-: 3100:
21: 3101: free[natt] = true;
-: 3102:
21: 3103: reconstructed = palloc0(toast_pointer.va_rawsize);
call 0 returned 100%
-: 3104:
21: 3105: ent->reconstructed = reconstructed;
-: 3106:
-: 3107: /* stitch toast tuple back together from its parts */
322: 3108: dlist_foreach(it, &ent->chunks)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 93%
branch 3 taken 7% (fallthrough)
-: 3109: {
-: 3110: bool isnull;
-: 3111: ReorderBufferChange *cchange;
-: 3112: ReorderBufferTupleBuf *ctup;
-: 3113: Pointer chunk;
-: 3114:
301: 3115: cchange = dlist_container(ReorderBufferChange, node, it.cur);
301: 3116: ctup = cchange->data.tp.newtuple;
301: 3117: chunk = DatumGetPointer(
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 0% (fallthrough)
branch 5 taken 100%
branch 6 never executed
branch 7 never executed
branch 8 never executed
branch 9 never executed
branch 10 never executed
branch 11 never executed
branch 12 never executed
branch 13 never executed
call 14 never executed
call 15 never executed
branch 16 never executed
branch 17 never executed
call 18 never executed
-: 3118: fastgetattr(&ctup->tuple, 3, toast_desc, &isnull));
-: 3119:
301: 3120: Assert(!isnull);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
301: 3121: Assert(!VARATT_IS_EXTERNAL(chunk));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
301: 3122: Assert(!VARATT_IS_SHORT(chunk));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3123:
602: 3124: memcpy(VARDATA(reconstructed) + data_done,
301: 3125: VARDATA(chunk),
301: 3126: VARSIZE(chunk) - VARHDRSZ);
301: 3127: data_done += VARSIZE(chunk) - VARHDRSZ;
-: 3128: }
21: 3129: Assert(data_done == toast_pointer.va_extsize);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3130:
-: 3131: /* make sure its marked as compressed or not */
21: 3132: if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
branch 0 taken 14% (fallthrough)
branch 1 taken 86%
3: 3133: SET_VARSIZE_COMPRESSED(reconstructed, data_done + VARHDRSZ);
-: 3134: else
18: 3135: SET_VARSIZE(reconstructed, data_done + VARHDRSZ);
-: 3136:
21: 3137: memset(&redirect_pointer, 0, sizeof(redirect_pointer));
21: 3138: redirect_pointer.pointer = reconstructed;
-: 3139:
21: 3140: SET_VARTAG_EXTERNAL(new_datum, VARTAG_INDIRECT);
21: 3141: memcpy(VARDATA_EXTERNAL(new_datum), &redirect_pointer,
-: 3142: sizeof(redirect_pointer));
-: 3143:
21: 3144: attrs[natt] = PointerGetDatum(new_datum);
-: 3145: }
-: 3146:
-: 3147: /*
-: 3148: * Build tuple in separate memory & copy tuple back into the tuplebuf
-: 3149: * passed to the output plugin. We can't directly heap_fill_tuple() into
-: 3150: * the tuplebuf because attrs[] will point back into the current content.
-: 3151: */
219: 3152: tmphtup = heap_form_tuple(desc, attrs, isnull);
call 0 returned 100%
219: 3153: Assert(newtup->tuple.t_len <= MaxHeapTupleSize);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
219: 3154: Assert(ReorderBufferTupleBufData(newtup) == newtup->tuple.t_data);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3155:
219: 3156: memcpy(newtup->tuple.t_data, tmphtup->t_data, tmphtup->t_len);
219: 3157: newtup->tuple.t_len = tmphtup->t_len;
-: 3158:
-: 3159: /*
-: 3160: * free resources we won't further need, more persistent stuff will be
-: 3161: * free'd in ReorderBufferToastReset().
-: 3162: */
219: 3163: RelationClose(toast_rel);
call 0 returned 100%
219: 3164: pfree(tmphtup);
call 0 returned 100%
698: 3165: for (natt = 0; natt < desc->natts; natt++)
branch 0 taken 69%
branch 1 taken 31% (fallthrough)
-: 3166: {
479: 3167: if (free[natt])
branch 0 taken 4% (fallthrough)
branch 1 taken 96%
21: 3168: pfree(DatumGetPointer(attrs[natt]));
call 0 returned 100%
-: 3169: }
219: 3170: pfree(attrs);
call 0 returned 100%
219: 3171: pfree(free);
call 0 returned 100%
219: 3172: pfree(isnull);
call 0 returned 100%
-: 3173:
219: 3174: MemoryContextSwitchTo(oldcontext);
call 0 returned 100%
-: 3175:}
-: 3176:
-: 3177:/*
-: 3178: * Free all resources allocated for toast reconstruction.
-: 3179: */
-: 3180:static void
function ReorderBufferToastReset called 146779 returned 100% blocks executed 95%
146779: 3181:ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn)
-: 3182:{
-: 3183: HASH_SEQ_STATUS hstat;
-: 3184: ReorderBufferToastEnt *ent;
-: 3185:
146779: 3186: if (txn->toast_hash == NULL)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
293541: 3187: return;
-: 3188:
-: 3189: /* sequentially walk over the hash and free everything */
17: 3190: hash_seq_init(&hstat, txn->toast_hash);
call 0 returned 100%
55: 3191: while ((ent = (ReorderBufferToastEnt *) hash_seq_search(&hstat)) != NULL)
call 0 returned 100%
branch 1 taken 55%
branch 2 taken 45% (fallthrough)
-: 3192: {
-: 3193: dlist_mutable_iter it;
-: 3194:
21: 3195: if (ent->reconstructed != NULL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
21: 3196: pfree(ent->reconstructed);
call 0 returned 100%
-: 3197:
322: 3198: dlist_foreach_modify(it, &ent->chunks)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 93%
branch 3 taken 7% (fallthrough)
-: 3199: {
301: 3200: ReorderBufferChange *change =
301: 3201: dlist_container(ReorderBufferChange, node, it.cur);
-: 3202:
301: 3203: dlist_delete(&change->node);
call 0 returned 100%
301: 3204: ReorderBufferReturnChange(rb, change);
call 0 returned 100%
-: 3205: }
-: 3206: }
-: 3207:
17: 3208: hash_destroy(txn->toast_hash);
call 0 returned 100%
17: 3209: txn->toast_hash = NULL;
-: 3210:}
-: 3211:
-: 3212:
-: 3213:/* ---------------------------------------
-: 3214: * Visibility support for logical decoding
-: 3215: *
-: 3216: *
-: 3217: * Lookup actual cmin/cmax values when using decoding snapshot. We can't
-: 3218: * always rely on stored cmin/cmax values because of two scenarios:
-: 3219: *
-: 3220: * * A tuple got changed multiple times during a single transaction and thus
-: 3221: * has got a combocid. Combocid's are only valid for the duration of a
-: 3222: * single transaction.
-: 3223: * * A tuple with a cmin but no cmax (and thus no combocid) got
-: 3224: * deleted/updated in another transaction than the one which created it
-: 3225: * which we are looking at right now. As only one of cmin, cmax or combocid
-: 3226: * is actually stored in the heap we don't have access to the value we
-: 3227: * need anymore.
-: 3228: *
-: 3229: * To resolve those problems we have a per-transaction hash of (cmin,
-: 3230: * cmax) tuples keyed by (relfilenode, ctid) which contains the actual
-: 3231: * (cmin, cmax) values. That also takes care of combocids by simply
-: 3232: * not caring about them at all. As we have the real cmin/cmax values
-: 3233: * combocids aren't interesting.
-: 3234: *
-: 3235: * As we only care about catalog tuples here the overhead of this
-: 3236: * hashtable should be acceptable.
-: 3237: *
-: 3238: * Heap rewrites complicate this a bit, check rewriteheap.c for
-: 3239: * details.
-: 3240: * -------------------------------------------------------------------------
-: 3241: */
-: 3242:
-: 3243:/* struct for sorting mapping files by LSN efficiently */
-: 3244:typedef struct RewriteMappingFile
-: 3245:{
-: 3246: XLogRecPtr lsn;
-: 3247: char fname[MAXPGPATH];
-: 3248:} RewriteMappingFile;
-: 3249:
-: 3250:#ifdef NOT_USED
-: 3251:static void
-: 3252:DisplayMapping(HTAB *tuplecid_data)
-: 3253:{
-: 3254: HASH_SEQ_STATUS hstat;
-: 3255: ReorderBufferTupleCidEnt *ent;
-: 3256:
-: 3257: hash_seq_init(&hstat, tuplecid_data);
-: 3258: while ((ent = (ReorderBufferTupleCidEnt *) hash_seq_search(&hstat)) != NULL)
-: 3259: {
-: 3260: elog(DEBUG3, "mapping: node: %u/%u/%u tid: %u/%u cmin: %u, cmax: %u",
-: 3261: ent->key.relnode.dbNode,
-: 3262: ent->key.relnode.spcNode,
-: 3263: ent->key.relnode.relNode,
-: 3264: ItemPointerGetBlockNumber(&ent->key.tid),
-: 3265: ItemPointerGetOffsetNumber(&ent->key.tid),
-: 3266: ent->cmin,
-: 3267: ent->cmax
-: 3268: );
-: 3269: }
-: 3270:}
-: 3271:#endif
-: 3272:
-: 3273:/*
-: 3274: * Apply a single mapping file to tuplecid_data.
-: 3275: *
-: 3276: * The mapping file has to have been verified to be a) committed b) for our
-: 3277: * transaction c) applied in LSN order.
-: 3278: */
-: 3279:static void
function ApplyLogicalMappingFile called 22 returned 100% blocks executed 41%
22: 3280:ApplyLogicalMappingFile(HTAB *tuplecid_data, Oid relid, const char *fname)
-: 3281:{
-: 3282: char path[MAXPGPATH];
-: 3283: int fd;
-: 3284: int readBytes;
-: 3285: LogicalRewriteMappingData map;
-: 3286:
22: 3287: sprintf(path, "pg_logical/mappings/%s", fname);
call 0 returned 100%
22: 3288: fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
call 0 returned 100%
22: 3289: if (fd < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3290: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 3291: (errcode_for_file_access(),
-: 3292: errmsg("could not open file \"%s\": %m", path)));
-: 3293:
-: 3294: while (true)
-: 3295: {
-: 3296: ReorderBufferTupleCidKey key;
-: 3297: ReorderBufferTupleCidEnt *ent;
-: 3298: ReorderBufferTupleCidEnt *new_ent;
-: 3299: bool found;
-: 3300:
-: 3301: /* be careful about padding */
141: 3302: memset(&key, 0, sizeof(ReorderBufferTupleCidKey));
-: 3303:
-: 3304: /* read all mappings till the end of the file */
141: 3305: pgstat_report_wait_start(WAIT_EVENT_REORDER_LOGICAL_MAPPING_READ);
call 0 returned 100%
141: 3306: readBytes = read(fd, &map, sizeof(LogicalRewriteMappingData));
call 0 returned 100%
141: 3307: pgstat_report_wait_end();
call 0 returned 100%
-: 3308:
141: 3309: if (readBytes < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3310: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 3311: (errcode_for_file_access(),
-: 3312: errmsg("could not read file \"%s\": %m",
-: 3313: path)));
141: 3314: else if (readBytes == 0) /* EOF */
branch 0 taken 16% (fallthrough)
branch 1 taken 84%
22: 3315: break;
119: 3316: else if (readBytes != sizeof(LogicalRewriteMappingData))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3317: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 3318: (errcode_for_file_access(),
-: 3319: errmsg("could not read from file \"%s\": read %d instead of %d bytes",
-: 3320: path, readBytes,
-: 3321: (int32) sizeof(LogicalRewriteMappingData))));
-: 3322:
119: 3323: key.relnode = map.old_node;
119: 3324: ItemPointerCopy(&map.old_tid,
-: 3325: &key.tid);
-: 3326:
-: 3327:
119: 3328: ent = (ReorderBufferTupleCidEnt *)
call 0 returned 100%
-: 3329: hash_search(tuplecid_data,
-: 3330: (void *) &key,
-: 3331: HASH_FIND,
-: 3332: NULL);
-: 3333:
-: 3334: /* no existing mapping, no need to update */
119: 3335: if (!ent)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3336: continue;
-: 3337:
119: 3338: key.relnode = map.new_node;
119: 3339: ItemPointerCopy(&map.new_tid,
-: 3340: &key.tid);
-: 3341:
119: 3342: new_ent = (ReorderBufferTupleCidEnt *)
call 0 returned 100%
-: 3343: hash_search(tuplecid_data,
-: 3344: (void *) &key,
-: 3345: HASH_ENTER,
-: 3346: &found);
-: 3347:
119: 3348: if (found)
branch 0 taken 5% (fallthrough)
branch 1 taken 95%
-: 3349: {
-: 3350: /*
-: 3351: * Make sure the existing mapping makes sense. We sometime update
-: 3352: * old records that did not yet have a cmax (e.g. pg_class' own
-: 3353: * entry while rewriting it) during rewrites, so allow that.
-: 3354: */
6: 3355: Assert(ent->cmin == InvalidCommandId || ent->cmin == new_ent->cmin);
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
call 4 never executed
6: 3356: Assert(ent->cmax == InvalidCommandId || ent->cmax == new_ent->cmax);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
branch 2 never executed
branch 3 never executed
call 4 never executed
-: 3357: }
-: 3358: else
-: 3359: {
-: 3360: /* update mapping */
113: 3361: new_ent->cmin = ent->cmin;
113: 3362: new_ent->cmax = ent->cmax;
113: 3363: new_ent->combocid = ent->combocid;
-: 3364: }
119: 3365: }
-: 3366:
22: 3367: if (CloseTransientFile(fd) != 0)
call 0 returned 100%
branch 1 taken 0%
branch 2 taken 100%
#####: 3368: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 3369: (errcode_for_file_access(),
-: 3370: errmsg("could not close file \"%s\": %m", path)));
22: 3371:}
-: 3372:
-: 3373:
-: 3374:/*
-: 3375: * Check whether the TransactionId 'xid' is in the pre-sorted array 'xip'.
-: 3376: */
-: 3377:static bool
function TransactionIdInArray called 290 returned 100% blocks executed 100%
290: 3378:TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
-: 3379:{
290: 3380: return bsearch(&xid, xip, num,
call 0 returned 100%
-: 3381: sizeof(TransactionId), xidComparator) != NULL;
-: 3382:}
-: 3383:
-: 3384:/*
-: 3385: * list_sort() comparator for sorting RewriteMappingFiles in LSN order.
-: 3386: */
-: 3387:static int
function file_sort_by_lsn called 17 returned 100% blocks executed 50%
17: 3388:file_sort_by_lsn(const ListCell *a_p, const ListCell *b_p)
-: 3389:{
17: 3390: RewriteMappingFile *a = (RewriteMappingFile *) lfirst(a_p);
17: 3391: RewriteMappingFile *b = (RewriteMappingFile *) lfirst(b_p);
-: 3392:
17: 3393: if (a->lsn < b->lsn)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
17: 3394: return -1;
#####: 3395: else if (a->lsn > b->lsn)
branch 0 never executed
branch 1 never executed
#####: 3396: return 1;
#####: 3397: return 0;
-: 3398:}
-: 3399:
-: 3400:/*
-: 3401: * Apply any existing logical remapping files if there are any targeted at our
-: 3402: * transaction for relid.
-: 3403: */
-: 3404:static void
function UpdateLogicalMappings called 5 returned 100% blocks executed 87%
5: 3405:UpdateLogicalMappings(HTAB *tuplecid_data, Oid relid, Snapshot snapshot)
-: 3406:{
-: 3407: DIR *mapping_dir;
-: 3408: struct dirent *mapping_de;
5: 3409: List *files = NIL;
-: 3410: ListCell *file;
5: 3411: Oid dboid = IsSharedRelation(relid) ? InvalidOid : MyDatabaseId;
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 3412:
5: 3413: mapping_dir = AllocateDir("pg_logical/mappings");
call 0 returned 100%
465: 3414: while ((mapping_de = ReadDir(mapping_dir, "pg_logical/mappings")) != NULL)
call 0 returned 100%
branch 1 taken 99%
branch 2 taken 1% (fallthrough)
-: 3415: {
-: 3416: Oid f_dboid;
-: 3417: Oid f_relid;
-: 3418: TransactionId f_mapped_xid;
-: 3419: TransactionId f_create_xid;
-: 3420: XLogRecPtr f_lsn;
-: 3421: uint32 f_hi,
-: 3422: f_lo;
-: 3423: RewriteMappingFile *f;
-: 3424:
905: 3425: if (strcmp(mapping_de->d_name, ".") == 0 ||
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 1% (fallthrough)
branch 3 taken 99%
450: 3426: strcmp(mapping_de->d_name, "..") == 0)
443: 3427: continue;
-: 3428:
-: 3429: /* Ignore files that aren't ours */
445: 3430: if (strncmp(mapping_de->d_name, "map-", 4) != 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3431: continue;
-: 3432:
445: 3433: if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT,
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 3434: &f_dboid, &f_relid, &f_hi, &f_lo,
-: 3435: &f_mapped_xid, &f_create_xid) != 6)
#####: 3436: elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name);
call 0 never executed
call 1 never executed
call 2 never executed
-: 3437:
445: 3438: f_lsn = ((uint64) f_hi) << 32 | f_lo;
-: 3439:
-: 3440: /* mapping for another database */
445: 3441: if (f_dboid != dboid)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3442: continue;
-: 3443:
-: 3444: /* mapping for another relation */
445: 3445: if (f_relid != relid)
branch 0 taken 10% (fallthrough)
branch 1 taken 90%
45: 3446: continue;
-: 3447:
-: 3448: /* did the creating transaction abort? */
400: 3449: if (!TransactionIdDidCommit(f_create_xid))
call 0 returned 100%
branch 1 taken 28% (fallthrough)
branch 2 taken 73%
110: 3450: continue;
-: 3451:
-: 3452: /* not for our transaction */
290: 3453: if (!TransactionIdInArray(f_mapped_xid, snapshot->subxip, snapshot->subxcnt))
call 0 returned 100%
branch 1 taken 92% (fallthrough)
branch 2 taken 8%
268: 3454: continue;
-: 3455:
-: 3456: /* ok, relevant, queue for apply */
22: 3457: f = palloc(sizeof(RewriteMappingFile));
call 0 returned 100%
22: 3458: f->lsn = f_lsn;
22: 3459: strcpy(f->fname, mapping_de->d_name);
22: 3460: files = lappend(files, f);
call 0 returned 100%
-: 3461: }
5: 3462: FreeDir(mapping_dir);
call 0 returned 100%
-: 3463:
-: 3464: /* sort files so we apply them in LSN order */
5: 3465: list_sort(files, file_sort_by_lsn);
call 0 returned 100%
-: 3466:
27: 3467: foreach(file, files)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 81% (fallthrough)
branch 3 taken 19%
branch 4 taken 81%
branch 5 taken 19% (fallthrough)
-: 3468: {
22: 3469: RewriteMappingFile *f = (RewriteMappingFile *) lfirst(file);
-: 3470:
22: 3471: elog(DEBUG1, "applying mapping: \"%s\" in %u", f->fname,
call 0 returned 100%
call 1 returned 100%
-: 3472: snapshot->subxip[0]);
22: 3473: ApplyLogicalMappingFile(tuplecid_data, relid, f->fname);
call 0 returned 100%
22: 3474: pfree(f);
call 0 returned 100%
-: 3475: }
5: 3476:}
-: 3477:
-: 3478:/*
-: 3479: * Lookup cmin/cmax of a tuple, during logical decoding where we can't rely on
-: 3480: * combocids.
-: 3481: */
-: 3482:bool
function ResolveCminCmaxDuringDecoding called 404 returned 100% blocks executed 75%
404: 3483:ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
-: 3484: Snapshot snapshot,
-: 3485: HeapTuple htup, Buffer buffer,
-: 3486: CommandId *cmin, CommandId *cmax)
-: 3487:{
-: 3488: ReorderBufferTupleCidKey key;
-: 3489: ReorderBufferTupleCidEnt *ent;
-: 3490: ForkNumber forkno;
-: 3491: BlockNumber blockno;
404: 3492: bool updated_mapping = false;
-: 3493:
-: 3494: /* be careful about padding */
404: 3495: memset(&key, 0, sizeof(key));
-: 3496:
404: 3497: Assert(!BufferIsLocal(buffer));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3498:
-: 3499: /*
-: 3500: * get relfilenode from the buffer, no convenient way to access it other
-: 3501: * than that.
-: 3502: */
404: 3503: BufferGetTag(buffer, &key.relnode, &forkno, &blockno);
call 0 returned 100%
-: 3504:
-: 3505: /* tuples can only be in the main fork */
404: 3506: Assert(forkno == MAIN_FORKNUM);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
404: 3507: Assert(blockno == ItemPointerGetBlockNumber(&htup->t_self));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 0% (fallthrough)
branch 5 taken 100%
call 6 never executed
branch 7 taken 0% (fallthrough)
branch 8 taken 100%
call 9 never executed
branch 10 taken 0% (fallthrough)
branch 11 taken 100%
call 12 never executed
-: 3508:
404: 3509: ItemPointerCopy(&htup->t_self,
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3510: &key.tid);
-: 3511:
-: 3512:restart:
409: 3513: ent = (ReorderBufferTupleCidEnt *)
call 0 returned 100%
-: 3514: hash_search(tuplecid_data,
-: 3515: (void *) &key,
-: 3516: HASH_FIND,
-: 3517: NULL);
-: 3518:
-: 3519: /*
-: 3520: * failed to find a mapping, check whether the table was rewritten and
-: 3521: * apply mapping if so, but only do that once - there can be no new
-: 3522: * mappings while we are in here since we have to hold a lock on the
-: 3523: * relation.
-: 3524: */
409: 3525: if (ent == NULL && !updated_mapping)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
-: 3526: {
5: 3527: UpdateLogicalMappings(tuplecid_data, htup->t_tableOid, snapshot);
call 0 returned 100%
-: 3528: /* now check but don't update for a mapping again */
5: 3529: updated_mapping = true;
5: 3530: goto restart;
-: 3531: }
404: 3532: else if (ent == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3533: return false;
-: 3534:
404: 3535: if (cmin)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
404: 3536: *cmin = ent->cmin;
404: 3537: if (cmax)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
404: 3538: *cmax = ent->cmax;
404: 3539: return true;
-: 3540:}
base_code_coverage/worker.c.gcov 0000664 0001750 0000000 00000262417 13560201434 016456 0 ustar vignesh root -: 0:Source:worker.c
-: 0:Graph:./worker.gcno
-: 0:Data:./worker.gcda
-: 0:Runs:6536
-: 0:Programs:1
-: 1:/*-------------------------------------------------------------------------
-: 2: * worker.c
-: 3: * PostgreSQL logical replication worker (apply)
-: 4: *
-: 5: * Copyright (c) 2016-2019, PostgreSQL Global Development Group
-: 6: *
-: 7: * IDENTIFICATION
-: 8: * src/backend/replication/logical/worker.c
-: 9: *
-: 10: * NOTES
-: 11: * This file contains the worker which applies logical changes as they come
-: 12: * from remote logical replication stream.
-: 13: *
-: 14: * The main worker (apply) is started by logical replication worker
-: 15: * launcher for every enabled subscription in a database. It uses
-: 16: * walsender protocol to communicate with publisher.
-: 17: *
-: 18: * This module includes server facing code and shares libpqwalreceiver
-: 19: * module with walreceiver for providing the libpq specific functionality.
-: 20: *
-: 21: *-------------------------------------------------------------------------
-: 22: */
-: 23:
-: 24:#include "postgres.h"
-: 25:
-: 26:#include "access/table.h"
-: 27:#include "access/tableam.h"
-: 28:#include "access/xact.h"
-: 29:#include "access/xlog_internal.h"
-: 30:#include "catalog/catalog.h"
-: 31:#include "catalog/namespace.h"
-: 32:#include "catalog/pg_subscription.h"
-: 33:#include "catalog/pg_subscription_rel.h"
-: 34:#include "commands/tablecmds.h"
-: 35:#include "commands/trigger.h"
-: 36:#include "executor/executor.h"
-: 37:#include "executor/nodeModifyTable.h"
-: 38:#include "funcapi.h"
-: 39:#include "libpq/pqformat.h"
-: 40:#include "libpq/pqsignal.h"
-: 41:#include "mb/pg_wchar.h"
-: 42:#include "miscadmin.h"
-: 43:#include "nodes/makefuncs.h"
-: 44:#include "optimizer/optimizer.h"
-: 45:#include "parser/parse_relation.h"
-: 46:#include "pgstat.h"
-: 47:#include "postmaster/bgworker.h"
-: 48:#include "postmaster/postmaster.h"
-: 49:#include "postmaster/walwriter.h"
-: 50:#include "replication/decode.h"
-: 51:#include "replication/logical.h"
-: 52:#include "replication/logicalproto.h"
-: 53:#include "replication/logicalrelation.h"
-: 54:#include "replication/logicalworker.h"
-: 55:#include "replication/origin.h"
-: 56:#include "replication/reorderbuffer.h"
-: 57:#include "replication/snapbuild.h"
-: 58:#include "replication/walreceiver.h"
-: 59:#include "replication/worker_internal.h"
-: 60:#include "rewrite/rewriteHandler.h"
-: 61:#include "storage/bufmgr.h"
-: 62:#include "storage/ipc.h"
-: 63:#include "storage/lmgr.h"
-: 64:#include "storage/proc.h"
-: 65:#include "storage/procarray.h"
-: 66:#include "tcop/tcopprot.h"
-: 67:#include "utils/builtins.h"
-: 68:#include "utils/catcache.h"
-: 69:#include "utils/datum.h"
-: 70:#include "utils/fmgroids.h"
-: 71:#include "utils/guc.h"
-: 72:#include "utils/inval.h"
-: 73:#include "utils/lsyscache.h"
-: 74:#include "utils/memutils.h"
-: 75:#include "utils/rel.h"
-: 76:#include "utils/syscache.h"
-: 77:#include "utils/timeout.h"
-: 78:
-: 79:#define NAPTIME_PER_CYCLE 1000 /* max sleep time between cycles (1s) */
-: 80:
-: 81:typedef struct FlushPosition
-: 82:{
-: 83: dlist_node node;
-: 84: XLogRecPtr local_end;
-: 85: XLogRecPtr remote_end;
-: 86:} FlushPosition;
-: 87:
-: 88:static dlist_head lsn_mapping = DLIST_STATIC_INIT(lsn_mapping);
-: 89:
-: 90:typedef struct SlotErrCallbackArg
-: 91:{
-: 92: LogicalRepRelMapEntry *rel;
-: 93: int local_attnum;
-: 94: int remote_attnum;
-: 95:} SlotErrCallbackArg;
-: 96:
-: 97:static MemoryContext ApplyMessageContext = NULL;
-: 98:MemoryContext ApplyContext = NULL;
-: 99:
-: 100:WalReceiverConn *wrconn = NULL;
-: 101:
-: 102:Subscription *MySubscription = NULL;
-: 103:bool MySubscriptionValid = false;
-: 104:
-: 105:bool in_remote_transaction = false;
-: 106:static XLogRecPtr remote_final_lsn = InvalidXLogRecPtr;
-: 107:
-: 108:static void send_feedback(XLogRecPtr recvpos, bool force, bool requestReply);
-: 109:
-: 110:static void store_flush_position(XLogRecPtr remote_lsn);
-: 111:
-: 112:static void maybe_reread_subscription(void);
-: 113:
-: 114:/* Flags set by signal handlers */
-: 115:static volatile sig_atomic_t got_SIGHUP = false;
-: 116:
-: 117:/*
-: 118: * Should this worker apply changes for given relation.
-: 119: *
-: 120: * This is mainly needed for initial relation data sync as that runs in
-: 121: * separate worker process running in parallel and we need some way to skip
-: 122: * changes coming to the main apply worker during the sync of a table.
-: 123: *
-: 124: * Note we need to do smaller or equals comparison for SYNCDONE state because
-: 125: * it might hold position of end of initial slot consistent point WAL
-: 126: * record + 1 (ie start of next record) and next record can be COMMIT of
-: 127: * transaction we are now processing (which is what we set remote_final_lsn
-: 128: * to in apply_handle_begin).
-: 129: */
-: 130:static bool
function should_apply_changes_for_rel called 667 returned 100% blocks executed 90%
667: 131:should_apply_changes_for_rel(LogicalRepRelMapEntry *rel)
-: 132:{
667: 133: if (am_tablesync_worker())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 134: return MyLogicalRepWorker->relid == rel->localreloid;
-: 135: else
1335: 136: return (rel->state == SUBREL_STATE_READY ||
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
branch 2 taken 8% (fallthrough)
branch 3 taken 92%
14: 137: (rel->state == SUBREL_STATE_SYNCDONE &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
1: 138: rel->statelsn <= remote_final_lsn));
-: 139:}
-: 140:
-: 141:/*
-: 142: * Make sure that we started local transaction.
-: 143: *
-: 144: * Also switches to ApplyMessageContext as necessary.
-: 145: */
-: 146:static bool
function ensure_transaction called 667 returned 100% blocks executed 92%
667: 147:ensure_transaction(void)
-: 148:{
667: 149: if (IsTransactionState())
call 0 returned 100%
branch 1 taken 84% (fallthrough)
branch 2 taken 16%
-: 150: {
562: 151: SetCurrentStatementStartTimestamp();
call 0 returned 100%
-: 152:
562: 153: if (CurrentMemoryContext != ApplyMessageContext)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 154: MemoryContextSwitchTo(ApplyMessageContext);
call 0 never executed
-: 155:
562: 156: return false;
-: 157: }
-: 158:
105: 159: SetCurrentStatementStartTimestamp();
call 0 returned 100%
105: 160: StartTransactionCommand();
call 0 returned 100%
-: 161:
105: 162: maybe_reread_subscription();
call 0 returned 100%
-: 163:
105: 164: MemoryContextSwitchTo(ApplyMessageContext);
call 0 returned 100%
105: 165: return true;
-: 166:}
-: 167:
-: 168:
-: 169:/*
-: 170: * Executor state preparation for evaluation of constraint expressions,
-: 171: * indexes and triggers.
-: 172: *
-: 173: * This is based on similar code in copy.c
-: 174: */
-: 175:static EState *
function create_estate_for_relation called 649 returned 100% blocks executed 100%
649: 176:create_estate_for_relation(LogicalRepRelMapEntry *rel)
-: 177:{
-: 178: EState *estate;
-: 179: ResultRelInfo *resultRelInfo;
-: 180: RangeTblEntry *rte;
-: 181:
649: 182: estate = CreateExecutorState();
call 0 returned 100%
-: 183:
649: 184: rte = makeNode(RangeTblEntry);
call 0 returned 100%
649: 185: rte->rtekind = RTE_RELATION;
649: 186: rte->relid = RelationGetRelid(rel->localrel);
649: 187: rte->relkind = rel->localrel->rd_rel->relkind;
649: 188: rte->rellockmode = AccessShareLock;
649: 189: ExecInitRangeTable(estate, list_make1(rte));
call 0 returned 100%
call 1 returned 100%
-: 190:
649: 191: resultRelInfo = makeNode(ResultRelInfo);
call 0 returned 100%
649: 192: InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
call 0 returned 100%
-: 193:
649: 194: estate->es_result_relations = resultRelInfo;
649: 195: estate->es_num_result_relations = 1;
649: 196: estate->es_result_relation_info = resultRelInfo;
-: 197:
649: 198: estate->es_output_cid = GetCurrentCommandId(true);
call 0 returned 100%
-: 199:
-: 200: /* Prepare to catch AFTER triggers. */
649: 201: AfterTriggerBeginQuery();
call 0 returned 100%
-: 202:
649: 203: return estate;
-: 204:}
-: 205:
-: 206:/*
-: 207: * Executes default values for columns for which we can't map to remote
-: 208: * relation columns.
-: 209: *
-: 210: * This allows us to support tables which have more columns on the downstream
-: 211: * than on the upstream.
-: 212: */
-: 213:static void
function slot_fill_defaults called 351 returned 100% blocks executed 96%
351: 214:slot_fill_defaults(LogicalRepRelMapEntry *rel, EState *estate,
-: 215: TupleTableSlot *slot)
-: 216:{
351: 217: TupleDesc desc = RelationGetDescr(rel->localrel);
351: 218: int num_phys_attrs = desc->natts;
-: 219: int i;
-: 220: int attnum,
351: 221: num_defaults = 0;
-: 222: int *defmap;
-: 223: ExprState **defexprs;
-: 224: ExprContext *econtext;
-: 225:
351: 226: econtext = GetPerTupleExprContext(estate);
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
call 2 never executed
-: 227:
-: 228: /* We got all the data via replication, no need to evaluate anything. */
351: 229: if (num_phys_attrs == rel->remoterel.natts)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
698: 230: return;
-: 231:
4: 232: defmap = (int *) palloc(num_phys_attrs * sizeof(int));
call 0 returned 100%
4: 233: defexprs = (ExprState **) palloc(num_phys_attrs * sizeof(ExprState *));
call 0 returned 100%
-: 234:
16: 235: for (attnum = 0; attnum < num_phys_attrs; attnum++)
branch 0 taken 75%
branch 1 taken 25% (fallthrough)
-: 236: {
-: 237: Expr *defexpr;
-: 238:
12: 239: if (TupleDescAttr(desc, attnum)->attisdropped || TupleDescAttr(desc, attnum)->attgenerated)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 17% (fallthrough)
branch 3 taken 83%
2: 240: continue;
-: 241:
10: 242: if (rel->attrmap[attnum] >= 0)
branch 0 taken 60% (fallthrough)
branch 1 taken 40%
6: 243: continue;
-: 244:
4: 245: defexpr = (Expr *) build_column_default(rel->localrel, attnum + 1);
call 0 returned 100%
-: 246:
4: 247: if (defexpr != NULL)
branch 0 taken 75% (fallthrough)
branch 1 taken 25%
-: 248: {
-: 249: /* Run the expression through planner */
3: 250: defexpr = expression_planner(defexpr);
call 0 returned 100%
-: 251:
-: 252: /* Initialize executable expression in copycontext */
3: 253: defexprs[num_defaults] = ExecInitExpr(defexpr, NULL);
call 0 returned 100%
3: 254: defmap[num_defaults] = attnum;
3: 255: num_defaults++;
-: 256: }
-: 257:
-: 258: }
-: 259:
7: 260: for (i = 0; i < num_defaults; i++)
branch 0 taken 43%
branch 1 taken 57% (fallthrough)
6: 261: slot->tts_values[defmap[i]] =
3: 262: ExecEvalExpr(defexprs[i], econtext, &slot->tts_isnull[defmap[i]]);
call 0 returned 100%
-: 263:}
-: 264:
-: 265:/*
-: 266: * Error callback to give more context info about type conversion failure.
-: 267: */
-: 268:static void
function slot_store_error_callback called 0 returned 0% blocks executed 0%
#####: 269:slot_store_error_callback(void *arg)
-: 270:{
#####: 271: SlotErrCallbackArg *errarg = (SlotErrCallbackArg *) arg;
-: 272: LogicalRepRelMapEntry *rel;
-: 273: char *remotetypname;
-: 274: Oid remotetypoid,
-: 275: localtypoid;
-: 276:
-: 277: /* Nothing to do if remote attribute number is not set */
#####: 278: if (errarg->remote_attnum < 0)
branch 0 never executed
branch 1 never executed
#####: 279: return;
-: 280:
#####: 281: rel = errarg->rel;
#####: 282: remotetypoid = rel->remoterel.atttyps[errarg->remote_attnum];
-: 283:
-: 284: /* Fetch remote type name from the LogicalRepTypMap cache */
#####: 285: remotetypname = logicalrep_typmap_gettypname(remotetypoid);
call 0 never executed
-: 286:
-: 287: /* Fetch local type OID from the local sys cache */
#####: 288: localtypoid = get_atttype(rel->localreloid, errarg->local_attnum + 1);
call 0 never executed
-: 289:
#####: 290: errcontext("processing remote data for replication target relation \"%s.%s\" column \"%s\", "
call 0 never executed
call 1 never executed
call 2 never executed
-: 291: "remote type %s, local type %s",
-: 292: rel->remoterel.nspname, rel->remoterel.relname,
#####: 293: rel->remoterel.attnames[errarg->remote_attnum],
-: 294: remotetypname,
-: 295: format_type_be(localtypoid));
-: 296:}
-: 297:
-: 298:/*
-: 299: * Store data in C string form into slot.
-: 300: * This is similar to BuildTupleFromCStrings but TupleTableSlot fits our
-: 301: * use better.
-: 302: */
-: 303:static void
function slot_store_cstrings called 649 returned 100% blocks executed 100%
649: 304:slot_store_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
-: 305: char **values)
-: 306:{
649: 307: int natts = slot->tts_tupleDescriptor->natts;
-: 308: int i;
-: 309: SlotErrCallbackArg errarg;
-: 310: ErrorContextCallback errcallback;
-: 311:
649: 312: ExecClearTuple(slot);
call 0 returned 100%
-: 313:
-: 314: /* Push callback + info on the error context stack */
649: 315: errarg.rel = rel;
649: 316: errarg.local_attnum = -1;
649: 317: errarg.remote_attnum = -1;
649: 318: errcallback.callback = slot_store_error_callback;
649: 319: errcallback.arg = (void *) &errarg;
649: 320: errcallback.previous = error_context_stack;
649: 321: error_context_stack = &errcallback;
-: 322:
-: 323: /* Call the "in" function for each non-dropped attribute */
1599: 324: for (i = 0; i < natts; i++)
branch 0 taken 59%
branch 1 taken 41% (fallthrough)
-: 325: {
950: 326: Form_pg_attribute att = TupleDescAttr(slot->tts_tupleDescriptor, i);
950: 327: int remoteattnum = rel->attrmap[i];
-: 328:
1881: 329: if (!att->attisdropped && remoteattnum >= 0 &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 98% (fallthrough)
branch 3 taken 2%
branch 4 taken 85% (fallthrough)
branch 5 taken 15%
931: 330: values[remoteattnum] != NULL)
788: 331: {
-: 332: Oid typinput;
-: 333: Oid typioparam;
-: 334:
788: 335: errarg.local_attnum = i;
788: 336: errarg.remote_attnum = remoteattnum;
-: 337:
788: 338: getTypeInputInfo(att->atttypid, &typinput, &typioparam);
call 0 returned 100%
1576: 339: slot->tts_values[i] =
788: 340: OidInputFunctionCall(typinput, values[remoteattnum],
call 0 returned 100%
-: 341: typioparam, att->atttypmod);
788: 342: slot->tts_isnull[i] = false;
-: 343:
788: 344: errarg.local_attnum = -1;
788: 345: errarg.remote_attnum = -1;
-: 346: }
-: 347: else
-: 348: {
-: 349: /*
-: 350: * We assign NULL to dropped attributes, NULL values, and missing
-: 351: * values (missing values should be later filled using
-: 352: * slot_fill_defaults).
-: 353: */
162: 354: slot->tts_values[i] = (Datum) 0;
162: 355: slot->tts_isnull[i] = true;
-: 356: }
-: 357: }
-: 358:
-: 359: /* Pop the error context stack */
649: 360: error_context_stack = errcallback.previous;
-: 361:
649: 362: ExecStoreVirtualTuple(slot);
call 0 returned 100%
649: 363:}
-: 364:
-: 365:/*
-: 366: * Modify slot with user data provided as C strings.
-: 367: * This is somewhat similar to heap_modify_tuple but also calls the type
-: 368: * input function on the user data as the input is the text representation
-: 369: * of the types.
-: 370: */
-: 371:static void
function slot_modify_cstrings called 107 returned 100% blocks executed 94%
107: 372:slot_modify_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
-: 373: char **values, bool *replaces)
-: 374:{
107: 375: int natts = slot->tts_tupleDescriptor->natts;
-: 376: int i;
-: 377: SlotErrCallbackArg errarg;
-: 378: ErrorContextCallback errcallback;
-: 379:
107: 380: slot_getallattrs(slot);
call 0 returned 100%
107: 381: ExecClearTuple(slot);
call 0 returned 100%
-: 382:
-: 383: /* Push callback + info on the error context stack */
107: 384: errarg.rel = rel;
107: 385: errarg.local_attnum = -1;
107: 386: errarg.remote_attnum = -1;
107: 387: errcallback.callback = slot_store_error_callback;
107: 388: errcallback.arg = (void *) &errarg;
107: 389: errcallback.previous = error_context_stack;
107: 390: error_context_stack = &errcallback;
-: 391:
-: 392: /* Call the "in" function for each replaced attribute */
300: 393: for (i = 0; i < natts; i++)
branch 0 taken 64%
branch 1 taken 36% (fallthrough)
-: 394: {
193: 395: Form_pg_attribute att = TupleDescAttr(slot->tts_tupleDescriptor, i);
193: 396: int remoteattnum = rel->attrmap[i];
-: 397:
193: 398: if (remoteattnum < 0)
branch 0 taken 7% (fallthrough)
branch 1 taken 93%
13: 399: continue;
-: 400:
180: 401: if (!replaces[remoteattnum])
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 402: continue;
-: 403:
180: 404: if (values[remoteattnum] != NULL)
branch 0 taken 88% (fallthrough)
branch 1 taken 12%
-: 405: {
-: 406: Oid typinput;
-: 407: Oid typioparam;
-: 408:
158: 409: errarg.local_attnum = i;
158: 410: errarg.remote_attnum = remoteattnum;
-: 411:
158: 412: getTypeInputInfo(att->atttypid, &typinput, &typioparam);
call 0 returned 100%
316: 413: slot->tts_values[i] =
158: 414: OidInputFunctionCall(typinput, values[remoteattnum],
call 0 returned 100%
-: 415: typioparam, att->atttypmod);
158: 416: slot->tts_isnull[i] = false;
-: 417:
158: 418: errarg.local_attnum = -1;
158: 419: errarg.remote_attnum = -1;
-: 420: }
-: 421: else
-: 422: {
22: 423: slot->tts_values[i] = (Datum) 0;
22: 424: slot->tts_isnull[i] = true;
-: 425: }
-: 426: }
-: 427:
-: 428: /* Pop the error context stack */
107: 429: error_context_stack = errcallback.previous;
-: 430:
107: 431: ExecStoreVirtualTuple(slot);
call 0 returned 100%
107: 432:}
-: 433:
-: 434:/*
-: 435: * Handle BEGIN message.
-: 436: */
-: 437:static void
function apply_handle_begin called 135 returned 100% blocks executed 100%
135: 438:apply_handle_begin(StringInfo s)
-: 439:{
-: 440: LogicalRepBeginData begin_data;
-: 441:
135: 442: logicalrep_read_begin(s, &begin_data);
call 0 returned 100%
-: 443:
135: 444: remote_final_lsn = begin_data.final_lsn;
-: 445:
135: 446: in_remote_transaction = true;
-: 447:
135: 448: pgstat_report_activity(STATE_RUNNING, NULL);
call 0 returned 100%
135: 449:}
-: 450:
-: 451:/*
-: 452: * Handle COMMIT message.
-: 453: *
-: 454: * TODO, support tracking of multiple origins
-: 455: */
-: 456:static void
function apply_handle_commit called 134 returned 100% blocks executed 93%
134: 457:apply_handle_commit(StringInfo s)
-: 458:{
-: 459: LogicalRepCommitData commit_data;
-: 460:
134: 461: logicalrep_read_commit(s, &commit_data);
call 0 returned 100%
-: 462:
134: 463: Assert(commit_data.commit_lsn == remote_final_lsn);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 464:
-: 465: /* The synchronization worker runs in single transaction. */
134: 466: if (IsTransactionState() && !am_tablesync_worker())
call 0 returned 100%
branch 1 taken 78% (fallthrough)
branch 2 taken 22%
call 3 returned 100%
branch 4 taken 100% (fallthrough)
branch 5 taken 0%
-: 467: {
-: 468: /*
-: 469: * Update origin state so we can restart streaming from correct
-: 470: * position in case of crash.
-: 471: */
104: 472: replorigin_session_origin_lsn = commit_data.end_lsn;
104: 473: replorigin_session_origin_timestamp = commit_data.committime;
-: 474:
104: 475: CommitTransactionCommand();
call 0 returned 100%
104: 476: pgstat_report_stat(false);
call 0 returned 100%
-: 477:
104: 478: store_flush_position(commit_data.end_lsn);
call 0 returned 100%
-: 479: }
-: 480: else
-: 481: {
-: 482: /* Process any invalidation messages that might have accumulated. */
30: 483: AcceptInvalidationMessages();
call 0 returned 100%
30: 484: maybe_reread_subscription();
call 0 returned 100%
-: 485: }
-: 486:
134: 487: in_remote_transaction = false;
-: 488:
-: 489: /* Process any tables that are being synchronized in parallel. */
134: 490: process_syncing_tables(commit_data.end_lsn);
call 0 returned 100%
-: 491:
134: 492: pgstat_report_activity(STATE_IDLE, NULL);
call 0 returned 100%
134: 493:}
-: 494:
-: 495:/*
-: 496: * Handle ORIGIN message.
-: 497: *
-: 498: * TODO, support tracking of multiple origins
-: 499: */
-: 500:static void
function apply_handle_origin called 0 returned 0% blocks executed 0%
#####: 501:apply_handle_origin(StringInfo s)
-: 502:{
-: 503: /*
-: 504: * ORIGIN message can only come inside remote transaction and before any
-: 505: * actual writes.
-: 506: */
#####: 507: if (!in_remote_transaction ||
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
#####: 508: (IsTransactionState() && !am_tablesync_worker()))
call 0 never executed
call 1 never executed
branch 2 never executed
branch 3 never executed
#####: 509: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 510: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 511: errmsg("ORIGIN message sent out of order")));
#####: 512:}
-: 513:
-: 514:/*
-: 515: * Handle RELATION message.
-: 516: *
-: 517: * Note we don't do validation against local schema here. The validation
-: 518: * against local schema is postponed until first change for given relation
-: 519: * comes as we only care about it when applying changes for it anyway and we
-: 520: * do less locking this way.
-: 521: */
-: 522:static void
function apply_handle_relation called 44 returned 100% blocks executed 100%
44: 523:apply_handle_relation(StringInfo s)
-: 524:{
-: 525: LogicalRepRelation *rel;
-: 526:
44: 527: rel = logicalrep_read_rel(s);
call 0 returned 100%
44: 528: logicalrep_relmap_update(rel);
call 0 returned 100%
44: 529:}
-: 530:
-: 531:/*
-: 532: * Handle TYPE message.
-: 533: *
-: 534: * Note we don't do local mapping here, that's done when the type is
-: 535: * actually used.
-: 536: */
-: 537:static void
function apply_handle_type called 16 returned 100% blocks executed 100%
16: 538:apply_handle_type(StringInfo s)
-: 539:{
-: 540: LogicalRepTyp typ;
-: 541:
16: 542: logicalrep_read_typ(s, &typ);
call 0 returned 100%
16: 543: logicalrep_typmap_update(&typ);
call 0 returned 100%
16: 544:}
-: 545:
-: 546:/*
-: 547: * Get replica identity index or if it is not defined a primary key.
-: 548: *
-: 549: * If neither is defined, returns InvalidOid
-: 550: */
-: 551:static Oid
function GetRelationIdentityOrPK called 298 returned 100% blocks executed 100%
298: 552:GetRelationIdentityOrPK(Relation rel)
-: 553:{
-: 554: Oid idxoid;
-: 555:
298: 556: idxoid = RelationGetReplicaIndex(rel);
call 0 returned 100%
-: 557:
298: 558: if (!OidIsValid(idxoid))
branch 0 taken 41% (fallthrough)
branch 1 taken 59%
122: 559: idxoid = RelationGetPrimaryKeyIndex(rel);
call 0 returned 100%
-: 560:
298: 561: return idxoid;
-: 562:}
-: 563:
-: 564:/*
-: 565: * Handle INSERT message.
-: 566: */
-: 567:static void
function apply_handle_insert called 364 returned 99% blocks executed 97%
364: 568:apply_handle_insert(StringInfo s)
-: 569:{
-: 570: LogicalRepRelMapEntry *rel;
-: 571: LogicalRepTupleData newtup;
-: 572: LogicalRepRelId relid;
-: 573: EState *estate;
-: 574: TupleTableSlot *remoteslot;
-: 575: MemoryContext oldctx;
-: 576:
364: 577: ensure_transaction();
call 0 returned 100%
-: 578:
364: 579: relid = logicalrep_read_insert(s, &newtup);
call 0 returned 100%
364: 580: rel = logicalrep_rel_open(relid, RowExclusiveLock);
call 0 returned 99%
363: 581: if (!should_apply_changes_for_rel(rel))
call 0 returned 100%
branch 1 taken 3% (fallthrough)
branch 2 taken 97%
-: 582: {
-: 583: /*
-: 584: * The relation can't become interesting in the middle of the
-: 585: * transaction so it's safe to unlock it.
-: 586: */
12: 587: logicalrep_rel_close(rel, RowExclusiveLock);
call 0 returned 100%
375: 588: return;
-: 589: }
-: 590:
-: 591: /* Initialize the executor state. */
351: 592: estate = create_estate_for_relation(rel);
call 0 returned 100%
351: 593: remoteslot = ExecInitExtraTupleSlot(estate,
call 0 returned 100%
351: 594: RelationGetDescr(rel->localrel),
-: 595: &TTSOpsVirtual);
-: 596:
-: 597: /* Input functions may need an active snapshot, so get one */
351: 598: PushActiveSnapshot(GetTransactionSnapshot());
call 0 returned 100%
call 1 returned 100%
-: 599:
-: 600: /* Process and store remote tuple in the slot */
351: 601: oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 returned 100%
call 3 returned 100%
351: 602: slot_store_cstrings(remoteslot, rel, newtup.values);
call 0 returned 100%
351: 603: slot_fill_defaults(rel, estate, remoteslot);
call 0 returned 100%
351: 604: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 605:
351: 606: ExecOpenIndices(estate->es_result_relation_info, false);
call 0 returned 100%
-: 607:
-: 608: /* Do the insert. */
351: 609: ExecSimpleRelationInsert(estate, remoteslot);
call 0 returned 100%
-: 610:
-: 611: /* Cleanup. */
351: 612: ExecCloseIndices(estate->es_result_relation_info);
call 0 returned 100%
351: 613: PopActiveSnapshot();
call 0 returned 100%
-: 614:
-: 615: /* Handle queued AFTER triggers. */
351: 616: AfterTriggerEndQuery(estate);
call 0 returned 100%
-: 617:
351: 618: ExecResetTupleTable(estate->es_tupleTable, false);
call 0 returned 100%
351: 619: FreeExecutorState(estate);
call 0 returned 100%
-: 620:
351: 621: logicalrep_rel_close(rel, NoLock);
call 0 returned 100%
-: 622:
351: 623: CommandCounterIncrement();
call 0 returned 100%
-: 624:}
-: 625:
-: 626:/*
-: 627: * Check if the logical replication relation is updatable and throw
-: 628: * appropriate error if it isn't.
-: 629: */
-: 630:static void
function check_relation_updatable called 298 returned 100% blocks executed 16%
298: 631:check_relation_updatable(LogicalRepRelMapEntry *rel)
-: 632:{
-: 633: /* Updatable, no error. */
298: 634: if (rel->updatable)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
596: 635: return;
-: 636:
-: 637: /*
-: 638: * We are in error mode so it's fine this is somewhat slow. It's better to
-: 639: * give user correct error.
-: 640: */
#####: 641: if (OidIsValid(GetRelationIdentityOrPK(rel->localrel)))
call 0 never executed
branch 1 never executed
branch 2 never executed
-: 642: {
#####: 643: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 644: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 645: errmsg("publisher did not send replica identity column "
-: 646: "expected by the logical replication target relation \"%s.%s\"",
-: 647: rel->remoterel.nspname, rel->remoterel.relname)));
-: 648: }
-: 649:
#####: 650: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 651: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 652: errmsg("logical replication target relation \"%s.%s\" has "
-: 653: "neither REPLICA IDENTITY index nor PRIMARY "
-: 654: "KEY and published relation does not have "
-: 655: "REPLICA IDENTITY FULL",
-: 656: rel->remoterel.nspname, rel->remoterel.relname)));
-: 657:}
-: 658:
-: 659:/*
-: 660: * Handle UPDATE message.
-: 661: *
-: 662: * TODO: FDW support
-: 663: */
-: 664:static void
function apply_handle_update called 107 returned 100% blocks executed 87%
107: 665:apply_handle_update(StringInfo s)
-: 666:{
-: 667: LogicalRepRelMapEntry *rel;
-: 668: LogicalRepRelId relid;
-: 669: Oid idxoid;
-: 670: EState *estate;
-: 671: EPQState epqstate;
-: 672: LogicalRepTupleData oldtup;
-: 673: LogicalRepTupleData newtup;
-: 674: bool has_oldtup;
-: 675: TupleTableSlot *localslot;
-: 676: TupleTableSlot *remoteslot;
-: 677: bool found;
-: 678: MemoryContext oldctx;
-: 679:
107: 680: ensure_transaction();
call 0 returned 100%
-: 681:
107: 682: relid = logicalrep_read_update(s, &has_oldtup, &oldtup,
call 0 returned 100%
-: 683: &newtup);
107: 684: rel = logicalrep_rel_open(relid, RowExclusiveLock);
call 0 returned 100%
107: 685: if (!should_apply_changes_for_rel(rel))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 686: {
-: 687: /*
-: 688: * The relation can't become interesting in the middle of the
-: 689: * transaction so it's safe to unlock it.
-: 690: */
#####: 691: logicalrep_rel_close(rel, RowExclusiveLock);
call 0 never executed
107: 692: return;
-: 693: }
-: 694:
-: 695: /* Check if we can do the update. */
107: 696: check_relation_updatable(rel);
call 0 returned 100%
-: 697:
-: 698: /* Initialize the executor state. */
107: 699: estate = create_estate_for_relation(rel);
call 0 returned 100%
107: 700: remoteslot = ExecInitExtraTupleSlot(estate,
call 0 returned 100%
107: 701: RelationGetDescr(rel->localrel),
-: 702: &TTSOpsVirtual);
107: 703: localslot = table_slot_create(rel->localrel,
call 0 returned 100%
-: 704: &estate->es_tupleTable);
107: 705: EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1);
call 0 returned 100%
-: 706:
107: 707: PushActiveSnapshot(GetTransactionSnapshot());
call 0 returned 100%
call 1 returned 100%
107: 708: ExecOpenIndices(estate->es_result_relation_info, false);
call 0 returned 100%
-: 709:
-: 710: /* Build the search tuple. */
107: 711: oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 returned 100%
call 3 returned 100%
107: 712: slot_store_cstrings(remoteslot, rel,
branch 0 taken 59% (fallthrough)
branch 1 taken 41%
call 2 returned 100%
-: 713: has_oldtup ? oldtup.values : newtup.values);
107: 714: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 715:
-: 716: /*
-: 717: * Try to find tuple using either replica identity index, primary key or
-: 718: * if needed, sequential scan.
-: 719: */
107: 720: idxoid = GetRelationIdentityOrPK(rel->localrel);
call 0 returned 100%
107: 721: Assert(OidIsValid(idxoid) ||
branch 0 taken 21% (fallthrough)
branch 1 taken 79%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 0% (fallthrough)
branch 5 taken 100%
call 6 never executed
-: 722: (rel->remoterel.replident == REPLICA_IDENTITY_FULL && has_oldtup));
-: 723:
107: 724: if (OidIsValid(idxoid))
branch 0 taken 79% (fallthrough)
branch 1 taken 21%
85: 725: found = RelationFindReplTupleByIndex(rel->localrel, idxoid,
call 0 returned 100%
-: 726: LockTupleExclusive,
-: 727: remoteslot, localslot);
-: 728: else
22: 729: found = RelationFindReplTupleSeq(rel->localrel, LockTupleExclusive,
call 0 returned 100%
-: 730: remoteslot, localslot);
-: 731:
107: 732: ExecClearTuple(remoteslot);
call 0 returned 100%
-: 733:
-: 734: /*
-: 735: * Tuple found.
-: 736: *
-: 737: * Note this will fail if there are other conflicting unique indexes.
-: 738: */
107: 739: if (found)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 740: {
-: 741: /* Process and store remote tuple in the slot */
107: 742: oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
call 2 never executed
call 3 returned 100%
107: 743: ExecCopySlot(remoteslot, localslot);
call 0 returned 100%
107: 744: slot_modify_cstrings(remoteslot, rel, newtup.values, newtup.changed);
call 0 returned 100%
107: 745: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 746:
107: 747: EvalPlanQualSetSlot(&epqstate, remoteslot);
-: 748:
-: 749: /* Do the actual update. */
107: 750: ExecSimpleRelationUpdate(estate, &epqstate, localslot, remoteslot);
call 0 returned 100%
-: 751: }
-: 752: else
-: 753: {
-: 754: /*
-: 755: * The tuple to be updated could not be found.
-: 756: *
-: 757: * TODO what to do here, change the log level to LOG perhaps?
-: 758: */
#####: 759: elog(DEBUG1,
call 0 never executed
call 1 never executed
-: 760: "logical replication did not find row for update "
-: 761: "in replication target relation \"%s\"",
-: 762: RelationGetRelationName(rel->localrel));
-: 763: }
-: 764:
-: 765: /* Cleanup. */
107: 766: ExecCloseIndices(estate->es_result_relation_info);
call 0 returned 100%
107: 767: PopActiveSnapshot();
call 0 returned 100%
-: 768:
-: 769: /* Handle queued AFTER triggers. */
107: 770: AfterTriggerEndQuery(estate);
call 0 returned 100%
-: 771:
107: 772: EvalPlanQualEnd(&epqstate);
call 0 returned 100%
107: 773: ExecResetTupleTable(estate->es_tupleTable, false);
call 0 returned 100%
107: 774: FreeExecutorState(estate);
call 0 returned 100%
-: 775:
107: 776: logicalrep_rel_close(rel, NoLock);
call 0 returned 100%
-: 777:
107: 778: CommandCounterIncrement();
call 0 returned 100%
-: 779:}
-: 780:
-: 781:/*
-: 782: * Handle DELETE message.
-: 783: *
-: 784: * TODO: FDW support
-: 785: */
-: 786:static void
function apply_handle_delete called 191 returned 100% blocks executed 86%
191: 787:apply_handle_delete(StringInfo s)
-: 788:{
-: 789: LogicalRepRelMapEntry *rel;
-: 790: LogicalRepTupleData oldtup;
-: 791: LogicalRepRelId relid;
-: 792: Oid idxoid;
-: 793: EState *estate;
-: 794: EPQState epqstate;
-: 795: TupleTableSlot *remoteslot;
-: 796: TupleTableSlot *localslot;
-: 797: bool found;
-: 798: MemoryContext oldctx;
-: 799:
191: 800: ensure_transaction();
call 0 returned 100%
-: 801:
191: 802: relid = logicalrep_read_delete(s, &oldtup);
call 0 returned 100%
191: 803: rel = logicalrep_rel_open(relid, RowExclusiveLock);
call 0 returned 100%
191: 804: if (!should_apply_changes_for_rel(rel))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 805: {
-: 806: /*
-: 807: * The relation can't become interesting in the middle of the
-: 808: * transaction so it's safe to unlock it.
-: 809: */
#####: 810: logicalrep_rel_close(rel, RowExclusiveLock);
call 0 never executed
191: 811: return;
-: 812: }
-: 813:
-: 814: /* Check if we can do the delete. */
191: 815: check_relation_updatable(rel);
call 0 returned 100%
-: 816:
-: 817: /* Initialize the executor state. */
191: 818: estate = create_estate_for_relation(rel);
call 0 returned 100%
191: 819: remoteslot = ExecInitExtraTupleSlot(estate,
call 0 returned 100%
191: 820: RelationGetDescr(rel->localrel),
-: 821: &TTSOpsVirtual);
191: 822: localslot = table_slot_create(rel->localrel,
call 0 returned 100%
-: 823: &estate->es_tupleTable);
191: 824: EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1);
call 0 returned 100%
-: 825:
191: 826: PushActiveSnapshot(GetTransactionSnapshot());
call 0 returned 100%
call 1 returned 100%
191: 827: ExecOpenIndices(estate->es_result_relation_info, false);
call 0 returned 100%
-: 828:
-: 829: /* Find the tuple using the replica identity index. */
191: 830: oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 returned 100%
call 3 returned 100%
191: 831: slot_store_cstrings(remoteslot, rel, oldtup.values);
call 0 returned 100%
191: 832: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 833:
-: 834: /*
-: 835: * Try to find tuple using either replica identity index, primary key or
-: 836: * if needed, sequential scan.
-: 837: */
191: 838: idxoid = GetRelationIdentityOrPK(rel->localrel);
call 0 returned 100%
191: 839: Assert(OidIsValid(idxoid) ||
branch 0 taken 52% (fallthrough)
branch 1 taken 48%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
call 4 never executed
-: 840: (rel->remoterel.replident == REPLICA_IDENTITY_FULL));
-: 841:
191: 842: if (OidIsValid(idxoid))
branch 0 taken 48% (fallthrough)
branch 1 taken 52%
91: 843: found = RelationFindReplTupleByIndex(rel->localrel, idxoid,
call 0 returned 100%
-: 844: LockTupleExclusive,
-: 845: remoteslot, localslot);
-: 846: else
100: 847: found = RelationFindReplTupleSeq(rel->localrel, LockTupleExclusive,
call 0 returned 100%
-: 848: remoteslot, localslot);
-: 849: /* If found delete it. */
191: 850: if (found)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 851: {
191: 852: EvalPlanQualSetSlot(&epqstate, localslot);
-: 853:
-: 854: /* Do the actual delete. */
191: 855: ExecSimpleRelationDelete(estate, &epqstate, localslot);
call 0 returned 100%
-: 856: }
-: 857: else
-: 858: {
-: 859: /* The tuple to be deleted could not be found. */
#####: 860: elog(DEBUG1,
call 0 never executed
call 1 never executed
-: 861: "logical replication could not find row for delete "
-: 862: "in replication target relation \"%s\"",
-: 863: RelationGetRelationName(rel->localrel));
-: 864: }
-: 865:
-: 866: /* Cleanup. */
191: 867: ExecCloseIndices(estate->es_result_relation_info);
call 0 returned 100%
191: 868: PopActiveSnapshot();
call 0 returned 100%
-: 869:
-: 870: /* Handle queued AFTER triggers. */
191: 871: AfterTriggerEndQuery(estate);
call 0 returned 100%
-: 872:
191: 873: EvalPlanQualEnd(&epqstate);
call 0 returned 100%
191: 874: ExecResetTupleTable(estate->es_tupleTable, false);
call 0 returned 100%
191: 875: FreeExecutorState(estate);
call 0 returned 100%
-: 876:
191: 877: logicalrep_rel_close(rel, NoLock);
call 0 returned 100%
-: 878:
191: 879: CommandCounterIncrement();
call 0 returned 100%
-: 880:}
-: 881:
-: 882:/*
-: 883: * Handle TRUNCATE message.
-: 884: *
-: 885: * TODO: FDW support
-: 886: */
-: 887:static void
function apply_handle_truncate called 5 returned 100% blocks executed 94%
5: 888:apply_handle_truncate(StringInfo s)
-: 889:{
5: 890: bool cascade = false;
5: 891: bool restart_seqs = false;
5: 892: List *remote_relids = NIL;
5: 893: List *remote_rels = NIL;
5: 894: List *rels = NIL;
5: 895: List *relids = NIL;
5: 896: List *relids_logged = NIL;
-: 897: ListCell *lc;
-: 898:
5: 899: ensure_transaction();
call 0 returned 100%
-: 900:
5: 901: remote_relids = logicalrep_read_truncate(s, &cascade, &restart_seqs);
call 0 returned 100%
-: 902:
11: 903: foreach(lc, remote_relids)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 55% (fallthrough)
branch 3 taken 45%
branch 4 taken 55%
branch 5 taken 45% (fallthrough)
-: 904: {
6: 905: LogicalRepRelId relid = lfirst_oid(lc);
-: 906: LogicalRepRelMapEntry *rel;
-: 907:
6: 908: rel = logicalrep_rel_open(relid, RowExclusiveLock);
call 0 returned 100%
6: 909: if (!should_apply_changes_for_rel(rel))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 910: {
-: 911: /*
-: 912: * The relation can't become interesting in the middle of the
-: 913: * transaction so it's safe to unlock it.
-: 914: */
#####: 915: logicalrep_rel_close(rel, RowExclusiveLock);
call 0 never executed
#####: 916: continue;
-: 917: }
-: 918:
6: 919: remote_rels = lappend(remote_rels, rel);
call 0 returned 100%
6: 920: rels = lappend(rels, rel->localrel);
call 0 returned 100%
6: 921: relids = lappend_oid(relids, rel->localreloid);
call 0 returned 100%
6: 922: if (RelationIsLogicallyLogged(rel->localrel))
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
call 4 returned 100%
branch 5 taken 100% (fallthrough)
branch 6 taken 0%
6: 923: relids_logged = lappend_oid(relids_logged, rel->localreloid);
call 0 returned 100%
-: 924: }
-: 925:
-: 926: /*
-: 927: * Even if we used CASCADE on the upstream master we explicitly default to
-: 928: * replaying changes without further cascading. This might be later
-: 929: * changeable with a user specified option.
-: 930: */
5: 931: ExecuteTruncateGuts(rels, relids, relids_logged, DROP_RESTRICT, restart_seqs);
call 0 returned 100%
-: 932:
11: 933: foreach(lc, remote_rels)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 55% (fallthrough)
branch 3 taken 45%
branch 4 taken 55%
branch 5 taken 45% (fallthrough)
-: 934: {
6: 935: LogicalRepRelMapEntry *rel = lfirst(lc);
-: 936:
6: 937: logicalrep_rel_close(rel, NoLock);
call 0 returned 100%
-: 938: }
-: 939:
5: 940: CommandCounterIncrement();
call 0 returned 100%
5: 941:}
-: 942:
-: 943:
-: 944:/*
-: 945: * Logical replication protocol message dispatcher.
-: 946: */
-: 947:static void
function apply_dispatch called 996 returned 99% blocks executed 68%
996: 948:apply_dispatch(StringInfo s)
-: 949:{
996: 950: char action = pq_getmsgbyte(s);
call 0 returned 100%
-: 951:
996: 952: switch (action)
branch 0 taken 14%
branch 1 taken 13%
branch 2 taken 37%
branch 3 taken 11%
branch 4 taken 19%
branch 5 taken 1%
branch 6 taken 4%
branch 7 taken 2%
branch 8 taken 0%
branch 9 taken 0%
-: 953: {
-: 954: /* BEGIN */
-: 955: case 'B':
135: 956: apply_handle_begin(s);
call 0 returned 100%
135: 957: break;
-: 958: /* COMMIT */
-: 959: case 'C':
134: 960: apply_handle_commit(s);
call 0 returned 100%
134: 961: break;
-: 962: /* INSERT */
-: 963: case 'I':
364: 964: apply_handle_insert(s);
call 0 returned 99%
363: 965: break;
-: 966: /* UPDATE */
-: 967: case 'U':
107: 968: apply_handle_update(s);
call 0 returned 100%
107: 969: break;
-: 970: /* DELETE */
-: 971: case 'D':
191: 972: apply_handle_delete(s);
call 0 returned 100%
191: 973: break;
-: 974: /* TRUNCATE */
-: 975: case 'T':
5: 976: apply_handle_truncate(s);
call 0 returned 100%
5: 977: break;
-: 978: /* RELATION */
-: 979: case 'R':
44: 980: apply_handle_relation(s);
call 0 returned 100%
44: 981: break;
-: 982: /* TYPE */
-: 983: case 'Y':
16: 984: apply_handle_type(s);
call 0 returned 100%
16: 985: break;
-: 986: /* ORIGIN */
-: 987: case 'O':
#####: 988: apply_handle_origin(s);
call 0 never executed
#####: 989: break;
-: 990: default:
#####: 991: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 992: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 993: errmsg("invalid logical replication message type \"%c\"", action)));
-: 994: }
995: 995:}
-: 996:
-: 997:/*
-: 998: * Figure out which write/flush positions to report to the walsender process.
-: 999: *
-: 1000: * We can't simply report back the last LSN the walsender sent us because the
-: 1001: * local transaction might not yet be flushed to disk locally. Instead we
-: 1002: * build a list that associates local with remote LSNs for every commit. When
-: 1003: * reporting back the flush position to the sender we iterate that list and
-: 1004: * check which entries on it are already locally flushed. Those we can report
-: 1005: * as having been flushed.
-: 1006: *
-: 1007: * The have_pending_txes is true if there are outstanding transactions that
-: 1008: * need to be flushed.
-: 1009: */
-: 1010:static void
function get_flush_position called 1816 returned 100% blocks executed 94%
1816: 1011:get_flush_position(XLogRecPtr *write, XLogRecPtr *flush,
-: 1012: bool *have_pending_txes)
-: 1013:{
-: 1014: dlist_mutable_iter iter;
1816: 1015: XLogRecPtr local_flush = GetFlushRecPtr();
call 0 returned 100%
-: 1016:
1816: 1017: *write = InvalidXLogRecPtr;
1816: 1018: *flush = InvalidXLogRecPtr;
-: 1019:
1914: 1020: dlist_foreach_modify(iter, &lsn_mapping)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 85%
branch 3 taken 15% (fallthrough)
-: 1021: {
1618: 1022: FlushPosition *pos =
1618: 1023: dlist_container(FlushPosition, node, iter.cur);
-: 1024:
1618: 1025: *write = pos->remote_end;
-: 1026:
1618: 1027: if (pos->local_end <= local_flush)
branch 0 taken 6% (fallthrough)
branch 1 taken 94%
-: 1028: {
98: 1029: *flush = pos->remote_end;
98: 1030: dlist_delete(iter.cur);
call 0 returned 100%
98: 1031: pfree(pos);
call 0 returned 100%
-: 1032: }
-: 1033: else
-: 1034: {
-: 1035: /*
-: 1036: * Don't want to uselessly iterate over the rest of the list which
-: 1037: * could potentially be long. Instead get the last element and
-: 1038: * grab the write position from there.
-: 1039: */
1520: 1040: pos = dlist_tail_element(FlushPosition, node,
call 0 returned 100%
-: 1041: &lsn_mapping);
1520: 1042: *write = pos->remote_end;
1520: 1043: *have_pending_txes = true;
3040: 1044: return;
-: 1045: }
-: 1046: }
-: 1047:
296: 1048: *have_pending_txes = !dlist_is_empty(&lsn_mapping);
call 0 returned 100%
-: 1049:}
-: 1050:
-: 1051:/*
-: 1052: * Store current remote/local lsn pair in the tracking list.
-: 1053: */
-: 1054:static void
function store_flush_position called 104 returned 100% blocks executed 100%
104: 1055:store_flush_position(XLogRecPtr remote_lsn)
-: 1056:{
-: 1057: FlushPosition *flushpos;
-: 1058:
-: 1059: /* Need to do this in permanent context */
104: 1060: MemoryContextSwitchTo(ApplyContext);
call 0 returned 100%
-: 1061:
-: 1062: /* Track commit lsn */
104: 1063: flushpos = (FlushPosition *) palloc(sizeof(FlushPosition));
call 0 returned 100%
104: 1064: flushpos->local_end = XactLastCommitEnd;
104: 1065: flushpos->remote_end = remote_lsn;
-: 1066:
104: 1067: dlist_push_tail(&lsn_mapping, &flushpos->node);
call 0 returned 100%
104: 1068: MemoryContextSwitchTo(ApplyMessageContext);
call 0 returned 100%
104: 1069:}
-: 1070:
-: 1071:
-: 1072:/* Update statistics of the worker. */
-: 1073:static void
function UpdateWorkerStats called 1809 returned 100% blocks executed 100%
1809: 1074:UpdateWorkerStats(XLogRecPtr last_lsn, TimestampTz send_time, bool reply)
-: 1075:{
1809: 1076: MyLogicalRepWorker->last_lsn = last_lsn;
1809: 1077: MyLogicalRepWorker->last_send_time = send_time;
1809: 1078: MyLogicalRepWorker->last_recv_time = GetCurrentTimestamp();
call 0 returned 100%
1809: 1079: if (reply)
branch 0 taken 45% (fallthrough)
branch 1 taken 55%
-: 1080: {
813: 1081: MyLogicalRepWorker->reply_lsn = last_lsn;
813: 1082: MyLogicalRepWorker->reply_time = send_time;
-: 1083: }
1809: 1084:}
-: 1085:
-: 1086:/*
-: 1087: * Apply main loop.
-: 1088: */
-: 1089:static void
function LogicalRepApplyLoop called 26 returned 0% blocks executed 84%
26: 1090:LogicalRepApplyLoop(XLogRecPtr last_received)
-: 1091:{
26: 1092: TimestampTz last_recv_timestamp = GetCurrentTimestamp();
call 0 returned 100%
-: 1093:
-: 1094: /*
-: 1095: * Init the ApplyMessageContext which we clean up after each replication
-: 1096: * protocol message.
-: 1097: */
26: 1098: ApplyMessageContext = AllocSetContextCreate(ApplyContext,
call 0 returned 100%
-: 1099: "ApplyMessageContext",
-: 1100: ALLOCSET_DEFAULT_SIZES);
-: 1101:
-: 1102: /* mark as idle, before starting to loop */
26: 1103: pgstat_report_activity(STATE_IDLE, NULL);
call 0 returned 100%
-: 1104:
-: 1105: for (;;)
-: 1106: {
998: 1107: pgsocket fd = PGINVALID_SOCKET;
-: 1108: int rc;
-: 1109: int len;
998: 1110: char *buf = NULL;
998: 1111: bool endofstream = false;
998: 1112: bool ping_sent = false;
-: 1113: long wait_time;
-: 1114:
998: 1115: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1116:
998: 1117: MemoryContextSwitchTo(ApplyMessageContext);
call 0 returned 100%
-: 1118:
998: 1119: len = walrcv_receive(wrconn, &buf, &fd);
call 0 returned 99%
-: 1120:
995: 1121: if (len != 0)
branch 0 taken 93% (fallthrough)
branch 1 taken 7%
-: 1122: {
-: 1123: /* Process the data */
-: 1124: for (;;)
-: 1125: {
2729: 1126: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1127:
2729: 1128: if (len == 0)
branch 0 taken 34% (fallthrough)
branch 1 taken 66%
-: 1129: {
918: 1130: break;
-: 1131: }
1811: 1132: else if (len < 0)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 1133: {
2: 1134: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 1135: (errmsg("data stream from publisher has ended")));
2: 1136: endofstream = true;
2: 1137: break;
-: 1138: }
-: 1139: else
-: 1140: {
-: 1141: int c;
-: 1142: StringInfoData s;
-: 1143:
-: 1144: /* Reset timeout. */
1809: 1145: last_recv_timestamp = GetCurrentTimestamp();
call 0 returned 100%
1809: 1146: ping_sent = false;
-: 1147:
-: 1148: /* Ensure we are reading the data into our memory context. */
1809: 1149: MemoryContextSwitchTo(ApplyMessageContext);
call 0 returned 100%
-: 1150:
1809: 1151: s.data = buf;
1809: 1152: s.len = len;
1809: 1153: s.cursor = 0;
1809: 1154: s.maxlen = -1;
-: 1155:
1809: 1156: c = pq_getmsgbyte(&s);
call 0 returned 100%
-: 1157:
1809: 1158: if (c == 'w')
branch 0 taken 55% (fallthrough)
branch 1 taken 45%
-: 1159: {
-: 1160: XLogRecPtr start_lsn;
-: 1161: XLogRecPtr end_lsn;
-: 1162: TimestampTz send_time;
-: 1163:
996: 1164: start_lsn = pq_getmsgint64(&s);
call 0 returned 100%
996: 1165: end_lsn = pq_getmsgint64(&s);
call 0 returned 100%
996: 1166: send_time = pq_getmsgint64(&s);
call 0 returned 100%
-: 1167:
996: 1168: if (last_received < start_lsn)
branch 0 taken 71% (fallthrough)
branch 1 taken 29%
707: 1169: last_received = start_lsn;
-: 1170:
996: 1171: if (last_received < end_lsn)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1172: last_received = end_lsn;
-: 1173:
996: 1174: UpdateWorkerStats(last_received, send_time, false);
call 0 returned 100%
-: 1175:
996: 1176: apply_dispatch(&s);
call 0 returned 99%
-: 1177: }
813: 1178: else if (c == 'k')
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1179: {
-: 1180: XLogRecPtr end_lsn;
-: 1181: TimestampTz timestamp;
-: 1182: bool reply_requested;
-: 1183:
813: 1184: end_lsn = pq_getmsgint64(&s);
call 0 returned 100%
813: 1185: timestamp = pq_getmsgint64(&s);
call 0 returned 100%
813: 1186: reply_requested = pq_getmsgbyte(&s);
call 0 returned 100%
-: 1187:
813: 1188: if (last_received < end_lsn)
branch 0 taken 9% (fallthrough)
branch 1 taken 91%
71: 1189: last_received = end_lsn;
-: 1190:
813: 1191: send_feedback(last_received, reply_requested, false);
call 0 returned 100%
813: 1192: UpdateWorkerStats(last_received, timestamp, true);
call 0 returned 100%
-: 1193: }
-: 1194: /* other message types are purposefully ignored */
-: 1195:
1808: 1196: MemoryContextReset(ApplyMessageContext);
call 0 returned 100%
-: 1197: }
-: 1198:
1808: 1199: len = walrcv_receive(wrconn, &buf, &fd);
call 0 returned 100%
1808: 1200: }
-: 1201: }
-: 1202:
-: 1203: /* confirm all writes so far */
994: 1204: send_feedback(last_received, false, false);
call 0 returned 100%
-: 1205:
994: 1206: if (!in_remote_transaction)
branch 0 taken 97% (fallthrough)
branch 1 taken 3%
-: 1207: {
-: 1208: /*
-: 1209: * If we didn't get any transactions for a while there might be
-: 1210: * unconsumed invalidation messages in the queue, consume them
-: 1211: * now.
-: 1212: */
969: 1213: AcceptInvalidationMessages();
call 0 returned 100%
969: 1214: maybe_reread_subscription();
call 0 returned 99%
-: 1215:
-: 1216: /* Process any table synchronization changes. */
966: 1217: process_syncing_tables(last_received);
call 0 returned 99%
-: 1218: }
-: 1219:
-: 1220: /* Cleanup the memory. */
987: 1221: MemoryContextResetAndDeleteChildren(ApplyMessageContext);
call 0 returned 100%
987: 1222: MemoryContextSwitchTo(TopMemoryContext);
call 0 returned 100%
-: 1223:
-: 1224: /* Check if we need to exit the streaming loop. */
987: 1225: if (endofstream)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 1226: {
-: 1227: TimeLineID tli;
-: 1228:
2: 1229: walrcv_endstreaming(wrconn, &tli);
call 0 returned 0%
#####: 1230: break;
-: 1231: }
-: 1232:
-: 1233: /*
-: 1234: * Wait for more data or latch. If we have unflushed transactions,
-: 1235: * wake up after WalWriterDelay to see if they've been flushed yet (in
-: 1236: * which case we should send a feedback message). Otherwise, there's
-: 1237: * no particular urgency about waking up unless we get data or a
-: 1238: * signal.
-: 1239: */
985: 1240: if (!dlist_is_empty(&lsn_mapping))
call 0 returned 100%
branch 1 taken 81% (fallthrough)
branch 2 taken 19%
793: 1241: wait_time = WalWriterDelay;
-: 1242: else
192: 1243: wait_time = NAPTIME_PER_CYCLE;
-: 1244:
985: 1245: rc = WaitLatchOrSocket(MyLatch,
call 0 returned 100%
-: 1246: WL_SOCKET_READABLE | WL_LATCH_SET |
-: 1247: WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-: 1248: fd, wait_time,
-: 1249: WAIT_EVENT_LOGICAL_APPLY_MAIN);
-: 1250:
985: 1251: if (rc & WL_LATCH_SET)
branch 0 taken 7% (fallthrough)
branch 1 taken 93%
-: 1252: {
73: 1253: ResetLatch(MyLatch);
call 0 returned 100%
73: 1254: CHECK_FOR_INTERRUPTS();
branch 0 taken 18% (fallthrough)
branch 1 taken 82%
call 2 returned 0%
-: 1255: }
-: 1256:
972: 1257: if (got_SIGHUP)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1258: {
#####: 1259: got_SIGHUP = false;
#####: 1260: ProcessConfigFile(PGC_SIGHUP);
call 0 never executed
-: 1261: }
-: 1262:
972: 1263: if (rc & WL_TIMEOUT)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 1264: {
-: 1265: /*
-: 1266: * We didn't receive anything new. If we haven't heard anything
-: 1267: * from the server for more than wal_receiver_timeout / 2, ping
-: 1268: * the server. Also, if it's been longer than
-: 1269: * wal_receiver_status_interval since the last update we sent,
-: 1270: * send a status update to the master anyway, to report any
-: 1271: * progress in applying WAL.
-: 1272: */
9: 1273: bool requestReply = false;
-: 1274:
-: 1275: /*
-: 1276: * Check if time since last receive from standby has reached the
-: 1277: * configured limit.
-: 1278: */
9: 1279: if (wal_receiver_timeout > 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1280: {
9: 1281: TimestampTz now = GetCurrentTimestamp();
call 0 returned 100%
-: 1282: TimestampTz timeout;
-: 1283:
9: 1284: timeout =
9: 1285: TimestampTzPlusMilliseconds(last_recv_timestamp,
-: 1286: wal_receiver_timeout);
-: 1287:
9: 1288: if (now >= timeout)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1289: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 1290: (errmsg("terminating logical replication worker due to timeout")));
-: 1291:
-: 1292: /*
-: 1293: * We didn't receive anything new, for half of receiver
-: 1294: * replication timeout. Ping the server.
-: 1295: */
9: 1296: if (!ping_sent)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1297: {
9: 1298: timeout = TimestampTzPlusMilliseconds(last_recv_timestamp,
-: 1299: (wal_receiver_timeout / 2));
9: 1300: if (now >= timeout)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1301: {
#####: 1302: requestReply = true;
#####: 1303: ping_sent = true;
-: 1304: }
-: 1305: }
-: 1306: }
-: 1307:
9: 1308: send_feedback(last_received, requestReply, requestReply);
call 0 returned 100%
-: 1309: }
972: 1310: }
#####: 1311:}
-: 1312:
-: 1313:/*
-: 1314: * Send a Standby Status Update message to server.
-: 1315: *
-: 1316: * 'recvpos' is the latest LSN we've received data to, force is set if we need
-: 1317: * to send a response to avoid timeouts.
-: 1318: */
-: 1319:static void
function send_feedback called 1816 returned 100% blocks executed 93%
1816: 1320:send_feedback(XLogRecPtr recvpos, bool force, bool requestReply)
-: 1321:{
-: 1322: static StringInfo reply_message = NULL;
-: 1323: static TimestampTz send_time = 0;
-: 1324:
-: 1325: static XLogRecPtr last_recvpos = InvalidXLogRecPtr;
-: 1326: static XLogRecPtr last_writepos = InvalidXLogRecPtr;
-: 1327: static XLogRecPtr last_flushpos = InvalidXLogRecPtr;
-: 1328:
-: 1329: XLogRecPtr writepos;
-: 1330: XLogRecPtr flushpos;
-: 1331: TimestampTz now;
-: 1332: bool have_pending_txes;
-: 1333:
-: 1334: /*
-: 1335: * If the user doesn't want status to be reported to the publisher, be
-: 1336: * sure to exit before doing anything at all.
-: 1337: */
1816: 1338: if (!force && wal_receiver_status_interval <= 0)
branch 0 taken 61% (fallthrough)
branch 1 taken 39%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
902: 1339: return;
-: 1340:
-: 1341: /* It's legal to not pass a recvpos */
1816: 1342: if (recvpos < last_recvpos)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1343: recvpos = last_recvpos;
-: 1344:
1816: 1345: get_flush_position(&writepos, &flushpos, &have_pending_txes);
call 0 returned 100%
-: 1346:
-: 1347: /*
-: 1348: * No outstanding transactions to flush, we can report the latest received
-: 1349: * position. This is important for synchronous replication.
-: 1350: */
1816: 1351: if (!have_pending_txes)
branch 0 taken 16% (fallthrough)
branch 1 taken 84%
296: 1352: flushpos = writepos = recvpos;
-: 1353:
1816: 1354: if (writepos < last_writepos)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1355: writepos = last_writepos;
-: 1356:
1816: 1357: if (flushpos < last_flushpos)
branch 0 taken 83% (fallthrough)
branch 1 taken 17%
1509: 1358: flushpos = last_flushpos;
-: 1359:
1816: 1360: now = GetCurrentTimestamp();
call 0 returned 100%
-: 1361:
-: 1362: /* if we've already reported everything we're good */
2920: 1363: if (!force &&
branch 0 taken 61% (fallthrough)
branch 1 taken 39%
branch 2 taken 82% (fallthrough)
branch 3 taken 18%
2014: 1364: writepos == last_writepos &&
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
1815: 1365: flushpos == last_flushpos &&
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
905: 1366: !TimestampDifferenceExceeds(send_time, now,
call 0 returned 100%
-: 1367: wal_receiver_status_interval * 1000))
902: 1368: return;
914: 1369: send_time = now;
-: 1370:
914: 1371: if (!reply_message)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
-: 1372: {
26: 1373: MemoryContext oldctx = MemoryContextSwitchTo(ApplyContext);
call 0 returned 100%
-: 1374:
26: 1375: reply_message = makeStringInfo();
call 0 returned 100%
26: 1376: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 1377: }
-: 1378: else
888: 1379: resetStringInfo(reply_message);
call 0 returned 100%
-: 1380:
914: 1381: pq_sendbyte(reply_message, 'r');
call 0 returned 100%
914: 1382: pq_sendint64(reply_message, recvpos); /* write */
call 0 returned 100%
914: 1383: pq_sendint64(reply_message, flushpos); /* flush */
call 0 returned 100%
914: 1384: pq_sendint64(reply_message, writepos); /* apply */
call 0 returned 100%
914: 1385: pq_sendint64(reply_message, now); /* sendTime */
call 0 returned 100%
914: 1386: pq_sendbyte(reply_message, requestReply); /* replyRequested */
call 0 returned 100%
-: 1387:
914: 1388: elog(DEBUG2, "sending feedback (force %d) to recv %X/%X, write %X/%X, flush %X/%X",
call 0 returned 100%
call 1 returned 100%
-: 1389: force,
-: 1390: (uint32) (recvpos >> 32), (uint32) recvpos,
-: 1391: (uint32) (writepos >> 32), (uint32) writepos,
-: 1392: (uint32) (flushpos >> 32), (uint32) flushpos
-: 1393: );
-: 1394:
914: 1395: walrcv_send(wrconn, reply_message->data, reply_message->len);
call 0 returned 100%
-: 1396:
914: 1397: if (recvpos > last_recvpos)
branch 0 taken 21% (fallthrough)
branch 1 taken 79%
195: 1398: last_recvpos = recvpos;
914: 1399: if (writepos > last_writepos)
branch 0 taken 21% (fallthrough)
branch 1 taken 79%
195: 1400: last_writepos = writepos;
914: 1401: if (flushpos > last_flushpos)
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
134: 1402: last_flushpos = flushpos;
-: 1403:}
-: 1404:
-: 1405:/*
-: 1406: * Reread subscription info if needed. Most changes will be exit.
-: 1407: */
-: 1408:static void
function maybe_reread_subscription called 1104 returned 99% blocks executed 66%
1104: 1409:maybe_reread_subscription(void)
-: 1410:{
-: 1411: MemoryContext oldctx;
-: 1412: Subscription *newsub;
1104: 1413: bool started_tx = false;
-: 1414:
-: 1415: /* When cache state is valid there is nothing to do here. */
1104: 1416: if (MySubscriptionValid)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
2197: 1417: return;
-: 1418:
-: 1419: /* This function might be called inside or outside of transaction. */
8: 1420: if (!IsTransactionState())
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
-: 1421: {
8: 1422: StartTransactionCommand();
call 0 returned 100%
8: 1423: started_tx = true;
-: 1424: }
-: 1425:
-: 1426: /* Ensure allocations in permanent context. */
8: 1427: oldctx = MemoryContextSwitchTo(ApplyContext);
call 0 returned 100%
-: 1428:
8: 1429: newsub = GetSubscription(MyLogicalRepWorker->subid, true);
call 0 returned 100%
-: 1430:
-: 1431: /*
-: 1432: * Exit if the subscription was removed. This normally should not happen
-: 1433: * as the worker gets killed during DROP SUBSCRIPTION.
-: 1434: */
8: 1435: if (!newsub)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1436: {
#####: 1437: ereport(LOG,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 1438: (errmsg("logical replication apply worker for subscription \"%s\" will "
-: 1439: "stop because the subscription was removed",
-: 1440: MySubscription->name)));
-: 1441:
#####: 1442: proc_exit(0);
call 0 never executed
-: 1443: }
-: 1444:
-: 1445: /*
-: 1446: * Exit if the subscription was disabled. This normally should not happen
-: 1447: * as the worker gets killed during ALTER SUBSCRIPTION ... DISABLE.
-: 1448: */
8: 1449: if (!newsub->enabled)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1450: {
#####: 1451: ereport(LOG,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 1452: (errmsg("logical replication apply worker for subscription \"%s\" will "
-: 1453: "stop because the subscription was disabled",
-: 1454: MySubscription->name)));
-: 1455:
#####: 1456: proc_exit(0);
call 0 never executed
-: 1457: }
-: 1458:
-: 1459: /*
-: 1460: * Exit if connection string was changed. The launcher will start new
-: 1461: * worker.
-: 1462: */
8: 1463: if (strcmp(newsub->conninfo, MySubscription->conninfo) != 0)
branch 0 taken 13% (fallthrough)
branch 1 taken 88%
-: 1464: {
1: 1465: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 1466: (errmsg("logical replication apply worker for subscription \"%s\" will "
-: 1467: "restart because the connection information was changed",
-: 1468: MySubscription->name)));
-: 1469:
1: 1470: proc_exit(0);
call 0 returned 0%
-: 1471: }
-: 1472:
-: 1473: /*
-: 1474: * Exit if subscription name was changed (it's used for
-: 1475: * fallback_application_name). The launcher will start new worker.
-: 1476: */
7: 1477: if (strcmp(newsub->name, MySubscription->name) != 0)
branch 0 taken 14% (fallthrough)
branch 1 taken 86%
-: 1478: {
1: 1479: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 1480: (errmsg("logical replication apply worker for subscription \"%s\" will "
-: 1481: "restart because subscription was renamed",
-: 1482: MySubscription->name)));
-: 1483:
1: 1484: proc_exit(0);
call 0 returned 0%
-: 1485: }
-: 1486:
-: 1487: /* !slotname should never happen when enabled is true. */
6: 1488: Assert(newsub->slotname);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1489:
-: 1490: /*
-: 1491: * We need to make new connection to new slot if slot name has changed so
-: 1492: * exit here as well if that's the case.
-: 1493: */
6: 1494: if (strcmp(newsub->slotname, MySubscription->slotname) != 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1495: {
#####: 1496: ereport(LOG,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 1497: (errmsg("logical replication apply worker for subscription \"%s\" will "
-: 1498: "restart because the replication slot name was changed",
-: 1499: MySubscription->name)));
-: 1500:
#####: 1501: proc_exit(0);
call 0 never executed
-: 1502: }
-: 1503:
-: 1504: /*
-: 1505: * Exit if publication list was changed. The launcher will start new
-: 1506: * worker.
-: 1507: */
6: 1508: if (!equal(newsub->publications, MySubscription->publications))
call 0 returned 100%
branch 1 taken 17% (fallthrough)
branch 2 taken 83%
-: 1509: {
1: 1510: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 1511: (errmsg("logical replication apply worker for subscription \"%s\" will "
-: 1512: "restart because subscription's publications were changed",
-: 1513: MySubscription->name)));
-: 1514:
1: 1515: proc_exit(0);
call 0 returned 0%
-: 1516: }
-: 1517:
-: 1518: /* Check for other changes that should never happen too. */
5: 1519: if (newsub->dbid != MySubscription->dbid)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1520: {
#####: 1521: elog(ERROR, "subscription %u changed unexpectedly",
call 0 never executed
call 1 never executed
call 2 never executed
-: 1522: MyLogicalRepWorker->subid);
-: 1523: }
-: 1524:
-: 1525: /* Clean old subscription info and switch to new one. */
5: 1526: FreeSubscription(MySubscription);
call 0 returned 100%
5: 1527: MySubscription = newsub;
-: 1528:
5: 1529: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 1530:
-: 1531: /* Change synchronous commit according to the user's wishes */
5: 1532: SetConfigOption("synchronous_commit", MySubscription->synccommit,
call 0 returned 100%
-: 1533: PGC_BACKEND, PGC_S_OVERRIDE);
-: 1534:
5: 1535: if (started_tx)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
5: 1536: CommitTransactionCommand();
call 0 returned 100%
-: 1537:
5: 1538: MySubscriptionValid = true;
-: 1539:}
-: 1540:
-: 1541:/*
-: 1542: * Callback from subscription syscache invalidation.
-: 1543: */
-: 1544:static void
function subscription_change_cb called 8 returned 100% blocks executed 100%
8: 1545:subscription_change_cb(Datum arg, int cacheid, uint32 hashvalue)
-: 1546:{
8: 1547: MySubscriptionValid = false;
8: 1548:}
-: 1549:
-: 1550:/* SIGHUP: set flag to reload configuration at next convenient time */
-: 1551:static void
function logicalrep_worker_sighup called 0 returned 0% blocks executed 0%
#####: 1552:logicalrep_worker_sighup(SIGNAL_ARGS)
-: 1553:{
#####: 1554: int save_errno = errno;
call 0 never executed
-: 1555:
#####: 1556: got_SIGHUP = true;
-: 1557:
-: 1558: /* Waken anything waiting on the process latch */
#####: 1559: SetLatch(MyLatch);
call 0 never executed
-: 1560:
#####: 1561: errno = save_errno;
call 0 never executed
#####: 1562:}
-: 1563:
-: 1564:/* Logical Replication Apply worker entry point */
-: 1565:void
function ApplyWorkerMain called 61 returned 0% blocks executed 74%
61: 1566:ApplyWorkerMain(Datum main_arg)
-: 1567:{
61: 1568: int worker_slot = DatumGetInt32(main_arg);
-: 1569: MemoryContext oldctx;
-: 1570: char originname[NAMEDATALEN];
-: 1571: XLogRecPtr origin_startpos;
-: 1572: char *myslotname;
-: 1573: WalRcvStreamOptions options;
-: 1574:
-: 1575: /* Attach to slot */
61: 1576: logicalrep_worker_attach(worker_slot);
call 0 returned 100%
-: 1577:
-: 1578: /* Setup signal handling */
61: 1579: pqsignal(SIGHUP, logicalrep_worker_sighup);
call 0 returned 100%
61: 1580: pqsignal(SIGTERM, die);
call 0 returned 100%
61: 1581: BackgroundWorkerUnblockSignals();
call 0 returned 100%
-: 1582:
-: 1583: /*
-: 1584: * We don't currently need any ResourceOwner in a walreceiver process, but
-: 1585: * if we did, we could call CreateAuxProcessResourceOwner here.
-: 1586: */
-: 1587:
-: 1588: /* Initialise stats to a sanish value */
122: 1589: MyLogicalRepWorker->last_send_time = MyLogicalRepWorker->last_recv_time =
61: 1590: MyLogicalRepWorker->reply_time = GetCurrentTimestamp();
call 0 returned 100%
-: 1591:
-: 1592: /* Load the libpq-specific functions */
61: 1593: load_file("libpqwalreceiver", false);
call 0 returned 100%
-: 1594:
-: 1595: /* Run as replica session replication role. */
61: 1596: SetConfigOption("session_replication_role", "replica",
call 0 returned 100%
-: 1597: PGC_SUSET, PGC_S_OVERRIDE);
-: 1598:
-: 1599: /* Connect to our database. */
61: 1600: BackgroundWorkerInitializeConnectionByOid(MyLogicalRepWorker->dbid,
call 0 returned 100%
61: 1601: MyLogicalRepWorker->userid,
-: 1602: 0);
-: 1603:
-: 1604: /* Load the subscription into persistent memory context. */
61: 1605: ApplyContext = AllocSetContextCreate(TopMemoryContext,
call 0 returned 100%
-: 1606: "ApplyContext",
-: 1607: ALLOCSET_DEFAULT_SIZES);
61: 1608: StartTransactionCommand();
call 0 returned 100%
61: 1609: oldctx = MemoryContextSwitchTo(ApplyContext);
call 0 returned 100%
-: 1610:
61: 1611: MySubscription = GetSubscription(MyLogicalRepWorker->subid, true);
call 0 returned 100%
61: 1612: if (!MySubscription)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1613: {
#####: 1614: ereport(LOG,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 1615: (errmsg("logical replication apply worker for subscription %u will not "
-: 1616: "start because the subscription was removed during startup",
-: 1617: MyLogicalRepWorker->subid)));
#####: 1618: proc_exit(0);
call 0 never executed
-: 1619: }
-: 1620:
61: 1621: MySubscriptionValid = true;
61: 1622: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 1623:
61: 1624: if (!MySubscription->enabled)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1625: {
#####: 1626: ereport(LOG,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 1627: (errmsg("logical replication apply worker for subscription \"%s\" will not "
-: 1628: "start because the subscription was disabled during startup",
-: 1629: MySubscription->name)));
-: 1630:
#####: 1631: proc_exit(0);
call 0 never executed
-: 1632: }
-: 1633:
-: 1634: /* Setup synchronous commit according to the user's wishes */
61: 1635: SetConfigOption("synchronous_commit", MySubscription->synccommit,
call 0 returned 100%
-: 1636: PGC_BACKEND, PGC_S_OVERRIDE);
-: 1637:
-: 1638: /* Keep us informed about subscription changes. */
61: 1639: CacheRegisterSyscacheCallback(SUBSCRIPTIONOID,
call 0 returned 100%
-: 1640: subscription_change_cb,
-: 1641: (Datum) 0);
-: 1642:
61: 1643: if (am_tablesync_worker())
call 0 returned 100%
branch 1 taken 61% (fallthrough)
branch 2 taken 39%
37: 1644: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 100%
-: 1645: (errmsg("logical replication table synchronization worker for subscription \"%s\", table \"%s\" has started",
-: 1646: MySubscription->name, get_rel_name(MyLogicalRepWorker->relid))));
-: 1647: else
24: 1648: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 1649: (errmsg("logical replication apply worker for subscription \"%s\" has started",
-: 1650: MySubscription->name)));
-: 1651:
61: 1652: CommitTransactionCommand();
call 0 returned 100%
-: 1653:
-: 1654: /* Connect to the origin and start the replication. */
61: 1655: elog(DEBUG1, "connecting to publisher using connection string \"%s\"",
call 0 returned 100%
call 1 returned 100%
-: 1656: MySubscription->conninfo);
-: 1657:
61: 1658: if (am_tablesync_worker())
call 0 returned 100%
branch 1 taken 61% (fallthrough)
branch 2 taken 39%
-: 1659: {
-: 1660: char *syncslotname;
-: 1661:
-: 1662: /* This is table synchronization worker, call initial sync. */
37: 1663: syncslotname = LogicalRepSyncTableStart(&origin_startpos);
call 0 returned 11%
-: 1664:
-: 1665: /* The slot name needs to be allocated in permanent memory context. */
4: 1666: oldctx = MemoryContextSwitchTo(ApplyContext);
call 0 returned 100%
4: 1667: myslotname = pstrdup(syncslotname);
call 0 returned 100%
4: 1668: MemoryContextSwitchTo(oldctx);
call 0 returned 100%
-: 1669:
4: 1670: pfree(syncslotname);
call 0 returned 100%
-: 1671: }
-: 1672: else
-: 1673: {
-: 1674: /* This is main apply worker */
-: 1675: RepOriginId originid;
-: 1676: TimeLineID startpointTLI;
-: 1677: char *err;
-: 1678:
24: 1679: myslotname = MySubscription->slotname;
-: 1680:
-: 1681: /*
-: 1682: * This shouldn't happen if the subscription is enabled, but guard
-: 1683: * against DDL bugs or manual catalog changes. (libpqwalreceiver will
-: 1684: * crash if slot is NULL.)
-: 1685: */
24: 1686: if (!myslotname)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1687: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 1688: (errmsg("subscription has no replication slot set")));
-: 1689:
-: 1690: /* Setup replication origin tracking. */
24: 1691: StartTransactionCommand();
call 0 returned 100%
24: 1692: snprintf(originname, sizeof(originname), "pg_%u", MySubscription->oid);
call 0 returned 100%
24: 1693: originid = replorigin_by_name(originname, true);
call 0 returned 100%
24: 1694: if (!OidIsValid(originid))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1695: originid = replorigin_create(originname);
call 0 never executed
24: 1696: replorigin_session_setup(originid);
call 0 returned 100%
24: 1697: replorigin_session_origin = originid;
24: 1698: origin_startpos = replorigin_session_get_progress(false);
call 0 returned 100%
24: 1699: CommitTransactionCommand();
call 0 returned 100%
-: 1700:
24: 1701: wrconn = walrcv_connect(MySubscription->conninfo, true, MySubscription->name,
call 0 returned 100%
-: 1702: &err);
24: 1703: if (wrconn == NULL)
branch 0 taken 8% (fallthrough)
branch 1 taken 92%
2: 1704: ereport(ERROR,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 0%
call 5 never executed
-: 1705: (errmsg("could not connect to the publisher: %s", err)));
-: 1706:
-: 1707: /*
-: 1708: * We don't really use the output identify_system for anything but it
-: 1709: * does some initializations on the upstream so let's still call it.
-: 1710: */
22: 1711: (void) walrcv_identify_system(wrconn, &startpointTLI);
call 0 returned 100%
-: 1712:
-: 1713: }
-: 1714:
-: 1715: /*
-: 1716: * Setup callback for syscache so that we know when something changes in
-: 1717: * the subscription relation state.
-: 1718: */
26: 1719: CacheRegisterSyscacheCallback(SUBSCRIPTIONRELMAP,
call 0 returned 100%
-: 1720: invalidate_syncing_table_states,
-: 1721: (Datum) 0);
-: 1722:
-: 1723: /* Build logical replication streaming options. */
26: 1724: options.logical = true;
26: 1725: options.startpoint = origin_startpos;
26: 1726: options.slotname = myslotname;
26: 1727: options.proto.logical.proto_version = LOGICALREP_PROTO_VERSION_NUM;
26: 1728: options.proto.logical.publication_names = MySubscription->publications;
-: 1729:
-: 1730: /* Start normal logical streaming replication. */
26: 1731: walrcv_startstreaming(wrconn, &options);
call 0 returned 100%
-: 1732:
-: 1733: /* Run the main loop. */
26: 1734: LogicalRepApplyLoop(origin_startpos);
call 0 returned 0%
-: 1735:
#####: 1736: proc_exit(0);
-: 1737:}
-: 1738:
-: 1739:/*
-: 1740: * Is current process a logical replication worker?
-: 1741: */
-: 1742:bool
function IsLogicalWorker called 158 returned 100% blocks executed 100%
158: 1743:IsLogicalWorker(void)
-: 1744:{
158: 1745: return MyLogicalRepWorker != NULL;
-: 1746:}
base_code_coverage/decode.c.gcov 0000664 0001750 0000000 00000157726 13560201434 016376 0 ustar vignesh root -: 0:Source:decode.c
-: 0:Graph:./decode.gcno
-: 0:Data:./decode.gcda
-: 0:Runs:6535
-: 0:Programs:1
-: 1:/* -------------------------------------------------------------------------
-: 2: *
-: 3: * decode.c
-: 4: * This module decodes WAL records read using xlogreader.h's APIs for the
-: 5: * purpose of logical decoding by passing information to the
-: 6: * reorderbuffer module (containing the actual changes) and to the
-: 7: * snapbuild module to build a fitting catalog snapshot (to be able to
-: 8: * properly decode the changes in the reorderbuffer).
-: 9: *
-: 10: * NOTE:
-: 11: * This basically tries to handle all low level xlog stuff for
-: 12: * reorderbuffer.c and snapbuild.c. There's some minor leakage where a
-: 13: * specific record's struct is used to pass data along, but those just
-: 14: * happen to contain the right amount of data in a convenient
-: 15: * format. There isn't and shouldn't be much intelligence about the
-: 16: * contents of records in here except turning them into a more usable
-: 17: * format.
-: 18: *
-: 19: * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
-: 20: * Portions Copyright (c) 1994, Regents of the University of California
-: 21: *
-: 22: * IDENTIFICATION
-: 23: * src/backend/replication/logical/decode.c
-: 24: *
-: 25: * -------------------------------------------------------------------------
-: 26: */
-: 27:#include "postgres.h"
-: 28:
-: 29:#include "access/heapam.h"
-: 30:#include "access/heapam_xlog.h"
-: 31:#include "access/transam.h"
-: 32:#include "access/xact.h"
-: 33:#include "access/xlog_internal.h"
-: 34:#include "access/xlogutils.h"
-: 35:#include "access/xlogreader.h"
-: 36:#include "access/xlogrecord.h"
-: 37:
-: 38:#include "catalog/pg_control.h"
-: 39:
-: 40:#include "replication/decode.h"
-: 41:#include "replication/logical.h"
-: 42:#include "replication/message.h"
-: 43:#include "replication/reorderbuffer.h"
-: 44:#include "replication/origin.h"
-: 45:#include "replication/snapbuild.h"
-: 46:
-: 47:#include "storage/standby.h"
-: 48:
-: 49:typedef struct XLogRecordBuffer
-: 50:{
-: 51: XLogRecPtr origptr;
-: 52: XLogRecPtr endptr;
-: 53: XLogReaderState *record;
-: 54:} XLogRecordBuffer;
-: 55:
-: 56:/* RMGR Handlers */
-: 57:static void DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 58:static void DecodeHeapOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 59:static void DecodeHeap2Op(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 60:static void DecodeXactOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 61:static void DecodeStandbyOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 62:static void DecodeLogicalMsgOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 63:
-: 64:/* individual record(group)'s handlers */
-: 65:static void DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 66:static void DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 67:static void DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 68:static void DecodeTruncate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 69:static void DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 70:static void DecodeSpecConfirm(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
-: 71:
-: 72:static void DecodeCommit(LogicalDecodingContext *ctx, XLogRecordBuffer *buf,
-: 73: xl_xact_parsed_commit *parsed, TransactionId xid);
-: 74:static void DecodeAbort(LogicalDecodingContext *ctx, XLogRecordBuffer *buf,
-: 75: xl_xact_parsed_abort *parsed, TransactionId xid);
-: 76:
-: 77:/* common function to decode tuples */
-: 78:static void DecodeXLogTuple(char *data, Size len, ReorderBufferTupleBuf *tup);
-: 79:
-: 80:/*
-: 81: * Take every XLogReadRecord()ed record and perform the actions required to
-: 82: * decode it using the output plugin already setup in the logical decoding
-: 83: * context.
-: 84: *
-: 85: * NB: Note that every record's xid needs to be processed by reorderbuffer
-: 86: * (xids contained in the content of records are not relevant for this rule).
-: 87: * That means that for records which'd otherwise not go through the
-: 88: * reorderbuffer ReorderBufferProcessXid() has to be called. We don't want to
-: 89: * call ReorderBufferProcessXid for each record type by default, because
-: 90: * e.g. empty xacts can be handled more efficiently if there's no previous
-: 91: * state for them.
-: 92: *
-: 93: * We also support the ability to fast forward thru records, skipping some
-: 94: * record types completely - see individual record types for details.
-: 95: */
-: 96:void
function LogicalDecodingProcessRecord called 1595203 returned 100% blocks executed 84%
1595203: 97:LogicalDecodingProcessRecord(LogicalDecodingContext *ctx, XLogReaderState *record)
-: 98:{
-: 99: XLogRecordBuffer buf;
-: 100:
1595203: 101: buf.origptr = ctx->reader->ReadRecPtr;
1595203: 102: buf.endptr = ctx->reader->EndRecPtr;
1595203: 103: buf.record = record;
-: 104:
-: 105: /* cast so we get a warning when new rmgrs are added */
1595203: 106: switch ((RmgrIds) XLogRecGetRmid(record))
branch 0 taken 1%
branch 1 taken 1%
branch 2 taken 1%
branch 3 taken 1%
branch 4 taken 82%
branch 5 taken 1%
branch 6 taken 17%
branch 7 taken 0%
branch 8 taken 0%
-: 107: {
-: 108: /*
-: 109: * Rmgrs we care about for logical decoding. Add new rmgrs in
-: 110: * rmgrlist.h's order.
-: 111: */
-: 112: case RM_XLOG_ID:
1136: 113: DecodeXLogOp(ctx, &buf);
call 0 returned 100%
1136: 114: break;
-: 115:
-: 116: case RM_XACT_ID:
1396: 117: DecodeXactOp(ctx, &buf);
call 0 returned 100%
1396: 118: break;
-: 119:
-: 120: case RM_STANDBY_ID:
1449: 121: DecodeStandbyOp(ctx, &buf);
call 0 returned 100%
1449: 122: break;
-: 123:
-: 124: case RM_HEAP2_ID:
14071: 125: DecodeHeap2Op(ctx, &buf);
call 0 returned 100%
14071: 126: break;
-: 127:
-: 128: case RM_HEAP_ID:
1301389: 129: DecodeHeapOp(ctx, &buf);
call 0 returned 100%
1301389: 130: break;
-: 131:
-: 132: case RM_LOGICALMSG_ID:
32: 133: DecodeLogicalMsgOp(ctx, &buf);
call 0 returned 100%
32: 134: break;
-: 135:
-: 136: /*
-: 137: * Rmgrs irrelevant for logical decoding; they describe stuff not
-: 138: * represented in logical decoding. Add new rmgrs in rmgrlist.h's
-: 139: * order.
-: 140: */
-: 141: case RM_SMGR_ID:
-: 142: case RM_CLOG_ID:
-: 143: case RM_DBASE_ID:
-: 144: case RM_TBLSPC_ID:
-: 145: case RM_MULTIXACT_ID:
-: 146: case RM_RELMAP_ID:
-: 147: case RM_BTREE_ID:
-: 148: case RM_HASH_ID:
-: 149: case RM_GIN_ID:
-: 150: case RM_GIST_ID:
-: 151: case RM_SEQ_ID:
-: 152: case RM_SPGIST_ID:
-: 153: case RM_BRIN_ID:
-: 154: case RM_COMMIT_TS_ID:
-: 155: case RM_REPLORIGIN_ID:
-: 156: case RM_GENERIC_ID:
-: 157: /* just deal with xid, and done */
275730: 158: ReorderBufferProcessXid(ctx->reorder, XLogRecGetXid(record),
call 0 returned 100%
-: 159: buf.origptr);
275730: 160: break;
-: 161: case RM_NEXT_ID:
#####: 162: elog(ERROR, "unexpected RM_NEXT_ID rmgr_id: %u", (RmgrIds) XLogRecGetRmid(buf.record));
call 0 never executed
call 1 never executed
call 2 never executed
-: 163: }
1595203: 164:}
-: 165:
-: 166:/*
-: 167: * Handle rmgr XLOG_ID records for DecodeRecordIntoReorderBuffer().
-: 168: */
-: 169:static void
function DecodeXLogOp called 1136 returned 100% blocks executed 70%
1136: 170:DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 171:{
1136: 172: SnapBuild *builder = ctx->snapshot_builder;
1136: 173: uint8 info = XLogRecGetInfo(buf->record) & ~XLR_INFO_MASK;
-: 174:
1136: 175: ReorderBufferProcessXid(ctx->reorder, XLogRecGetXid(buf->record),
call 0 returned 100%
-: 176: buf->origptr);
-: 177:
1136: 178: switch (info)
branch 0 taken 1%
branch 1 taken 2%
branch 2 taken 97%
branch 3 taken 0%
-: 179: {
-: 180: /* this is also used in END_OF_RECOVERY checkpoints */
-: 181: case XLOG_CHECKPOINT_SHUTDOWN:
-: 182: case XLOG_END_OF_RECOVERY:
9: 183: SnapBuildSerializationPoint(builder, buf->origptr);
call 0 returned 100%
-: 184:
9: 185: break;
-: 186: case XLOG_CHECKPOINT_ONLINE:
-: 187:
-: 188: /*
-: 189: * a RUNNING_XACTS record will have been logged near to this, we
-: 190: * can restart from there.
-: 191: */
23: 192: break;
-: 193: case XLOG_NOOP:
-: 194: case XLOG_NEXTOID:
-: 195: case XLOG_SWITCH:
-: 196: case XLOG_BACKUP_END:
-: 197: case XLOG_PARAMETER_CHANGE:
-: 198: case XLOG_RESTORE_POINT:
-: 199: case XLOG_FPW_CHANGE:
-: 200: case XLOG_FPI_FOR_HINT:
-: 201: case XLOG_FPI:
1104: 202: break;
-: 203: default:
#####: 204: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info);
call 0 never executed
call 1 never executed
call 2 never executed
-: 205: }
1136: 206:}
-: 207:
-: 208:/*
-: 209: * Handle rmgr XACT_ID records for DecodeRecordIntoReorderBuffer().
-: 210: */
-: 211:static void
function DecodeXactOp called 1396 returned 100% blocks executed 90%
1396: 212:DecodeXactOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 213:{
1396: 214: SnapBuild *builder = ctx->snapshot_builder;
1396: 215: ReorderBuffer *reorder = ctx->reorder;
1396: 216: XLogReaderState *r = buf->record;
1396: 217: uint8 info = XLogRecGetInfo(r) & XLOG_XACT_OPMASK;
-: 218:
-: 219: /*
-: 220: * If the snapshot isn't yet fully built, we cannot decode anything, so
-: 221: * bail out.
-: 222: *
-: 223: * However, it's critical to process XLOG_XACT_ASSIGNMENT records even
-: 224: * when the snapshot is being built: it is possible to get later records
-: 225: * that require subxids to be properly assigned.
-: 226: */
1396: 227: if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT &&
call 0 returned 100%
branch 1 taken 1% (fallthrough)
branch 2 taken 99%
branch 3 taken 100% (fallthrough)
branch 4 taken 0%
-: 228: info != XLOG_XACT_ASSIGNMENT)
1401: 229: return;
-: 230:
1391: 231: switch (info)
branch 0 taken 90%
branch 1 taken 2%
branch 2 taken 8%
branch 3 taken 1%
branch 4 taken 0%
-: 232: {
-: 233: case XLOG_XACT_COMMIT:
-: 234: case XLOG_XACT_COMMIT_PREPARED:
-: 235: {
-: 236: xl_xact_commit *xlrec;
-: 237: xl_xact_parsed_commit parsed;
-: 238: TransactionId xid;
-: 239:
1256: 240: xlrec = (xl_xact_commit *) XLogRecGetData(r);
1256: 241: ParseCommitRecord(XLogRecGetInfo(buf->record), xlrec, &parsed);
call 0 returned 100%
-: 242:
1256: 243: if (!TransactionIdIsValid(parsed.twophase_xid))
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
1254: 244: xid = XLogRecGetXid(r);
-: 245: else
2: 246: xid = parsed.twophase_xid;
-: 247:
1256: 248: DecodeCommit(ctx, buf, &parsed, xid);
call 0 returned 100%
1256: 249: break;
-: 250: }
-: 251: case XLOG_XACT_ABORT:
-: 252: case XLOG_XACT_ABORT_PREPARED:
-: 253: {
-: 254: xl_xact_abort *xlrec;
-: 255: xl_xact_parsed_abort parsed;
-: 256: TransactionId xid;
-: 257:
23: 258: xlrec = (xl_xact_abort *) XLogRecGetData(r);
23: 259: ParseAbortRecord(XLogRecGetInfo(buf->record), xlrec, &parsed);
call 0 returned 100%
-: 260:
23: 261: if (!TransactionIdIsValid(parsed.twophase_xid))
branch 0 taken 96% (fallthrough)
branch 1 taken 4%
22: 262: xid = XLogRecGetXid(r);
-: 263: else
1: 264: xid = parsed.twophase_xid;
-: 265:
23: 266: DecodeAbort(ctx, buf, &parsed, xid);
call 0 returned 100%
23: 267: break;
-: 268: }
-: 269: case XLOG_XACT_ASSIGNMENT:
-: 270: {
-: 271: xl_xact_assignment *xlrec;
-: 272: int i;
-: 273: TransactionId *sub_xid;
-: 274:
109: 275: xlrec = (xl_xact_assignment *) XLogRecGetData(r);
-: 276:
109: 277: sub_xid = &xlrec->xsub[0];
-: 278:
722: 279: for (i = 0; i < xlrec->nsubxacts; i++)
branch 0 taken 85%
branch 1 taken 15% (fallthrough)
-: 280: {
1226: 281: ReorderBufferAssignChild(reorder, xlrec->xtop,
call 0 returned 100%
613: 282: *(sub_xid++), buf->origptr);
-: 283: }
109: 284: break;
-: 285: }
-: 286: case XLOG_XACT_PREPARE:
-: 287:
-: 288: /*
-: 289: * Currently decoding ignores PREPARE TRANSACTION and will just
-: 290: * decode the transaction when the COMMIT PREPARED is sent or
-: 291: * throw away the transaction's contents when a ROLLBACK PREPARED
-: 292: * is received. In the future we could add code to expose prepared
-: 293: * transactions in the changestream allowing for a kind of
-: 294: * distributed 2PC.
-: 295: */
3: 296: ReorderBufferProcessXid(reorder, XLogRecGetXid(r), buf->origptr);
call 0 returned 100%
3: 297: break;
-: 298: default:
#####: 299: elog(ERROR, "unexpected RM_XACT_ID record type: %u", info);
call 0 never executed
call 1 never executed
call 2 never executed
-: 300: }
-: 301:}
-: 302:
-: 303:/*
-: 304: * Handle rmgr STANDBY_ID records for DecodeRecordIntoReorderBuffer().
-: 305: */
-: 306:static void
function DecodeStandbyOp called 1449 returned 100% blocks executed 54%
1449: 307:DecodeStandbyOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 308:{
1449: 309: SnapBuild *builder = ctx->snapshot_builder;
1449: 310: XLogReaderState *r = buf->record;
1449: 311: uint8 info = XLogRecGetInfo(r) & ~XLR_INFO_MASK;
-: 312:
1449: 313: ReorderBufferProcessXid(ctx->reorder, XLogRecGetXid(r), buf->origptr);
call 0 returned 100%
-: 314:
1449: 315: switch (info)
branch 0 taken 24%
branch 1 taken 76%
branch 2 taken 0%
branch 3 taken 0%
-: 316: {
-: 317: case XLOG_RUNNING_XACTS:
-: 318: {
352: 319: xl_running_xacts *running = (xl_running_xacts *) XLogRecGetData(r);
-: 320:
352: 321: SnapBuildProcessRunningXacts(builder, buf->origptr, running);
call 0 returned 100%
-: 322:
-: 323: /*
-: 324: * Abort all transactions that we keep track of, that are
-: 325: * older than the record's oldestRunningXid. This is the most
-: 326: * convenient spot for doing so since, in contrast to shutdown
-: 327: * or end-of-recovery checkpoints, we have information about
-: 328: * all running transactions which includes prepared ones,
-: 329: * while shutdown checkpoints just know that no non-prepared
-: 330: * transactions are in progress.
-: 331: */
352: 332: ReorderBufferAbortOld(ctx->reorder, running->oldestRunningXid);
call 0 returned 100%
-: 333: }
352: 334: break;
-: 335: case XLOG_STANDBY_LOCK:
1097: 336: break;
-: 337: case XLOG_INVALIDATIONS:
-: 338: {
#####: 339: xl_invalidations *invalidations =
-: 340: (xl_invalidations *) XLogRecGetData(r);
-: 341:
#####: 342: if (!ctx->fast_forward)
branch 0 never executed
branch 1 never executed
#####: 343: ReorderBufferImmediateInvalidation(ctx->reorder,
call 0 never executed
#####: 344: invalidations->nmsgs,
#####: 345: invalidations->msgs);
-: 346: }
#####: 347: break;
-: 348: default:
#####: 349: elog(ERROR, "unexpected RM_STANDBY_ID record type: %u", info);
call 0 never executed
call 1 never executed
call 2 never executed
-: 350: }
1449: 351:}
-: 352:
-: 353:/*
-: 354: * Handle rmgr HEAP2_ID records for DecodeRecordIntoReorderBuffer().
-: 355: */
-: 356:static void
function DecodeHeap2Op called 14071 returned 100% blocks executed 84%
14071: 357:DecodeHeap2Op(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 358:{
14071: 359: uint8 info = XLogRecGetInfo(buf->record) & XLOG_HEAP_OPMASK;
14071: 360: TransactionId xid = XLogRecGetXid(buf->record);
14071: 361: SnapBuild *builder = ctx->snapshot_builder;
-: 362:
14071: 363: ReorderBufferProcessXid(ctx->reorder, xid, buf->origptr);
call 0 returned 100%
-: 364:
-: 365: /*
-: 366: * If we don't have snapshot or we are just fast-forwarding, there is no
-: 367: * point in decoding changes.
-: 368: */
28132: 369: if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT ||
call 0 returned 100%
branch 1 taken 99% (fallthrough)
branch 2 taken 1%
branch 3 taken 0% (fallthrough)
branch 4 taken 100%
14061: 370: ctx->fast_forward)
14081: 371: return;
-: 372:
14061: 373: switch (info)
branch 0 taken 1%
branch 1 taken 97%
branch 2 taken 1%
branch 3 taken 2%
branch 4 taken 0%
-: 374: {
-: 375: case XLOG_HEAP2_MULTI_INSERT:
16: 376: if (!ctx->fast_forward &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
8: 377: SnapBuildProcessChange(builder, xid, buf->origptr))
call 0 returned 100%
8: 378: DecodeMultiInsert(ctx, buf);
call 0 returned 100%
8: 379: break;
-: 380: case XLOG_HEAP2_NEW_CID:
-: 381: {
-: 382: xl_heap_new_cid *xlrec;
-: 383:
13630: 384: xlrec = (xl_heap_new_cid *) XLogRecGetData(buf->record);
13630: 385: SnapBuildProcessNewCid(builder, xid, buf->origptr, xlrec);
call 0 returned 100%
-: 386:
13630: 387: break;
-: 388: }
-: 389: case XLOG_HEAP2_REWRITE:
-: 390:
-: 391: /*
-: 392: * Although these records only exist to serve the needs of logical
-: 393: * decoding, all the work happens as part of crash or archive
-: 394: * recovery, so we don't need to do anything here.
-: 395: */
89: 396: break;
-: 397:
-: 398: /*
-: 399: * Everything else here is just low level physical stuff we're not
-: 400: * interested in.
-: 401: */
-: 402: case XLOG_HEAP2_FREEZE_PAGE:
-: 403: case XLOG_HEAP2_CLEAN:
-: 404: case XLOG_HEAP2_CLEANUP_INFO:
-: 405: case XLOG_HEAP2_VISIBLE:
-: 406: case XLOG_HEAP2_LOCK_UPDATED:
334: 407: break;
-: 408: default:
#####: 409: elog(ERROR, "unexpected RM_HEAP2_ID record type: %u", info);
call 0 never executed
call 1 never executed
call 2 never executed
-: 410: }
-: 411:}
-: 412:
-: 413:/*
-: 414: * Handle rmgr HEAP_ID records for DecodeRecordIntoReorderBuffer().
-: 415: */
-: 416:static void
function DecodeHeapOp called 1301389 returned 100% blocks executed 92%
1301389: 417:DecodeHeapOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 418:{
1301389: 419: uint8 info = XLogRecGetInfo(buf->record) & XLOG_HEAP_OPMASK;
1301389: 420: TransactionId xid = XLogRecGetXid(buf->record);
1301389: 421: SnapBuild *builder = ctx->snapshot_builder;
-: 422:
1301389: 423: ReorderBufferProcessXid(ctx->reorder, xid, buf->origptr);
call 0 returned 100%
-: 424:
-: 425: /*
-: 426: * If we don't have snapshot or we are just fast-forwarding, there is no
-: 427: * point in decoding data changes.
-: 428: */
2602774: 429: if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT ||
call 0 returned 100%
branch 1 taken 99% (fallthrough)
branch 2 taken 1%
branch 3 taken 1% (fallthrough)
branch 4 taken 99%
1301385: 430: ctx->fast_forward)
1301400: 431: return;
-: 432:
1301378: 433: switch (info)
branch 0 taken 74%
branch 1 taken 7%
branch 2 taken 9%
branch 3 taken 1%
branch 4 taken 1%
branch 5 taken 1%
branch 6 taken 8%
branch 7 taken 0%
-: 434: {
-: 435: case XLOG_HEAP_INSERT:
962824: 436: if (SnapBuildProcessChange(builder, xid, buf->origptr))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
962824: 437: DecodeInsert(ctx, buf);
call 0 returned 100%
962824: 438: break;
-: 439:
-: 440: /*
-: 441: * Treat HOT update as normal updates. There is no useful
-: 442: * information in the fact that we could make it a HOT update
-: 443: * locally and the WAL layout is compatible.
-: 444: */
-: 445: case XLOG_HEAP_HOT_UPDATE:
-: 446: case XLOG_HEAP_UPDATE:
87059: 447: if (SnapBuildProcessChange(builder, xid, buf->origptr))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
87059: 448: DecodeUpdate(ctx, buf);
call 0 returned 100%
87059: 449: break;
-: 450:
-: 451: case XLOG_HEAP_DELETE:
123546: 452: if (SnapBuildProcessChange(builder, xid, buf->origptr))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
123546: 453: DecodeDelete(ctx, buf);
call 0 returned 100%
123546: 454: break;
-: 455:
-: 456: case XLOG_HEAP_TRUNCATE:
8: 457: if (SnapBuildProcessChange(builder, xid, buf->origptr))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
8: 458: DecodeTruncate(ctx, buf);
call 0 returned 100%
8: 459: break;
-: 460:
-: 461: case XLOG_HEAP_INPLACE:
-: 462:
-: 463: /*
-: 464: * Inplace updates are only ever performed on catalog tuples and
-: 465: * can, per definition, not change tuple visibility. Since we
-: 466: * don't decode catalog tuples, we're not interested in the
-: 467: * record's contents.
-: 468: *
-: 469: * In-place updates can be used either by XID-bearing transactions
-: 470: * (e.g. in CREATE INDEX CONCURRENTLY) or by XID-less
-: 471: * transactions (e.g. VACUUM). In the former case, the commit
-: 472: * record will include cache invalidations, so we mark the
-: 473: * transaction as catalog modifying here. Currently that's
-: 474: * redundant because the commit will do that as well, but once we
-: 475: * support decoding in-progress relations, this will be important.
-: 476: */
603: 477: if (!TransactionIdIsValid(xid))
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
3: 478: break;
-: 479:
600: 480: SnapBuildProcessChange(builder, xid, buf->origptr);
call 0 returned 100%
600: 481: ReorderBufferXidSetCatalogChanges(ctx->reorder, xid, buf->origptr);
call 0 returned 100%
600: 482: break;
-: 483:
-: 484: case XLOG_HEAP_CONFIRM:
17900: 485: if (SnapBuildProcessChange(builder, xid, buf->origptr))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
17900: 486: DecodeSpecConfirm(ctx, buf);
call 0 returned 100%
17900: 487: break;
-: 488:
-: 489: case XLOG_HEAP_LOCK:
-: 490: /* we don't care about row level locks for now */
109438: 491: break;
-: 492:
-: 493: default:
#####: 494: elog(ERROR, "unexpected RM_HEAP_ID record type: %u", info);
call 0 never executed
call 1 never executed
call 2 never executed
-: 495: break;
-: 496: }
-: 497:}
-: 498:
-: 499:static inline bool
function FilterByOrigin called 1182743 returned 100% blocks executed 75%
1182743: 500:FilterByOrigin(LogicalDecodingContext *ctx, RepOriginId origin_id)
-: 501:{
1182743: 502: if (ctx->callbacks.filter_by_origin_cb == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 503: return false;
-: 504:
1182743: 505: return filter_by_origin_cb_wrapper(ctx, origin_id);
call 0 returned 100%
-: 506:}
-: 507:
-: 508:/*
-: 509: * Handle rmgr LOGICALMSG_ID records for DecodeRecordIntoReorderBuffer().
-: 510: */
-: 511:static void
function DecodeLogicalMsgOp called 32 returned 100% blocks executed 81%
32: 512:DecodeLogicalMsgOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 513:{
32: 514: SnapBuild *builder = ctx->snapshot_builder;
32: 515: XLogReaderState *r = buf->record;
32: 516: TransactionId xid = XLogRecGetXid(r);
32: 517: uint8 info = XLogRecGetInfo(r) & ~XLR_INFO_MASK;
32: 518: RepOriginId origin_id = XLogRecGetOrigin(r);
-: 519: Snapshot snapshot;
-: 520: xl_logical_message *message;
-: 521:
32: 522: if (info != XLOG_LOGICAL_MESSAGE)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 523: elog(ERROR, "unexpected RM_LOGICALMSG_ID record type: %u", info);
call 0 never executed
call 1 never executed
call 2 never executed
-: 524:
32: 525: ReorderBufferProcessXid(ctx->reorder, XLogRecGetXid(r), buf->origptr);
call 0 returned 100%
-: 526:
-: 527: /*
-: 528: * If we don't have snapshot or we are just fast-forwarding, there is no
-: 529: * point in decoding messages.
-: 530: */
64: 531: if (SnapBuildCurrentState(builder) < SNAPBUILD_FULL_SNAPSHOT ||
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
branch 3 taken 0% (fallthrough)
branch 4 taken 100%
32: 532: ctx->fast_forward)
#####: 533: return;
-: 534:
32: 535: message = (xl_logical_message *) XLogRecGetData(r);
-: 536:
62: 537: if (message->dbId != ctx->slot->data.database ||
branch 0 taken 94% (fallthrough)
branch 1 taken 6%
branch 2 taken 7% (fallthrough)
branch 3 taken 93%
30: 538: FilterByOrigin(ctx, origin_id))
call 0 returned 100%
4: 539: return;
-: 540:
50: 541: if (message->transactional &&
branch 0 taken 79% (fallthrough)
branch 1 taken 21%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
22: 542: !SnapBuildProcessChange(builder, xid, buf->origptr))
call 0 returned 100%
#####: 543: return;
34: 544: else if (!message->transactional &&
branch 0 taken 21% (fallthrough)
branch 1 taken 79%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
12: 545: (SnapBuildCurrentState(builder) != SNAPBUILD_CONSISTENT ||
call 0 returned 100%
branch 1 taken 50% (fallthrough)
branch 2 taken 50%
6: 546: SnapBuildXactNeedsSkip(builder, buf->origptr)))
call 0 returned 100%
3: 547: return;
-: 548:
25: 549: snapshot = SnapBuildGetOrBuildSnapshot(builder, xid);
call 0 returned 100%
50: 550: ReorderBufferQueueMessage(ctx->reorder, xid, snapshot, buf->endptr,
call 0 returned 100%
25: 551: message->transactional,
25: 552: message->message, /* first part of message is
-: 553: * prefix */
-: 554: message->message_size,
25: 555: message->message + message->prefix_size);
-: 556:}
-: 557:
-: 558:/*
-: 559: * Consolidated commit record handling between the different form of commit
-: 560: * records.
-: 561: */
-: 562:static void
function DecodeCommit called 1256 returned 100% blocks executed 100%
1256: 563:DecodeCommit(LogicalDecodingContext *ctx, XLogRecordBuffer *buf,
-: 564: xl_xact_parsed_commit *parsed, TransactionId xid)
-: 565:{
1256: 566: XLogRecPtr origin_lsn = InvalidXLogRecPtr;
1256: 567: TimestampTz commit_time = parsed->xact_time;
1256: 568: RepOriginId origin_id = XLogRecGetOrigin(buf->record);
-: 569: int i;
-: 570:
1256: 571: if (parsed->xinfo & XACT_XINFO_HAS_ORIGIN)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 572: {
2: 573: origin_lsn = parsed->origin_lsn;
2: 574: commit_time = parsed->origin_timestamp;
-: 575: }
-: 576:
-: 577: /*
-: 578: * Process invalidation messages, even if we're not interested in the
-: 579: * transaction's contents, since the various caches need to always be
-: 580: * consistent.
-: 581: */
1256: 582: if (parsed->nmsgs > 0)
branch 0 taken 36% (fallthrough)
branch 1 taken 64%
-: 583: {
451: 584: if (!ctx->fast_forward)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
902: 585: ReorderBufferAddInvalidations(ctx->reorder, xid, buf->origptr,
call 0 returned 100%
451: 586: parsed->nmsgs, parsed->msgs);
451: 587: ReorderBufferXidSetCatalogChanges(ctx->reorder, xid, buf->origptr);
call 0 returned 100%
-: 588: }
-: 589:
1256: 590: SnapBuildCommitTxn(ctx->snapshot_builder, buf->origptr, xid,
call 0 returned 100%
-: 591: parsed->nsubxacts, parsed->subxacts);
-: 592:
-: 593: /* ----
-: 594: * Check whether we are interested in this specific transaction, and tell
-: 595: * the reorderbuffer to forget the content of the (sub-)transactions
-: 596: * if not.
-: 597: *
-: 598: * There can be several reasons we might not be interested in this
-: 599: * transaction:
-: 600: * 1) We might not be interested in decoding transactions up to this
-: 601: * LSN. This can happen because we previously decoded it and now just
-: 602: * are restarting or if we haven't assembled a consistent snapshot yet.
-: 603: * 2) The transaction happened in another database.
-: 604: * 3) The output plugin is not interested in the origin.
-: 605: * 4) We are doing fast-forwarding
-: 606: *
-: 607: * We can't just use ReorderBufferAbort() here, because we need to execute
-: 608: * the transaction's invalidations. This currently won't be needed if
-: 609: * we're just skipping over the transaction because currently we only do
-: 610: * so during startup, to get to the first transaction the client needs. As
-: 611: * we have reset the catalog caches before starting to read WAL, and we
-: 612: * haven't yet touched any catalogs, there can't be anything to invalidate.
-: 613: * But if we're "forgetting" this commit because it's it happened in
-: 614: * another database, the invalidations might be important, because they
-: 615: * could be for shared catalogs and we might have loaded data into the
-: 616: * relevant syscaches.
-: 617: * ---
-: 618: */
1710: 619: if (SnapBuildXactNeedsSkip(ctx->snapshot_builder, buf->origptr) ||
call 0 returned 100%
branch 1 taken 36% (fallthrough)
branch 2 taken 64%
branch 3 taken 100% (fallthrough)
branch 4 taken 0%
1361: 620: (parsed->dbId != InvalidOid && parsed->dbId != ctx->slot->data.database) ||
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 99% (fallthrough)
branch 3 taken 1%
901: 621: ctx->fast_forward || FilterByOrigin(ctx, origin_id))
call 0 returned 100%
branch 1 taken 1% (fallthrough)
branch 2 taken 99%
-: 622: {
1519: 623: for (i = 0; i < parsed->nsubxacts; i++)
branch 0 taken 47%
branch 1 taken 53% (fallthrough)
-: 624: {
710: 625: ReorderBufferForget(ctx->reorder, parsed->subxacts[i], buf->origptr);
call 0 returned 100%
-: 626: }
809: 627: ReorderBufferForget(ctx->reorder, xid, buf->origptr);
call 0 returned 100%
-: 628:
2065: 629: return;
-: 630: }
-: 631:
-: 632: /* tell the reorderbuffer about the surviving subtransactions */
561: 633: for (i = 0; i < parsed->nsubxacts; i++)
branch 0 taken 20%
branch 1 taken 80% (fallthrough)
-: 634: {
114: 635: ReorderBufferCommitChild(ctx->reorder, xid, parsed->subxacts[i],
call 0 returned 100%
-: 636: buf->origptr, buf->endptr);
-: 637: }
-: 638:
-: 639: /* replay actions of all transaction + subtransactions in order */
447: 640: ReorderBufferCommit(ctx->reorder, xid, buf->origptr, buf->endptr,
call 0 returned 100%
-: 641: commit_time, origin_id, origin_lsn);
-: 642:}
-: 643:
-: 644:/*
-: 645: * Get the data from the various forms of abort records and pass it on to
-: 646: * snapbuild.c and reorderbuffer.c
-: 647: */
-: 648:static void
function DecodeAbort called 23 returned 100% blocks executed 67%
23: 649:DecodeAbort(LogicalDecodingContext *ctx, XLogRecordBuffer *buf,
-: 650: xl_xact_parsed_abort *parsed, TransactionId xid)
-: 651:{
-: 652: int i;
-: 653:
23: 654: for (i = 0; i < parsed->nsubxacts; i++)
branch 0 taken 0%
branch 1 taken 100% (fallthrough)
-: 655: {
#####: 656: ReorderBufferAbort(ctx->reorder, parsed->subxacts[i],
call 0 never executed
#####: 657: buf->record->EndRecPtr);
-: 658: }
-: 659:
23: 660: ReorderBufferAbort(ctx->reorder, xid, buf->record->EndRecPtr);
call 0 returned 100%
23: 661:}
-: 662:
-: 663:/*
-: 664: * Parse XLOG_HEAP_INSERT (not MULTI_INSERT!) records into tuplebufs.
-: 665: *
-: 666: * Deletes can contain the new tuple.
-: 667: */
-: 668:static void
function DecodeInsert called 962824 returned 100% blocks executed 95%
962824: 669:DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 670:{
-: 671: Size datalen;
-: 672: char *tupledata;
-: 673: Size tuplelen;
962824: 674: XLogReaderState *r = buf->record;
-: 675: xl_heap_insert *xlrec;
-: 676: ReorderBufferChange *change;
-: 677: RelFileNode target_node;
-: 678:
962824: 679: xlrec = (xl_heap_insert *) XLogRecGetData(r);
-: 680:
-: 681: /*
-: 682: * Ignore insert records without new tuples (this does happen when
-: 683: * raw_heap_insert marks the TOAST record as HEAP_INSERT_NO_LOGICAL).
-: 684: */
962824: 685: if (!(xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE))
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
18162: 686: return;
-: 687:
-: 688: /* only interested in our database */
953746: 689: XLogRecGetBlockTag(r, 0, &target_node, NULL, NULL);
call 0 returned 100%
953746: 690: if (target_node.dbNode != ctx->slot->data.database)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 691: return;
-: 692:
-: 693: /* output plugin doesn't look for this origin, no need to queue */
953746: 694: if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
call 0 returned 100%
branch 1 taken 1% (fallthrough)
branch 2 taken 99%
6: 695: return;
-: 696:
953740: 697: change = ReorderBufferGetChange(ctx->reorder);
call 0 returned 100%
953740: 698: if (!(xlrec->flags & XLH_INSERT_IS_SPECULATIVE))
branch 0 taken 98% (fallthrough)
branch 1 taken 2%
935840: 699: change->action = REORDER_BUFFER_CHANGE_INSERT;
-: 700: else
17900: 701: change->action = REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT;
953740: 702: change->origin_id = XLogRecGetOrigin(r);
-: 703:
953740: 704: memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode));
-: 705:
953740: 706: tupledata = XLogRecGetBlockData(r, 0, &datalen);
call 0 returned 100%
953740: 707: tuplelen = datalen - SizeOfHeapHeader;
-: 708:
953740: 709: change->data.tp.newtuple =
953740: 710: ReorderBufferGetTupleBuf(ctx->reorder, tuplelen);
call 0 returned 100%
-: 711:
953740: 712: DecodeXLogTuple(tupledata, datalen, change->data.tp.newtuple);
call 0 returned 100%
-: 713:
953740: 714: change->data.tp.clear_toast_afterwards = true;
-: 715:
953740: 716: ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r), buf->origptr, change);
call 0 returned 100%
-: 717:}
-: 718:
-: 719:/*
-: 720: * Parse XLOG_HEAP_UPDATE and XLOG_HEAP_HOT_UPDATE, which have the same layout
-: 721: * in the record, from wal into proper tuplebufs.
-: 722: *
-: 723: * Updates can possibly contain a new tuple and the old primary key.
-: 724: */
-: 725:static void
function DecodeUpdate called 87059 returned 100% blocks executed 84%
87059: 726:DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 727:{
87059: 728: XLogReaderState *r = buf->record;
-: 729: xl_heap_update *xlrec;
-: 730: ReorderBufferChange *change;
-: 731: char *data;
-: 732: RelFileNode target_node;
-: 733:
87059: 734: xlrec = (xl_heap_update *) XLogRecGetData(r);
-: 735:
-: 736: /* only interested in our database */
87059: 737: XLogRecGetBlockTag(r, 0, &target_node, NULL, NULL);
call 0 returned 100%
87059: 738: if (target_node.dbNode != ctx->slot->data.database)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 739: return;
-: 740:
-: 741: /* output plugin doesn't look for this origin, no need to queue */
87059: 742: if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 743: return;
-: 744:
87059: 745: change = ReorderBufferGetChange(ctx->reorder);
call 0 returned 100%
87059: 746: change->action = REORDER_BUFFER_CHANGE_UPDATE;
87059: 747: change->origin_id = XLogRecGetOrigin(r);
87059: 748: memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode));
-: 749:
87059: 750: if (xlrec->flags & XLH_UPDATE_CONTAINS_NEW_TUPLE)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
-: 751: {
-: 752: Size datalen;
-: 753: Size tuplelen;
-: 754:
86054: 755: data = XLogRecGetBlockData(r, 0, &datalen);
call 0 returned 100%
-: 756:
86054: 757: tuplelen = datalen - SizeOfHeapHeader;
-: 758:
86054: 759: change->data.tp.newtuple =
86054: 760: ReorderBufferGetTupleBuf(ctx->reorder, tuplelen);
call 0 returned 100%
-: 761:
86054: 762: DecodeXLogTuple(data, datalen, change->data.tp.newtuple);
call 0 returned 100%
-: 763: }
-: 764:
87059: 765: if (xlrec->flags & XLH_UPDATE_CONTAINS_OLD)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 766: {
-: 767: Size datalen;
-: 768: Size tuplelen;
-: 769:
-: 770: /* caution, remaining data in record is not aligned */
382: 771: data = XLogRecGetData(r) + SizeOfHeapUpdate;
382: 772: datalen = XLogRecGetDataLen(r) - SizeOfHeapUpdate;
382: 773: tuplelen = datalen - SizeOfHeapHeader;
-: 774:
382: 775: change->data.tp.oldtuple =
382: 776: ReorderBufferGetTupleBuf(ctx->reorder, tuplelen);
call 0 returned 100%
-: 777:
382: 778: DecodeXLogTuple(data, datalen, change->data.tp.oldtuple);
call 0 returned 100%
-: 779: }
-: 780:
87059: 781: change->data.tp.clear_toast_afterwards = true;
-: 782:
87059: 783: ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r), buf->origptr, change);
call 0 returned 100%
-: 784:}
-: 785:
-: 786:/*
-: 787: * Parse XLOG_HEAP_DELETE from wal into proper tuplebufs.
-: 788: *
-: 789: * Deletes can possibly contain the old primary key.
-: 790: */
-: 791:static void
function DecodeDelete called 123546 returned 100% blocks executed 83%
123546: 792:DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 793:{
123546: 794: XLogReaderState *r = buf->record;
-: 795: xl_heap_delete *xlrec;
-: 796: ReorderBufferChange *change;
-: 797: RelFileNode target_node;
-: 798:
123546: 799: xlrec = (xl_heap_delete *) XLogRecGetData(r);
-: 800:
-: 801: /* only interested in our database */
123546: 802: XLogRecGetBlockTag(r, 0, &target_node, NULL, NULL);
call 0 returned 100%
123546: 803: if (target_node.dbNode != ctx->slot->data.database)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
4: 804: return;
-: 805:
-: 806: /*
-: 807: * Super deletions are irrelevant for logical decoding, it's driven by the
-: 808: * confirmation records.
-: 809: */
123544: 810: if (xlrec->flags & XLH_DELETE_IS_SUPER)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 811: return;
-: 812:
-: 813: /* output plugin doesn't look for this origin, no need to queue */
123544: 814: if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 815: return;
-: 816:
123544: 817: change = ReorderBufferGetChange(ctx->reorder);
call 0 returned 100%
123544: 818: change->action = REORDER_BUFFER_CHANGE_DELETE;
123544: 819: change->origin_id = XLogRecGetOrigin(r);
-: 820:
123544: 821: memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode));
-: 822:
-: 823: /* old primary key stored */
123544: 824: if (xlrec->flags & XLH_DELETE_CONTAINS_OLD)
branch 0 taken 49% (fallthrough)
branch 1 taken 51%
-: 825: {
60629: 826: Size datalen = XLogRecGetDataLen(r) - SizeOfHeapDelete;
60629: 827: Size tuplelen = datalen - SizeOfHeapHeader;
-: 828:
60629: 829: Assert(XLogRecGetDataLen(r) > (SizeOfHeapDelete + SizeOfHeapHeader));
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 830:
60629: 831: change->data.tp.oldtuple =
60629: 832: ReorderBufferGetTupleBuf(ctx->reorder, tuplelen);
call 0 returned 100%
-: 833:
60629: 834: DecodeXLogTuple((char *) xlrec + SizeOfHeapDelete,
call 0 returned 100%
-: 835: datalen, change->data.tp.oldtuple);
-: 836: }
-: 837:
123544: 838: change->data.tp.clear_toast_afterwards = true;
-: 839:
123544: 840: ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r), buf->origptr, change);
call 0 returned 100%
-: 841:}
-: 842:
-: 843:/*
-: 844: * Parse XLOG_HEAP_TRUNCATE from wal
-: 845: */
-: 846:static void
function DecodeTruncate called 8 returned 100% blocks executed 85%
8: 847:DecodeTruncate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 848:{
8: 849: XLogReaderState *r = buf->record;
-: 850: xl_heap_truncate *xlrec;
-: 851: ReorderBufferChange *change;
-: 852:
8: 853: xlrec = (xl_heap_truncate *) XLogRecGetData(r);
-: 854:
-: 855: /* only interested in our database */
8: 856: if (xlrec->dbId != ctx->slot->data.database)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 857: return;
-: 858:
-: 859: /* output plugin doesn't look for this origin, no need to queue */
8: 860: if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 861: return;
-: 862:
8: 863: change = ReorderBufferGetChange(ctx->reorder);
call 0 returned 100%
8: 864: change->action = REORDER_BUFFER_CHANGE_TRUNCATE;
8: 865: change->origin_id = XLogRecGetOrigin(r);
8: 866: if (xlrec->flags & XLH_TRUNCATE_CASCADE)
branch 0 taken 13% (fallthrough)
branch 1 taken 88%
1: 867: change->data.truncate.cascade = true;
8: 868: if (xlrec->flags & XLH_TRUNCATE_RESTART_SEQS)
branch 0 taken 25% (fallthrough)
branch 1 taken 75%
2: 869: change->data.truncate.restart_seqs = true;
8: 870: change->data.truncate.nrelids = xlrec->nrelids;
8: 871: change->data.truncate.relids = ReorderBufferGetRelids(ctx->reorder,
call 0 returned 100%
8: 872: xlrec->nrelids);
8: 873: memcpy(change->data.truncate.relids, xlrec->relids,
8: 874: xlrec->nrelids * sizeof(Oid));
8: 875: ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r),
call 0 returned 100%
-: 876: buf->origptr, change);
-: 877:}
-: 878:
-: 879:/*
-: 880: * Decode XLOG_HEAP2_MULTI_INSERT_insert record into multiple tuplebufs.
-: 881: *
-: 882: * Currently MULTI_INSERT will always contain the full tuples.
-: 883: */
-: 884:static void
function DecodeMultiInsert called 8 returned 100% blocks executed 77%
8: 885:DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 886:{
8: 887: XLogReaderState *r = buf->record;
-: 888: xl_heap_multi_insert *xlrec;
-: 889: int i;
-: 890: char *data;
-: 891: char *tupledata;
-: 892: Size tuplelen;
-: 893: RelFileNode rnode;
-: 894:
8: 895: xlrec = (xl_heap_multi_insert *) XLogRecGetData(r);
-: 896:
-: 897: /* only interested in our database */
8: 898: XLogRecGetBlockTag(r, 0, &rnode, NULL, NULL);
call 0 returned 100%
8: 899: if (rnode.dbNode != ctx->slot->data.database)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 900: return;
-: 901:
-: 902: /* output plugin doesn't look for this origin, no need to queue */
8: 903: if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 904: return;
-: 905:
-: 906: /*
-: 907: * As multi_insert is not used for catalogs yet, the block should always
-: 908: * have data even if a full-page write of it is taken.
-: 909: */
8: 910: tupledata = XLogRecGetBlockData(r, 0, &tuplelen);
call 0 returned 100%
8: 911: Assert(tupledata != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 912:
8: 913: data = tupledata;
820: 914: for (i = 0; i < xlrec->ntuples; i++)
branch 0 taken 99%
branch 1 taken 1% (fallthrough)
-: 915: {
-: 916: ReorderBufferChange *change;
-: 917: xl_multi_insert_tuple *xlhdr;
-: 918: int datalen;
-: 919: ReorderBufferTupleBuf *tuple;
-: 920:
812: 921: change = ReorderBufferGetChange(ctx->reorder);
call 0 returned 100%
812: 922: change->action = REORDER_BUFFER_CHANGE_INSERT;
812: 923: change->origin_id = XLogRecGetOrigin(r);
-: 924:
812: 925: memcpy(&change->data.tp.relnode, &rnode, sizeof(RelFileNode));
-: 926:
812: 927: xlhdr = (xl_multi_insert_tuple *) SHORTALIGN(data);
812: 928: data = ((char *) xlhdr) + SizeOfMultiInsertTuple;
812: 929: datalen = xlhdr->datalen;
-: 930:
-: 931: /*
-: 932: * CONTAINS_NEW_TUPLE will always be set currently as multi_insert
-: 933: * isn't used for catalogs, but better be future proof.
-: 934: *
-: 935: * We decode the tuple in pretty much the same way as DecodeXLogTuple,
-: 936: * but since the layout is slightly different, we can't use it here.
-: 937: */
812: 938: if (xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 939: {
-: 940: HeapTupleHeader header;
-: 941:
812: 942: change->data.tp.newtuple =
812: 943: ReorderBufferGetTupleBuf(ctx->reorder, datalen);
call 0 returned 100%
-: 944:
812: 945: tuple = change->data.tp.newtuple;
812: 946: header = tuple->tuple.t_data;
-: 947:
-: 948: /* not a disk based tuple */
812: 949: ItemPointerSetInvalid(&tuple->tuple.t_self);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
branch 3 taken 0% (fallthrough)
branch 4 taken 100%
call 5 never executed
-: 950:
-: 951: /*
-: 952: * We can only figure this out after reassembling the
-: 953: * transactions.
-: 954: */
812: 955: tuple->tuple.t_tableOid = InvalidOid;
-: 956:
812: 957: tuple->tuple.t_len = datalen + SizeofHeapTupleHeader;
-: 958:
812: 959: memset(header, 0, SizeofHeapTupleHeader);
-: 960:
812: 961: memcpy((char *) tuple->tuple.t_data + SizeofHeapTupleHeader,
-: 962: (char *) data,
-: 963: datalen);
812: 964: header->t_infomask = xlhdr->t_infomask;
812: 965: header->t_infomask2 = xlhdr->t_infomask2;
812: 966: header->t_hoff = xlhdr->t_hoff;
-: 967: }
-: 968:
-: 969: /*
-: 970: * Reset toast reassembly state only after the last row in the last
-: 971: * xl_multi_insert_tuple record emitted by one heap_multi_insert()
-: 972: * call.
-: 973: */
936: 974: if (xlrec->flags & XLH_INSERT_LAST_IN_MULTI &&
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
branch 2 taken 3% (fallthrough)
branch 3 taken 97%
124: 975: (i + 1) == xlrec->ntuples)
4: 976: change->data.tp.clear_toast_afterwards = true;
-: 977: else
808: 978: change->data.tp.clear_toast_afterwards = false;
-: 979:
812: 980: ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r),
call 0 returned 100%
-: 981: buf->origptr, change);
-: 982:
-: 983: /* move to the next xl_multi_insert_tuple entry */
812: 984: data += datalen;
-: 985: }
8: 986: Assert(data == tupledata + tuplelen);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 987:}
-: 988:
-: 989:/*
-: 990: * Parse XLOG_HEAP_CONFIRM from wal into a confirmation change.
-: 991: *
-: 992: * This is pretty trivial, all the state essentially already setup by the
-: 993: * speculative insertion.
-: 994: */
-: 995:static void
function DecodeSpecConfirm called 17900 returned 100% blocks executed 73%
17900: 996:DecodeSpecConfirm(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
-: 997:{
17900: 998: XLogReaderState *r = buf->record;
-: 999: ReorderBufferChange *change;
-: 1000: RelFileNode target_node;
-: 1001:
-: 1002: /* only interested in our database */
17900: 1003: XLogRecGetBlockTag(r, 0, &target_node, NULL, NULL);
call 0 returned 100%
17900: 1004: if (target_node.dbNode != ctx->slot->data.database)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1005: return;
-: 1006:
-: 1007: /* output plugin doesn't look for this origin, no need to queue */
17900: 1008: if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 1009: return;
-: 1010:
17900: 1011: change = ReorderBufferGetChange(ctx->reorder);
call 0 returned 100%
17900: 1012: change->action = REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM;
17900: 1013: change->origin_id = XLogRecGetOrigin(r);
-: 1014:
17900: 1015: memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode));
-: 1016:
17900: 1017: change->data.tp.clear_toast_afterwards = true;
-: 1018:
17900: 1019: ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r), buf->origptr, change);
call 0 returned 100%
-: 1020:}
-: 1021:
-: 1022:
-: 1023:/*
-: 1024: * Read a HeapTuple as WAL logged by heap_insert, heap_update and heap_delete
-: 1025: * (but not by heap_multi_insert) into a tuplebuf.
-: 1026: *
-: 1027: * The size 'len' and the pointer 'data' in the record need to be
-: 1028: * computed outside as they are record specific.
-: 1029: */
-: 1030:static void
function DecodeXLogTuple called 1100805 returned 100% blocks executed 57%
1100805: 1031:DecodeXLogTuple(char *data, Size len, ReorderBufferTupleBuf *tuple)
-: 1032:{
-: 1033: xl_heap_header xlhdr;
1100805: 1034: int datalen = len - SizeOfHeapHeader;
-: 1035: HeapTupleHeader header;
-: 1036:
1100805: 1037: Assert(datalen >= 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1038:
1100805: 1039: tuple->tuple.t_len = datalen + SizeofHeapTupleHeader;
1100805: 1040: header = tuple->tuple.t_data;
-: 1041:
-: 1042: /* not a disk based tuple */
1100805: 1043: ItemPointerSetInvalid(&tuple->tuple.t_self);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
branch 3 taken 0% (fallthrough)
branch 4 taken 100%
call 5 never executed
-: 1044:
-: 1045: /* we can only figure this out after reassembling the transactions */
1100805: 1046: tuple->tuple.t_tableOid = InvalidOid;
-: 1047:
-: 1048: /* data is not stored aligned, copy to aligned storage */
1100805: 1049: memcpy((char *) &xlhdr,
-: 1050: data,
-: 1051: SizeOfHeapHeader);
-: 1052:
1100805: 1053: memset(header, 0, SizeofHeapTupleHeader);
-: 1054:
2201610: 1055: memcpy(((char *) tuple->tuple.t_data) + SizeofHeapTupleHeader,
1100805: 1056: data + SizeOfHeapHeader,
-: 1057: datalen);
-: 1058:
1100805: 1059: header->t_infomask = xlhdr.t_infomask;
1100805: 1060: header->t_infomask2 = xlhdr.t_infomask2;
1100805: 1061: header->t_hoff = xlhdr.t_hoff;
1100805: 1062:}
base_code_coverage/logical.c.gcov 0000664 0001750 0000000 00000174017 13560201434 016555 0 ustar vignesh root -: 0:Source:logical.c
-: 0:Graph:./logical.gcno
-: 0:Data:./logical.gcda
-: 0:Runs:6535
-: 0:Programs:1
-: 1:/*-------------------------------------------------------------------------
-: 2: * logical.c
-: 3: * PostgreSQL logical decoding coordination
-: 4: *
-: 5: * Copyright (c) 2012-2019, PostgreSQL Global Development Group
-: 6: *
-: 7: * IDENTIFICATION
-: 8: * src/backend/replication/logical/logical.c
-: 9: *
-: 10: * NOTES
-: 11: * This file coordinates interaction between the various modules that
-: 12: * together provide logical decoding, primarily by providing so
-: 13: * called LogicalDecodingContexts. The goal is to encapsulate most of the
-: 14: * internal complexity for consumers of logical decoding, so they can
-: 15: * create and consume a changestream with a low amount of code. Builtin
-: 16: * consumers are the walsender and SQL SRF interface, but it's possible to
-: 17: * add further ones without changing core code, e.g. to consume changes in
-: 18: * a bgworker.
-: 19: *
-: 20: * The idea is that a consumer provides three callbacks, one to read WAL,
-: 21: * one to prepare a data write, and a final one for actually writing since
-: 22: * their implementation depends on the type of consumer. Check
-: 23: * logicalfuncs.c for an example implementation of a fairly simple consumer
-: 24: * and an implementation of a WAL reading callback that's suitable for
-: 25: * simple consumers.
-: 26: *-------------------------------------------------------------------------
-: 27: */
-: 28:
-: 29:#include "postgres.h"
-: 30:
-: 31:#include "fmgr.h"
-: 32:#include "miscadmin.h"
-: 33:
-: 34:#include "access/xact.h"
-: 35:#include "access/xlog_internal.h"
-: 36:
-: 37:#include "replication/decode.h"
-: 38:#include "replication/logical.h"
-: 39:#include "replication/reorderbuffer.h"
-: 40:#include "replication/origin.h"
-: 41:#include "replication/snapbuild.h"
-: 42:
-: 43:#include "storage/proc.h"
-: 44:#include "storage/procarray.h"
-: 45:
-: 46:#include "utils/memutils.h"
-: 47:
-: 48:/* data for errcontext callback */
-: 49:typedef struct LogicalErrorCallbackState
-: 50:{
-: 51: LogicalDecodingContext *ctx;
-: 52: const char *callback_name;
-: 53: XLogRecPtr report_location;
-: 54:} LogicalErrorCallbackState;
-: 55:
-: 56:/* wrappers around output plugin callbacks */
-: 57:static void output_plugin_error_callback(void *arg);
-: 58:static void startup_cb_wrapper(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
-: 59: bool is_init);
-: 60:static void shutdown_cb_wrapper(LogicalDecodingContext *ctx);
-: 61:static void begin_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn);
-: 62:static void commit_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 63: XLogRecPtr commit_lsn);
-: 64:static void change_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 65: Relation relation, ReorderBufferChange *change);
-: 66:static void truncate_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 67: int nrelations, Relation relations[], ReorderBufferChange *change);
-: 68:static void message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 69: XLogRecPtr message_lsn, bool transactional,
-: 70: const char *prefix, Size message_size, const char *message);
-: 71:
-: 72:static void LoadOutputPlugin(OutputPluginCallbacks *callbacks, char *plugin);
-: 73:
-: 74:/*
-: 75: * Make sure the current settings & environment are capable of doing logical
-: 76: * decoding.
-: 77: */
-: 78:void
function CheckLogicalDecodingRequirements called 286 returned 100% blocks executed 22%
286: 79:CheckLogicalDecodingRequirements(void)
-: 80:{
286: 81: CheckSlotRequirements();
call 0 returned 100%
-: 82:
-: 83: /*
-: 84: * NB: Adding a new requirement likely means that RestoreSlotFromDisk()
-: 85: * needs the same check.
-: 86: */
-: 87:
286: 88: if (wal_level < WAL_LEVEL_LOGICAL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 89: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 90: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 91: errmsg("logical decoding requires wal_level >= logical")));
-: 92:
286: 93: if (MyDatabaseId == InvalidOid)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 94: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 95: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 96: errmsg("logical decoding requires a database connection")));
-: 97:
-: 98: /* ----
-: 99: * TODO: We got to change that someday soon...
-: 100: *
-: 101: * There's basically three things missing to allow this:
-: 102: * 1) We need to be able to correctly and quickly identify the timeline a
-: 103: * LSN belongs to
-: 104: * 2) We need to force hot_standby_feedback to be enabled at all times so
-: 105: * the primary cannot remove rows we need.
-: 106: * 3) support dropping replication slots referring to a database, in
-: 107: * dbase_redo. There can't be any active ones due to HS recovery
-: 108: * conflicts, so that should be relatively easy.
-: 109: * ----
-: 110: */
286: 111: if (RecoveryInProgress())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 112: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 113: (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-: 114: errmsg("logical decoding cannot be used while in recovery")));
286: 115:}
-: 116:
-: 117:/*
-: 118: * Helper function for CreateInitDecodingContext() and
-: 119: * CreateDecodingContext() performing common tasks.
-: 120: */
-: 121:static LogicalDecodingContext *
function StartupDecodingContext called 278 returned 99% blocks executed 71%
278: 122:StartupDecodingContext(List *output_plugin_options,
-: 123: XLogRecPtr start_lsn,
-: 124: TransactionId xmin_horizon,
-: 125: bool need_full_snapshot,
-: 126: bool fast_forward,
-: 127: XLogPageReadCB read_page,
-: 128: LogicalOutputPluginWriterPrepareWrite prepare_write,
-: 129: LogicalOutputPluginWriterWrite do_write,
-: 130: LogicalOutputPluginWriterUpdateProgress update_progress)
-: 131:{
-: 132: ReplicationSlot *slot;
-: 133: MemoryContext context,
-: 134: old_context;
-: 135: LogicalDecodingContext *ctx;
-: 136:
-: 137: /* shorter lines... */
278: 138: slot = MyReplicationSlot;
-: 139:
278: 140: context = AllocSetContextCreate(CurrentMemoryContext,
call 0 returned 100%
-: 141: "Logical decoding context",
-: 142: ALLOCSET_DEFAULT_SIZES);
278: 143: old_context = MemoryContextSwitchTo(context);
call 0 returned 100%
278: 144: ctx = palloc0(sizeof(LogicalDecodingContext));
call 0 returned 100%
-: 145:
278: 146: ctx->context = context;
-: 147:
-: 148: /*
-: 149: * (re-)load output plugins, so we detect a bad (removed) output plugin
-: 150: * now.
-: 151: */
278: 152: if (!fast_forward)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
276: 153: LoadOutputPlugin(&ctx->callbacks, NameStr(slot->data.plugin));
call 0 returned 99%
-: 154:
-: 155: /*
-: 156: * Now that the slot's xmin has been set, we can announce ourselves as a
-: 157: * logical decoding backend which doesn't need to be checked individually
-: 158: * when computing the xmin horizon because the xmin is enforced via
-: 159: * replication slots.
-: 160: *
-: 161: * We can only do so if we're outside of a transaction (i.e. the case when
-: 162: * streaming changes via walsender), otherwise an already setup
-: 163: * snapshot/xid would end up being ignored. That's not a particularly
-: 164: * bothersome restriction since the SQL interface can't be used for
-: 165: * streaming anyway.
-: 166: */
277: 167: if (!IsTransactionOrTransactionBlock())
call 0 returned 100%
branch 1 taken 16% (fallthrough)
branch 2 taken 84%
-: 168: {
45: 169: LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
call 0 returned 100%
45: 170: MyPgXact->vacuumFlags |= PROC_IN_LOGICAL_DECODING;
45: 171: LWLockRelease(ProcArrayLock);
call 0 returned 100%
-: 172: }
-: 173:
277: 174: ctx->slot = slot;
-: 175:
277: 176: ctx->reader = XLogReaderAllocate(wal_segment_size, NULL, read_page, ctx);
call 0 returned 100%
277: 177: if (!ctx->reader)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 178: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 179: (errcode(ERRCODE_OUT_OF_MEMORY),
-: 180: errmsg("out of memory")));
-: 181:
277: 182: ctx->reorder = ReorderBufferAllocate();
call 0 returned 100%
277: 183: ctx->snapshot_builder =
277: 184: AllocateSnapshotBuilder(ctx->reorder, xmin_horizon, start_lsn,
call 0 returned 100%
-: 185: need_full_snapshot);
-: 186:
277: 187: ctx->reorder->private_data = ctx;
-: 188:
-: 189: /* wrap output plugin callbacks, so we can add error context information */
277: 190: ctx->reorder->begin = begin_cb_wrapper;
277: 191: ctx->reorder->apply_change = change_cb_wrapper;
277: 192: ctx->reorder->apply_truncate = truncate_cb_wrapper;
277: 193: ctx->reorder->commit = commit_cb_wrapper;
277: 194: ctx->reorder->message = message_cb_wrapper;
-: 195:
277: 196: ctx->out = makeStringInfo();
call 0 returned 100%
277: 197: ctx->prepare_write = prepare_write;
277: 198: ctx->write = do_write;
277: 199: ctx->update_progress = update_progress;
-: 200:
277: 201: ctx->output_plugin_options = output_plugin_options;
-: 202:
277: 203: ctx->fast_forward = fast_forward;
-: 204:
277: 205: MemoryContextSwitchTo(old_context);
call 0 returned 100%
-: 206:
277: 207: return ctx;
-: 208:}
-: 209:
-: 210:/*
-: 211: * Create a new decoding context, for a new logical slot.
-: 212: *
-: 213: * plugin -- contains the name of the output plugin
-: 214: * output_plugin_options -- contains options passed to the output plugin
-: 215: * restart_lsn -- if given as invalid, it's this routine's responsibility to
-: 216: * mark WAL as reserved by setting a convenient restart_lsn for the slot.
-: 217: * Otherwise, we set for decoding to start from the given LSN without
-: 218: * marking WAL reserved beforehand. In that scenario, it's up to the
-: 219: * caller to guarantee that WAL remains available.
-: 220: * read_page, prepare_write, do_write, update_progress --
-: 221: * callbacks that perform the use-case dependent, actual, work.
-: 222: *
-: 223: * Needs to be called while in a memory context that's at least as long lived
-: 224: * as the decoding context because further memory contexts will be created
-: 225: * inside it.
-: 226: *
-: 227: * Returns an initialized decoding context after calling the output plugin's
-: 228: * startup function.
-: 229: */
-: 230:LogicalDecodingContext *
function CreateInitDecodingContext called 129 returned 98% blocks executed 64%
129: 231:CreateInitDecodingContext(char *plugin,
-: 232: List *output_plugin_options,
-: 233: bool need_full_snapshot,
-: 234: XLogRecPtr restart_lsn,
-: 235: XLogPageReadCB read_page,
-: 236: LogicalOutputPluginWriterPrepareWrite prepare_write,
-: 237: LogicalOutputPluginWriterWrite do_write,
-: 238: LogicalOutputPluginWriterUpdateProgress update_progress)
-: 239:{
129: 240: TransactionId xmin_horizon = InvalidTransactionId;
-: 241: ReplicationSlot *slot;
-: 242: LogicalDecodingContext *ctx;
-: 243: MemoryContext old_context;
-: 244:
-: 245: /* shorter lines... */
129: 246: slot = MyReplicationSlot;
-: 247:
-: 248: /* first some sanity checks that are unlikely to be violated */
129: 249: if (slot == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 250: elog(ERROR, "cannot perform logical decoding without an acquired slot");
call 0 never executed
call 1 never executed
call 2 never executed
-: 251:
129: 252: if (plugin == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 253: elog(ERROR, "cannot initialize logical decoding without a specified plugin");
call 0 never executed
call 1 never executed
call 2 never executed
-: 254:
-: 255: /* Make sure the passed slot is suitable. These are user facing errors. */
129: 256: if (SlotIsPhysical(slot))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 257: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 258: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 259: errmsg("cannot use physical replication slot for logical decoding")));
-: 260:
129: 261: if (slot->data.database != MyDatabaseId)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 262: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 263: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 264: errmsg("replication slot \"%s\" was not created in this database",
-: 265: NameStr(slot->data.name))));
-: 266:
240: 267: if (IsTransactionState() &&
call 0 returned 100%
branch 1 taken 86% (fallthrough)
branch 2 taken 14%
branch 3 taken 2% (fallthrough)
branch 4 taken 98%
111: 268: GetTopTransactionIdIfAny() != InvalidTransactionId)
call 0 returned 100%
2: 269: ereport(ERROR,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 0%
call 6 never executed
-: 270: (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
-: 271: errmsg("cannot create logical replication slot in transaction that has performed writes")));
-: 272:
-: 273: /* register output plugin name with slot */
127: 274: SpinLockAcquire(&slot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
127: 275: StrNCpy(NameStr(slot->data.plugin), plugin, NAMEDATALEN);
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
127: 276: SpinLockRelease(&slot->mutex);
call 0 returned 100%
-: 277:
127: 278: if (XLogRecPtrIsInvalid(restart_lsn))
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
121: 279: ReplicationSlotReserveWal();
call 0 returned 100%
-: 280: else
-: 281: {
6: 282: SpinLockAcquire(&slot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
6: 283: slot->data.restart_lsn = restart_lsn;
6: 284: SpinLockRelease(&slot->mutex);
call 0 returned 100%
-: 285: }
-: 286:
-: 287: /* ----
-: 288: * This is a bit tricky: We need to determine a safe xmin horizon to start
-: 289: * decoding from, to avoid starting from a running xacts record referring
-: 290: * to xids whose rows have been vacuumed or pruned
-: 291: * already. GetOldestSafeDecodingTransactionId() returns such a value, but
-: 292: * without further interlock its return value might immediately be out of
-: 293: * date.
-: 294: *
-: 295: * So we have to acquire the ProcArrayLock to prevent computation of new
-: 296: * xmin horizons by other backends, get the safe decoding xid, and inform
-: 297: * the slot machinery about the new limit. Once that's done the
-: 298: * ProcArrayLock can be released as the slot machinery now is
-: 299: * protecting against vacuum.
-: 300: *
-: 301: * Note that, temporarily, the data, not just the catalog, xmin has to be
-: 302: * reserved if a data snapshot is to be exported. Otherwise the initial
-: 303: * data snapshot created here is not guaranteed to be valid. After that
-: 304: * the data xmin doesn't need to be managed anymore and the global xmin
-: 305: * should be recomputed. As we are fine with losing the pegged data xmin
-: 306: * after crash - no chance a snapshot would get exported anymore - we can
-: 307: * get away with just setting the slot's
-: 308: * effective_xmin. ReplicationSlotRelease will reset it again.
-: 309: *
-: 310: * ----
-: 311: */
127: 312: LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
call 0 returned 100%
-: 313:
127: 314: xmin_horizon = GetOldestSafeDecodingTransactionId(!need_full_snapshot);
call 0 returned 100%
-: 315:
127: 316: SpinLockAcquire(&slot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
127: 317: slot->effective_catalog_xmin = xmin_horizon;
127: 318: slot->data.catalog_xmin = xmin_horizon;
127: 319: if (need_full_snapshot)
branch 0 taken 29% (fallthrough)
branch 1 taken 71%
37: 320: slot->effective_xmin = xmin_horizon;
127: 321: SpinLockRelease(&slot->mutex);
call 0 returned 100%
-: 322:
127: 323: ReplicationSlotsComputeRequiredXmin(true);
call 0 returned 100%
-: 324:
127: 325: LWLockRelease(ProcArrayLock);
call 0 returned 100%
-: 326:
127: 327: ReplicationSlotMarkDirty();
call 0 returned 100%
127: 328: ReplicationSlotSave();
call 0 returned 100%
-: 329:
127: 330: ctx = StartupDecodingContext(NIL, restart_lsn, xmin_horizon,
call 0 returned 99%
-: 331: need_full_snapshot, false,
-: 332: read_page, prepare_write, do_write,
-: 333: update_progress);
-: 334:
-: 335: /* call output plugin initialization callback */
126: 336: old_context = MemoryContextSwitchTo(ctx->context);
call 0 returned 100%
126: 337: if (ctx->callbacks.startup_cb != NULL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
126: 338: startup_cb_wrapper(ctx, &ctx->options, true);
call 0 returned 100%
126: 339: MemoryContextSwitchTo(old_context);
call 0 returned 100%
-: 340:
126: 341: ctx->reorder->output_rewrites = ctx->options.receive_rewrites;
-: 342:
126: 343: return ctx;
-: 344:}
-: 345:
-: 346:/*
-: 347: * Create a new decoding context, for a logical slot that has previously been
-: 348: * used already.
-: 349: *
-: 350: * start_lsn
-: 351: * The LSN at which to start decoding. If InvalidXLogRecPtr, restart
-: 352: * from the slot's confirmed_flush; otherwise, start from the specified
-: 353: * location (but move it forwards to confirmed_flush if it's older than
-: 354: * that, see below).
-: 355: *
-: 356: * output_plugin_options
-: 357: * options passed to the output plugin.
-: 358: *
-: 359: * fast_forward
-: 360: * bypass the generation of logical changes.
-: 361: *
-: 362: * read_page, prepare_write, do_write, update_progress
-: 363: * callbacks that have to be filled to perform the use-case dependent,
-: 364: * actual work.
-: 365: *
-: 366: * Needs to be called while in a memory context that's at least as long lived
-: 367: * as the decoding context because further memory contexts will be created
-: 368: * inside it.
-: 369: *
-: 370: * Returns an initialized decoding context after calling the output plugin's
-: 371: * startup function.
-: 372: */
-: 373:LogicalDecodingContext *
function CreateDecodingContext called 153 returned 97% blocks executed 74%
153: 374:CreateDecodingContext(XLogRecPtr start_lsn,
-: 375: List *output_plugin_options,
-: 376: bool fast_forward,
-: 377: XLogPageReadCB read_page,
-: 378: LogicalOutputPluginWriterPrepareWrite prepare_write,
-: 379: LogicalOutputPluginWriterWrite do_write,
-: 380: LogicalOutputPluginWriterUpdateProgress update_progress)
-: 381:{
-: 382: LogicalDecodingContext *ctx;
-: 383: ReplicationSlot *slot;
-: 384: MemoryContext old_context;
-: 385:
-: 386: /* shorter lines... */
153: 387: slot = MyReplicationSlot;
-: 388:
-: 389: /* first some sanity checks that are unlikely to be violated */
153: 390: if (slot == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 391: elog(ERROR, "cannot perform logical decoding without an acquired slot");
call 0 never executed
call 1 never executed
call 2 never executed
-: 392:
-: 393: /* make sure the passed slot is suitable, these are user facing errors */
153: 394: if (SlotIsPhysical(slot))
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
1: 395: ereport(ERROR,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 0%
call 6 never executed
-: 396: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 397: (errmsg("cannot use physical replication slot for logical decoding"))));
-: 398:
152: 399: if (slot->data.database != MyDatabaseId)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
1: 400: ereport(ERROR,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 0%
call 6 never executed
-: 401: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 402: (errmsg("replication slot \"%s\" was not created in this database",
-: 403: NameStr(slot->data.name)))));
-: 404:
151: 405: if (start_lsn == InvalidXLogRecPtr)
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
-: 406: {
-: 407: /* continue from last position */
143: 408: start_lsn = slot->data.confirmed_flush;
-: 409: }
8: 410: else if (start_lsn < slot->data.confirmed_flush)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 411: {
-: 412: /*
-: 413: * It might seem like we should error out in this case, but it's
-: 414: * pretty common for a client to acknowledge a LSN it doesn't have to
-: 415: * do anything for, and thus didn't store persistently, because the
-: 416: * xlog records didn't result in anything relevant for logical
-: 417: * decoding. Clients have to be able to do that to support synchronous
-: 418: * replication.
-: 419: */
#####: 420: elog(DEBUG1, "cannot stream from %X/%X, minimum is %X/%X, forwarding",
call 0 never executed
call 1 never executed
-: 421: (uint32) (start_lsn >> 32), (uint32) start_lsn,
-: 422: (uint32) (slot->data.confirmed_flush >> 32),
-: 423: (uint32) slot->data.confirmed_flush);
-: 424:
#####: 425: start_lsn = slot->data.confirmed_flush;
-: 426: }
-: 427:
151: 428: ctx = StartupDecodingContext(output_plugin_options,
call 0 returned 100%
-: 429: start_lsn, InvalidTransactionId, false,
-: 430: fast_forward, read_page, prepare_write,
-: 431: do_write, update_progress);
-: 432:
-: 433: /* call output plugin initialization callback */
151: 434: old_context = MemoryContextSwitchTo(ctx->context);
call 0 returned 100%
151: 435: if (ctx->callbacks.startup_cb != NULL)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
149: 436: startup_cb_wrapper(ctx, &ctx->options, false);
call 0 returned 98%
148: 437: MemoryContextSwitchTo(old_context);
call 0 returned 100%
-: 438:
148: 439: ctx->reorder->output_rewrites = ctx->options.receive_rewrites;
-: 440:
148: 441: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 100%
-: 442: (errmsg("starting logical decoding for slot \"%s\"",
-: 443: NameStr(slot->data.name)),
-: 444: errdetail("Streaming transactions committing after %X/%X, reading WAL from %X/%X.",
-: 445: (uint32) (slot->data.confirmed_flush >> 32),
-: 446: (uint32) slot->data.confirmed_flush,
-: 447: (uint32) (slot->data.restart_lsn >> 32),
-: 448: (uint32) slot->data.restart_lsn)));
-: 449:
148: 450: return ctx;
-: 451:}
-: 452:
-: 453:/*
-: 454: * Returns true if a consistent initial decoding snapshot has been built.
-: 455: */
-: 456:bool
function DecodingContextReady called 142 returned 100% blocks executed 100%
142: 457:DecodingContextReady(LogicalDecodingContext *ctx)
-: 458:{
142: 459: return SnapBuildCurrentState(ctx->snapshot_builder) == SNAPBUILD_CONSISTENT;
call 0 returned 100%
-: 460:}
-: 461:
-: 462:/*
-: 463: * Read from the decoding slot, until it is ready to start extracting changes.
-: 464: */
-: 465:void
function DecodingContextFindStartpoint called 126 returned 100% blocks executed 65%
126: 466:DecodingContextFindStartpoint(LogicalDecodingContext *ctx)
-: 467:{
-: 468: XLogRecPtr startptr;
126: 469: ReplicationSlot *slot = ctx->slot;
-: 470:
-: 471: /* Initialize from where to start reading WAL. */
126: 472: startptr = slot->data.restart_lsn;
-: 473:
126: 474: elog(DEBUG1, "searching for logical decoding starting point, starting at %X/%X",
call 0 returned 100%
call 1 returned 100%
-: 475: (uint32) (slot->data.restart_lsn >> 32),
-: 476: (uint32) slot->data.restart_lsn);
-: 477:
-: 478: /* Wait for a consistent starting point */
-: 479: for (;;)
-: 480: {
-: 481: XLogRecord *record;
142: 482: char *err = NULL;
-: 483:
-: 484: /* the read_page callback waits for new WAL */
142: 485: record = XLogReadRecord(ctx->reader, startptr, &err);
call 0 returned 100%
142: 486: if (err)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 487: elog(ERROR, "%s", err);
call 0 never executed
call 1 never executed
call 2 never executed
142: 488: if (!record)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 489: elog(ERROR, "no record found"); /* shouldn't happen */
call 0 never executed
call 1 never executed
call 2 never executed
-: 490:
142: 491: startptr = InvalidXLogRecPtr;
-: 492:
142: 493: LogicalDecodingProcessRecord(ctx, ctx->reader);
call 0 returned 100%
-: 494:
-: 495: /* only continue till we found a consistent spot */
142: 496: if (DecodingContextReady(ctx))
call 0 returned 100%
branch 1 taken 89% (fallthrough)
branch 2 taken 11%
126: 497: break;
-: 498:
16: 499: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
16: 500: }
-: 501:
126: 502: SpinLockAcquire(&slot->mutex);
call 0 returned 100%
branch 1 taken 0%
branch 2 taken 100%
call 3 never executed
126: 503: slot->data.confirmed_flush = ctx->reader->EndRecPtr;
126: 504: SpinLockRelease(&slot->mutex);
call 0 returned 100%
126: 505:}
-: 506:
-: 507:/*
-: 508: * Free a previously allocated decoding context, invoking the shutdown
-: 509: * callback if necessary.
-: 510: */
-: 511:void
function FreeDecodingContext called 250 returned 100% blocks executed 100%
250: 512:FreeDecodingContext(LogicalDecodingContext *ctx)
-: 513:{
250: 514: if (ctx->callbacks.shutdown_cb != NULL)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
248: 515: shutdown_cb_wrapper(ctx);
call 0 returned 100%
-: 516:
250: 517: ReorderBufferFree(ctx->reorder);
call 0 returned 100%
250: 518: FreeSnapshotBuilder(ctx->snapshot_builder);
call 0 returned 100%
250: 519: XLogReaderFree(ctx->reader);
call 0 returned 100%
250: 520: MemoryContextDelete(ctx->context);
call 0 returned 100%
250: 521:}
-: 522:
-: 523:/*
-: 524: * Prepare a write using the context's output routine.
-: 525: */
-: 526:void
function OutputPluginPrepareWrite called 146573 returned 100% blocks executed 50%
146573: 527:OutputPluginPrepareWrite(struct LogicalDecodingContext *ctx, bool last_write)
-: 528:{
146573: 529: if (!ctx->accept_writes)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 530: elog(ERROR, "writes are only accepted in commit, begin and change callbacks");
call 0 never executed
call 1 never executed
call 2 never executed
-: 531:
146573: 532: ctx->prepare_write(ctx, ctx->write_location, ctx->write_xid, last_write);
call 0 returned 100%
146573: 533: ctx->prepared_write = true;
146573: 534:}
-: 535:
-: 536:/*
-: 537: * Perform a write using the context's output routine.
-: 538: */
-: 539:void
function OutputPluginWrite called 146573 returned 100% blocks executed 50%
146573: 540:OutputPluginWrite(struct LogicalDecodingContext *ctx, bool last_write)
-: 541:{
146573: 542: if (!ctx->prepared_write)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 543: elog(ERROR, "OutputPluginPrepareWrite needs to be called before OutputPluginWrite");
call 0 never executed
call 1 never executed
call 2 never executed
-: 544:
146573: 545: ctx->write(ctx, ctx->write_location, ctx->write_xid, last_write);
call 0 returned 100%
146573: 546: ctx->prepared_write = false;
146573: 547:}
-: 548:
-: 549:/*
-: 550: * Update progress tracking (if supported).
-: 551: */
-: 552:void
function OutputPluginUpdateProgress called 117 returned 100% blocks executed 75%
117: 553:OutputPluginUpdateProgress(struct LogicalDecodingContext *ctx)
-: 554:{
117: 555: if (!ctx->update_progress)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
117: 556: return;
-: 557:
117: 558: ctx->update_progress(ctx, ctx->write_location, ctx->write_xid);
call 0 returned 100%
-: 559:}
-: 560:
-: 561:/*
-: 562: * Load the output plugin, lookup its output plugin init function, and check
-: 563: * that it provides the required callbacks.
-: 564: */
-: 565:static void
function LoadOutputPlugin called 276 returned 99% blocks executed 37%
276: 566:LoadOutputPlugin(OutputPluginCallbacks *callbacks, char *plugin)
-: 567:{
-: 568: LogicalOutputPluginInit plugin_init;
-: 569:
276: 570: plugin_init = (LogicalOutputPluginInit)
call 0 returned 99%
-: 571: load_external_function(plugin, "_PG_output_plugin_init", false, NULL);
-: 572:
275: 573: if (plugin_init == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 574: elog(ERROR, "output plugins have to declare the _PG_output_plugin_init symbol");
call 0 never executed
call 1 never executed
call 2 never executed
-: 575:
-: 576: /* ask the output plugin to fill the callback struct */
275: 577: plugin_init(callbacks);
call 0 returned 100%
-: 578:
275: 579: if (callbacks->begin_cb == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 580: elog(ERROR, "output plugins have to register a begin callback");
call 0 never executed
call 1 never executed
call 2 never executed
275: 581: if (callbacks->change_cb == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 582: elog(ERROR, "output plugins have to register a change callback");
call 0 never executed
call 1 never executed
call 2 never executed
275: 583: if (callbacks->commit_cb == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 584: elog(ERROR, "output plugins have to register a commit callback");
call 0 never executed
call 1 never executed
call 2 never executed
275: 585:}
-: 586:
-: 587:static void
function output_plugin_error_callback called 3 returned 100% blocks executed 67%
3: 588:output_plugin_error_callback(void *arg)
-: 589:{
3: 590: LogicalErrorCallbackState *state = (LogicalErrorCallbackState *) arg;
-: 591:
-: 592: /* not all callbacks have an associated LSN */
3: 593: if (state->report_location != InvalidXLogRecPtr)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 594: errcontext("slot \"%s\", output plugin \"%s\", in the %s callback, associated LSN %X/%X",
call 0 never executed
call 1 never executed
#####: 595: NameStr(state->ctx->slot->data.name),
#####: 596: NameStr(state->ctx->slot->data.plugin),
-: 597: state->callback_name,
#####: 598: (uint32) (state->report_location >> 32),
#####: 599: (uint32) state->report_location);
-: 600: else
6: 601: errcontext("slot \"%s\", output plugin \"%s\", in the %s callback",
call 0 returned 100%
call 1 returned 100%
3: 602: NameStr(state->ctx->slot->data.name),
3: 603: NameStr(state->ctx->slot->data.plugin),
-: 604: state->callback_name);
3: 605:}
-: 606:
-: 607:static void
function startup_cb_wrapper called 275 returned 99% blocks executed 75%
275: 608:startup_cb_wrapper(LogicalDecodingContext *ctx, OutputPluginOptions *opt, bool is_init)
-: 609:{
-: 610: LogicalErrorCallbackState state;
-: 611: ErrorContextCallback errcallback;
-: 612:
275: 613: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 614:
-: 615: /* Push callback + info on the error context stack */
275: 616: state.ctx = ctx;
275: 617: state.callback_name = "startup";
275: 618: state.report_location = InvalidXLogRecPtr;
275: 619: errcallback.callback = output_plugin_error_callback;
275: 620: errcallback.arg = (void *) &state;
275: 621: errcallback.previous = error_context_stack;
275: 622: error_context_stack = &errcallback;
-: 623:
-: 624: /* set output state */
275: 625: ctx->accept_writes = false;
-: 626:
-: 627: /* do the actual work: call callback */
275: 628: ctx->callbacks.startup_cb(ctx, opt, is_init);
call 0 returned 99%
-: 629:
-: 630: /* Pop the error context stack */
272: 631: error_context_stack = errcallback.previous;
272: 632:}
-: 633:
-: 634:static void
function shutdown_cb_wrapper called 248 returned 100% blocks executed 75%
248: 635:shutdown_cb_wrapper(LogicalDecodingContext *ctx)
-: 636:{
-: 637: LogicalErrorCallbackState state;
-: 638: ErrorContextCallback errcallback;
-: 639:
248: 640: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 641:
-: 642: /* Push callback + info on the error context stack */
248: 643: state.ctx = ctx;
248: 644: state.callback_name = "shutdown";
248: 645: state.report_location = InvalidXLogRecPtr;
248: 646: errcallback.callback = output_plugin_error_callback;
248: 647: errcallback.arg = (void *) &state;
248: 648: errcallback.previous = error_context_stack;
248: 649: error_context_stack = &errcallback;
-: 650:
-: 651: /* set output state */
248: 652: ctx->accept_writes = false;
-: 653:
-: 654: /* do the actual work: call callback */
248: 655: ctx->callbacks.shutdown_cb(ctx);
call 0 returned 100%
-: 656:
-: 657: /* Pop the error context stack */
248: 658: error_context_stack = errcallback.previous;
248: 659:}
-: 660:
-: 661:
-: 662:/*
-: 663: * Callbacks for ReorderBuffer which add in some more information and then call
-: 664: * output_plugin.h plugins.
-: 665: */
-: 666:static void
function begin_cb_wrapper called 445 returned 100% blocks executed 75%
445: 667:begin_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn)
-: 668:{
445: 669: LogicalDecodingContext *ctx = cache->private_data;
-: 670: LogicalErrorCallbackState state;
-: 671: ErrorContextCallback errcallback;
-: 672:
445: 673: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 674:
-: 675: /* Push callback + info on the error context stack */
445: 676: state.ctx = ctx;
445: 677: state.callback_name = "begin";
445: 678: state.report_location = txn->first_lsn;
445: 679: errcallback.callback = output_plugin_error_callback;
445: 680: errcallback.arg = (void *) &state;
445: 681: errcallback.previous = error_context_stack;
445: 682: error_context_stack = &errcallback;
-: 683:
-: 684: /* set output state */
445: 685: ctx->accept_writes = true;
445: 686: ctx->write_xid = txn->xid;
445: 687: ctx->write_location = txn->first_lsn;
-: 688:
-: 689: /* do the actual work: call callback */
445: 690: ctx->callbacks.begin_cb(ctx, txn);
call 0 returned 100%
-: 691:
-: 692: /* Pop the error context stack */
445: 693: error_context_stack = errcallback.previous;
445: 694:}
-: 695:
-: 696:static void
function commit_cb_wrapper called 445 returned 100% blocks executed 75%
445: 697:commit_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 698: XLogRecPtr commit_lsn)
-: 699:{
445: 700: LogicalDecodingContext *ctx = cache->private_data;
-: 701: LogicalErrorCallbackState state;
-: 702: ErrorContextCallback errcallback;
-: 703:
445: 704: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 705:
-: 706: /* Push callback + info on the error context stack */
445: 707: state.ctx = ctx;
445: 708: state.callback_name = "commit";
445: 709: state.report_location = txn->final_lsn; /* beginning of commit record */
445: 710: errcallback.callback = output_plugin_error_callback;
445: 711: errcallback.arg = (void *) &state;
445: 712: errcallback.previous = error_context_stack;
445: 713: error_context_stack = &errcallback;
-: 714:
-: 715: /* set output state */
445: 716: ctx->accept_writes = true;
445: 717: ctx->write_xid = txn->xid;
445: 718: ctx->write_location = txn->end_lsn; /* points to the end of the record */
-: 719:
-: 720: /* do the actual work: call callback */
445: 721: ctx->callbacks.commit_cb(ctx, txn, commit_lsn);
call 0 returned 100%
-: 722:
-: 723: /* Pop the error context stack */
445: 724: error_context_stack = errcallback.previous;
445: 725:}
-: 726:
-: 727:static void
function change_cb_wrapper called 146981 returned 100% blocks executed 75%
146981: 728:change_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 729: Relation relation, ReorderBufferChange *change)
-: 730:{
146981: 731: LogicalDecodingContext *ctx = cache->private_data;
-: 732: LogicalErrorCallbackState state;
-: 733: ErrorContextCallback errcallback;
-: 734:
146981: 735: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 736:
-: 737: /* Push callback + info on the error context stack */
146981: 738: state.ctx = ctx;
146981: 739: state.callback_name = "change";
146981: 740: state.report_location = change->lsn;
146981: 741: errcallback.callback = output_plugin_error_callback;
146981: 742: errcallback.arg = (void *) &state;
146981: 743: errcallback.previous = error_context_stack;
146981: 744: error_context_stack = &errcallback;
-: 745:
-: 746: /* set output state */
146981: 747: ctx->accept_writes = true;
146981: 748: ctx->write_xid = txn->xid;
-: 749:
-: 750: /*
-: 751: * report this change's lsn so replies from clients can give an up2date
-: 752: * answer. This won't ever be enough (and shouldn't be!) to confirm
-: 753: * receipt of this transaction, but it might allow another transaction's
-: 754: * commit to be confirmed with one message.
-: 755: */
146981: 756: ctx->write_location = change->lsn;
-: 757:
146981: 758: ctx->callbacks.change_cb(ctx, txn, relation, change);
call 0 returned 100%
-: 759:
-: 760: /* Pop the error context stack */
146981: 761: error_context_stack = errcallback.previous;
146981: 762:}
-: 763:
-: 764:static void
function truncate_cb_wrapper called 8 returned 100% blocks executed 71%
8: 765:truncate_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 766: int nrelations, Relation relations[], ReorderBufferChange *change)
-: 767:{
8: 768: LogicalDecodingContext *ctx = cache->private_data;
-: 769: LogicalErrorCallbackState state;
-: 770: ErrorContextCallback errcallback;
-: 771:
8: 772: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 773:
8: 774: if (!ctx->callbacks.truncate_cb)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
8: 775: return;
-: 776:
-: 777: /* Push callback + info on the error context stack */
8: 778: state.ctx = ctx;
8: 779: state.callback_name = "truncate";
8: 780: state.report_location = change->lsn;
8: 781: errcallback.callback = output_plugin_error_callback;
8: 782: errcallback.arg = (void *) &state;
8: 783: errcallback.previous = error_context_stack;
8: 784: error_context_stack = &errcallback;
-: 785:
-: 786: /* set output state */
8: 787: ctx->accept_writes = true;
8: 788: ctx->write_xid = txn->xid;
-: 789:
-: 790: /*
-: 791: * report this change's lsn so replies from clients can give an up2date
-: 792: * answer. This won't ever be enough (and shouldn't be!) to confirm
-: 793: * receipt of this transaction, but it might allow another transaction's
-: 794: * commit to be confirmed with one message.
-: 795: */
8: 796: ctx->write_location = change->lsn;
-: 797:
8: 798: ctx->callbacks.truncate_cb(ctx, txn, nrelations, relations, change);
call 0 returned 100%
-: 799:
-: 800: /* Pop the error context stack */
8: 801: error_context_stack = errcallback.previous;
-: 802:}
-: 803:
-: 804:bool
function filter_by_origin_cb_wrapper called 1182743 returned 100% blocks executed 80%
1182743: 805:filter_by_origin_cb_wrapper(LogicalDecodingContext *ctx, RepOriginId origin_id)
-: 806:{
-: 807: LogicalErrorCallbackState state;
-: 808: ErrorContextCallback errcallback;
-: 809: bool ret;
-: 810:
1182743: 811: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 812:
-: 813: /* Push callback + info on the error context stack */
1182743: 814: state.ctx = ctx;
1182743: 815: state.callback_name = "filter_by_origin";
1182743: 816: state.report_location = InvalidXLogRecPtr;
1182743: 817: errcallback.callback = output_plugin_error_callback;
1182743: 818: errcallback.arg = (void *) &state;
1182743: 819: errcallback.previous = error_context_stack;
1182743: 820: error_context_stack = &errcallback;
-: 821:
-: 822: /* set output state */
1182743: 823: ctx->accept_writes = false;
-: 824:
-: 825: /* do the actual work: call callback */
1182743: 826: ret = ctx->callbacks.filter_by_origin_cb(ctx, origin_id);
call 0 returned 100%
-: 827:
-: 828: /* Pop the error context stack */
1182743: 829: error_context_stack = errcallback.previous;
-: 830:
1182743: 831: return ret;
-: 832:}
-: 833:
-: 834:static void
function message_cb_wrapper called 8 returned 100% blocks executed 80%
8: 835:message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
-: 836: XLogRecPtr message_lsn, bool transactional,
-: 837: const char *prefix, Size message_size, const char *message)
-: 838:{
8: 839: LogicalDecodingContext *ctx = cache->private_data;
-: 840: LogicalErrorCallbackState state;
-: 841: ErrorContextCallback errcallback;
-: 842:
8: 843: Assert(!ctx->fast_forward);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 844:
8: 845: if (ctx->callbacks.message_cb == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
8: 846: return;
-: 847:
-: 848: /* Push callback + info on the error context stack */
8: 849: state.ctx = ctx;
8: 850: state.callback_name = "message";
8: 851: state.report_location = message_lsn;
8: 852: errcallback.callback = output_plugin_error_callback;
8: 853: errcallback.arg = (void *) &state;
8: 854: errcallback.previous = error_context_stack;
8: 855: error_context_stack = &errcallback;
-: 856:
-: 857: /* set output state */
8: 858: ctx->accept_writes = true;
8: 859: ctx->write_xid = txn != NULL ? txn->xid : InvalidTransactionId;
branch 0 taken 88% (fallthrough)
branch 1 taken 13%
8: 860: ctx->write_location = message_lsn;
-: 861:
-: 862: /* do the actual work: call callback */
8: 863: ctx->callbacks.message_cb(ctx, txn, message_lsn, transactional, prefix,
call 0 returned 100%
-: 864: message_size, message);
-: 865:
-: 866: /* Pop the error context stack */
8: 867: error_context_stack = errcallback.previous;
-: 868:}
-: 869:
-: 870:/*
-: 871: * Set the required catalog xmin horizon for historic snapshots in the current
-: 872: * replication slot.
-: 873: *
-: 874: * Note that in the most cases, we won't be able to immediately use the xmin
-: 875: * to increase the xmin horizon: we need to wait till the client has confirmed
-: 876: * receiving current_lsn with LogicalConfirmReceivedLocation().
-: 877: */
-: 878:void
function LogicalIncreaseXminForSlot called 81 returned 100% blocks executed 88%
81: 879:LogicalIncreaseXminForSlot(XLogRecPtr current_lsn, TransactionId xmin)
-: 880:{
81: 881: bool updated_xmin = false;
-: 882: ReplicationSlot *slot;
-: 883:
81: 884: slot = MyReplicationSlot;
-: 885:
81: 886: Assert(slot != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 887:
81: 888: SpinLockAcquire(&slot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 889:
-: 890: /*
-: 891: * don't overwrite if we already have a newer xmin. This can happen if we
-: 892: * restart decoding in a slot.
-: 893: */
81: 894: if (TransactionIdPrecedesOrEquals(xmin, slot->data.catalog_xmin))
call 0 returned 100%
branch 1 taken 32% (fallthrough)
branch 2 taken 68%
-: 895: {
-: 896: }
-: 897:
-: 898: /*
-: 899: * If the client has already confirmed up to this lsn, we directly can
-: 900: * mark this as accepted. This can happen if we restart decoding in a
-: 901: * slot.
-: 902: */
26: 903: else if (current_lsn <= slot->data.confirmed_flush)
branch 0 taken 19% (fallthrough)
branch 1 taken 81%
-: 904: {
5: 905: slot->candidate_catalog_xmin = xmin;
5: 906: slot->candidate_xmin_lsn = current_lsn;
-: 907:
-: 908: /* our candidate can directly be used */
5: 909: updated_xmin = true;
-: 910: }
-: 911:
-: 912: /*
-: 913: * Only increase if the previous values have been applied, otherwise we
-: 914: * might never end up updating if the receiver acks too slowly.
-: 915: */
21: 916: else if (slot->candidate_xmin_lsn == InvalidXLogRecPtr)
branch 0 taken 24% (fallthrough)
branch 1 taken 76%
-: 917: {
5: 918: slot->candidate_catalog_xmin = xmin;
5: 919: slot->candidate_xmin_lsn = current_lsn;
-: 920: }
81: 921: SpinLockRelease(&slot->mutex);
call 0 returned 100%
-: 922:
-: 923: /* candidate already valid with the current flush position, apply */
81: 924: if (updated_xmin)
branch 0 taken 6% (fallthrough)
branch 1 taken 94%
5: 925: LogicalConfirmReceivedLocation(slot->data.confirmed_flush);
call 0 returned 100%
81: 926:}
-: 927:
-: 928:/*
-: 929: * Mark the minimal LSN (restart_lsn) we need to read to replay all
-: 930: * transactions that have not yet committed at current_lsn.
-: 931: *
-: 932: * Just like LogicalIncreaseXminForSlot this only takes effect when the
-: 933: * client has confirmed to have received current_lsn.
-: 934: */
-: 935:void
function LogicalIncreaseRestartDecodingForSlot called 65 returned 100% blocks executed 83%
65: 936:LogicalIncreaseRestartDecodingForSlot(XLogRecPtr current_lsn, XLogRecPtr restart_lsn)
-: 937:{
65: 938: bool updated_lsn = false;
-: 939: ReplicationSlot *slot;
-: 940:
65: 941: slot = MyReplicationSlot;
-: 942:
65: 943: Assert(slot != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
65: 944: Assert(restart_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
65: 945: Assert(current_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 946:
65: 947: SpinLockAcquire(&slot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 948:
-: 949: /* don't overwrite if have a newer restart lsn */
65: 950: if (restart_lsn <= slot->data.restart_lsn)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 951: {
-: 952: }
-: 953:
-: 954: /*
-: 955: * We might have already flushed far enough to directly accept this lsn,
-: 956: * in this case there is no need to check for existing candidate LSNs
-: 957: */
65: 958: else if (current_lsn <= slot->data.confirmed_flush)
branch 0 taken 62% (fallthrough)
branch 1 taken 38%
-: 959: {
40: 960: slot->candidate_restart_valid = current_lsn;
40: 961: slot->candidate_restart_lsn = restart_lsn;
-: 962:
-: 963: /* our candidate can directly be used */
40: 964: updated_lsn = true;
-: 965: }
-: 966:
-: 967: /*
-: 968: * Only increase if the previous values have been applied, otherwise we
-: 969: * might never end up updating if the receiver acks too slowly. A missed
-: 970: * value here will just cause some extra effort after reconnecting.
-: 971: */
65: 972: if (slot->candidate_restart_valid == InvalidXLogRecPtr)
branch 0 taken 14% (fallthrough)
branch 1 taken 86%
-: 973: {
9: 974: slot->candidate_restart_valid = current_lsn;
9: 975: slot->candidate_restart_lsn = restart_lsn;
-: 976:
9: 977: elog(DEBUG1, "got new restart lsn %X/%X at %X/%X",
call 0 returned 100%
call 1 returned 100%
-: 978: (uint32) (restart_lsn >> 32), (uint32) restart_lsn,
-: 979: (uint32) (current_lsn >> 32), (uint32) current_lsn);
-: 980: }
-: 981: else
-: 982: {
56: 983: elog(DEBUG1, "failed to increase restart lsn: proposed %X/%X, after %X/%X, current candidate %X/%X, current after %X/%X, flushed up to %X/%X",
call 0 returned 100%
call 1 returned 100%
-: 984: (uint32) (restart_lsn >> 32), (uint32) restart_lsn,
-: 985: (uint32) (current_lsn >> 32), (uint32) current_lsn,
-: 986: (uint32) (slot->candidate_restart_lsn >> 32),
-: 987: (uint32) slot->candidate_restart_lsn,
-: 988: (uint32) (slot->candidate_restart_valid >> 32),
-: 989: (uint32) slot->candidate_restart_valid,
-: 990: (uint32) (slot->data.confirmed_flush >> 32),
-: 991: (uint32) slot->data.confirmed_flush
-: 992: );
-: 993: }
65: 994: SpinLockRelease(&slot->mutex);
call 0 returned 100%
-: 995:
-: 996: /* candidates are already valid with the current flush position, apply */
65: 997: if (updated_lsn)
branch 0 taken 62% (fallthrough)
branch 1 taken 38%
40: 998: LogicalConfirmReceivedLocation(slot->data.confirmed_flush);
call 0 returned 100%
65: 999:}
-: 1000:
-: 1001:/*
-: 1002: * Handle a consumer's confirmation having received all changes up to lsn.
-: 1003: */
-: 1004:void
function LogicalConfirmReceivedLocation called 1040 returned 100% blocks executed 88%
1040: 1005:LogicalConfirmReceivedLocation(XLogRecPtr lsn)
-: 1006:{
1040: 1007: Assert(lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1008:
-: 1009: /* Do an unlocked check for candidate_lsn first. */
2072: 1010: if (MyReplicationSlot->candidate_xmin_lsn != InvalidXLogRecPtr ||
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 4% (fallthrough)
branch 3 taken 96%
1032: 1011: MyReplicationSlot->candidate_restart_valid != InvalidXLogRecPtr)
54: 1012: {
54: 1013: bool updated_xmin = false;
54: 1014: bool updated_restart = false;
-: 1015:
54: 1016: SpinLockAcquire(&MyReplicationSlot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 1017:
54: 1018: MyReplicationSlot->data.confirmed_flush = lsn;
-: 1019:
-: 1020: /* if we're past the location required for bumping xmin, do so */
62: 1021: if (MyReplicationSlot->candidate_xmin_lsn != InvalidXLogRecPtr &&
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
8: 1022: MyReplicationSlot->candidate_xmin_lsn <= lsn)
-: 1023: {
-: 1024: /*
-: 1025: * We have to write the changed xmin to disk *before* we change
-: 1026: * the in-memory value, otherwise after a crash we wouldn't know
-: 1027: * that some catalog tuples might have been removed already.
-: 1028: *
-: 1029: * Ensure that by first writing to ->xmin and only update
-: 1030: * ->effective_xmin once the new state is synced to disk. After a
-: 1031: * crash ->effective_xmin is set to ->xmin.
-: 1032: */
16: 1033: if (TransactionIdIsValid(MyReplicationSlot->candidate_catalog_xmin) &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
8: 1034: MyReplicationSlot->data.catalog_xmin != MyReplicationSlot->candidate_catalog_xmin)
-: 1035: {
8: 1036: MyReplicationSlot->data.catalog_xmin = MyReplicationSlot->candidate_catalog_xmin;
8: 1037: MyReplicationSlot->candidate_catalog_xmin = InvalidTransactionId;
8: 1038: MyReplicationSlot->candidate_xmin_lsn = InvalidXLogRecPtr;
8: 1039: updated_xmin = true;
-: 1040: }
-: 1041: }
-: 1042:
103: 1043: if (MyReplicationSlot->candidate_restart_valid != InvalidXLogRecPtr &&
branch 0 taken 91% (fallthrough)
branch 1 taken 9%
branch 2 taken 98% (fallthrough)
branch 3 taken 2%
49: 1044: MyReplicationSlot->candidate_restart_valid <= lsn)
-: 1045: {
48: 1046: Assert(MyReplicationSlot->candidate_restart_lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1047:
48: 1048: MyReplicationSlot->data.restart_lsn = MyReplicationSlot->candidate_restart_lsn;
48: 1049: MyReplicationSlot->candidate_restart_lsn = InvalidXLogRecPtr;
48: 1050: MyReplicationSlot->candidate_restart_valid = InvalidXLogRecPtr;
48: 1051: updated_restart = true;
-: 1052: }
-: 1053:
54: 1054: SpinLockRelease(&MyReplicationSlot->mutex);
call 0 returned 100%
-: 1055:
-: 1056: /* first write new xmin to disk, so we know what's up after a crash */
54: 1057: if (updated_xmin || updated_restart)
branch 0 taken 85% (fallthrough)
branch 1 taken 15%
branch 2 taken 98% (fallthrough)
branch 3 taken 2%
-: 1058: {
53: 1059: ReplicationSlotMarkDirty();
call 0 returned 100%
53: 1060: ReplicationSlotSave();
call 0 returned 100%
53: 1061: elog(DEBUG1, "updated xmin: %u restart: %u", updated_xmin, updated_restart);
call 0 returned 100%
call 1 returned 100%
-: 1062: }
-: 1063:
-: 1064: /*
-: 1065: * Now the new xmin is safely on disk, we can let the global value
-: 1066: * advance. We do not take ProcArrayLock or similar since we only
-: 1067: * advance xmin here and there's not much harm done by a concurrent
-: 1068: * computation missing that.
-: 1069: */
54: 1070: if (updated_xmin)
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
-: 1071: {
8: 1072: SpinLockAcquire(&MyReplicationSlot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
8: 1073: MyReplicationSlot->effective_catalog_xmin = MyReplicationSlot->data.catalog_xmin;
8: 1074: SpinLockRelease(&MyReplicationSlot->mutex);
call 0 returned 100%
-: 1075:
8: 1076: ReplicationSlotsComputeRequiredXmin(false);
call 0 returned 100%
8: 1077: ReplicationSlotsComputeRequiredLSN();
call 0 returned 100%
-: 1078: }
-: 1079: }
-: 1080: else
-: 1081: {
986: 1082: SpinLockAcquire(&MyReplicationSlot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
986: 1083: MyReplicationSlot->data.confirmed_flush = lsn;
986: 1084: SpinLockRelease(&MyReplicationSlot->mutex);
call 0 returned 100%
-: 1085: }
1040: 1086:}
base_code_coverage/walreceiver.c.gcov 0000664 0001750 0000000 00000243014 13560201435 017446 0 ustar vignesh root -: 0:Source:walreceiver.c
-: 0:Graph:./walreceiver.gcno
-: 0:Data:./walreceiver.gcda
-: 0:Runs:6536
-: 0:Programs:1
-: 1:/*-------------------------------------------------------------------------
-: 2: *
-: 3: * walreceiver.c
-: 4: *
-: 5: * The WAL receiver process (walreceiver) is new as of Postgres 9.0. It
-: 6: * is the process in the standby server that takes charge of receiving
-: 7: * XLOG records from a primary server during streaming replication.
-: 8: *
-: 9: * When the startup process determines that it's time to start streaming,
-: 10: * it instructs postmaster to start walreceiver. Walreceiver first connects
-: 11: * to the primary server (it will be served by a walsender process
-: 12: * in the primary server), and then keeps receiving XLOG records and
-: 13: * writing them to the disk as long as the connection is alive. As XLOG
-: 14: * records are received and flushed to disk, it updates the
-: 15: * WalRcv->receivedUpto variable in shared memory, to inform the startup
-: 16: * process of how far it can proceed with XLOG replay.
-: 17: *
-: 18: * If the primary server ends streaming, but doesn't disconnect, walreceiver
-: 19: * goes into "waiting" mode, and waits for the startup process to give new
-: 20: * instructions. The startup process will treat that the same as
-: 21: * disconnection, and will rescan the archive/pg_wal directory. But when the
-: 22: * startup process wants to try streaming replication again, it will just
-: 23: * nudge the existing walreceiver process that's waiting, instead of launching
-: 24: * a new one.
-: 25: *
-: 26: * Normal termination is by SIGTERM, which instructs the walreceiver to
-: 27: * exit(0). Emergency termination is by SIGQUIT; like any postmaster child
-: 28: * process, the walreceiver will simply abort and exit on SIGQUIT. A close
-: 29: * of the connection and a FATAL error are treated not as a crash but as
-: 30: * normal operation.
-: 31: *
-: 32: * This file contains the server-facing parts of walreceiver. The libpq-
-: 33: * specific parts are in the libpqwalreceiver module. It's loaded
-: 34: * dynamically to avoid linking the server with libpq.
-: 35: *
-: 36: * Portions Copyright (c) 2010-2019, PostgreSQL Global Development Group
-: 37: *
-: 38: *
-: 39: * IDENTIFICATION
-: 40: * src/backend/replication/walreceiver.c
-: 41: *
-: 42: *-------------------------------------------------------------------------
-: 43: */
-: 44:#include "postgres.h"
-: 45:
-: 46:#include <signal.h>
-: 47:#include <unistd.h>
-: 48:
-: 49:#include "access/htup_details.h"
-: 50:#include "access/timeline.h"
-: 51:#include "access/transam.h"
-: 52:#include "access/xlog_internal.h"
-: 53:#include "catalog/pg_authid.h"
-: 54:#include "catalog/pg_type.h"
-: 55:#include "common/ip.h"
-: 56:#include "funcapi.h"
-: 57:#include "libpq/pqformat.h"
-: 58:#include "libpq/pqsignal.h"
-: 59:#include "miscadmin.h"
-: 60:#include "pgstat.h"
-: 61:#include "replication/walreceiver.h"
-: 62:#include "replication/walsender.h"
-: 63:#include "storage/ipc.h"
-: 64:#include "storage/pmsignal.h"
-: 65:#include "storage/procarray.h"
-: 66:#include "utils/builtins.h"
-: 67:#include "utils/guc.h"
-: 68:#include "utils/pg_lsn.h"
-: 69:#include "utils/ps_status.h"
-: 70:#include "utils/resowner.h"
-: 71:#include "utils/timestamp.h"
-: 72:
-: 73:
-: 74:/* GUC variables */
-: 75:int wal_receiver_status_interval;
-: 76:int wal_receiver_timeout;
-: 77:bool hot_standby_feedback;
-: 78:
-: 79:/* libpqwalreceiver connection */
-: 80:static WalReceiverConn *wrconn = NULL;
-: 81:WalReceiverFunctionsType *WalReceiverFunctions = NULL;
-: 82:
-: 83:#define NAPTIME_PER_CYCLE 100 /* max sleep time between cycles (100ms) */
-: 84:
-: 85:/*
-: 86: * These variables are used similarly to openLogFile/SegNo/Off,
-: 87: * but for walreceiver to write the XLOG. recvFileTLI is the TimeLineID
-: 88: * corresponding the filename of recvFile.
-: 89: */
-: 90:static int recvFile = -1;
-: 91:static TimeLineID recvFileTLI = 0;
-: 92:static XLogSegNo recvSegNo = 0;
-: 93:static uint32 recvOff = 0;
-: 94:
-: 95:/*
-: 96: * Flags set by interrupt handlers of walreceiver for later service in the
-: 97: * main loop.
-: 98: */
-: 99:static volatile sig_atomic_t got_SIGHUP = false;
-: 100:static volatile sig_atomic_t got_SIGTERM = false;
-: 101:
-: 102:/*
-: 103: * LogstreamResult indicates the byte positions that we have already
-: 104: * written/fsynced.
-: 105: */
-: 106:static struct
-: 107:{
-: 108: XLogRecPtr Write; /* last byte + 1 written out in the standby */
-: 109: XLogRecPtr Flush; /* last byte + 1 flushed in the standby */
-: 110:} LogstreamResult;
-: 111:
-: 112:static StringInfoData reply_message;
-: 113:static StringInfoData incoming_message;
-: 114:
-: 115:/* Prototypes for private functions */
-: 116:static void WalRcvFetchTimeLineHistoryFiles(TimeLineID first, TimeLineID last);
-: 117:static void WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI);
-: 118:static void WalRcvDie(int code, Datum arg);
-: 119:static void XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len);
-: 120:static void XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr);
-: 121:static void XLogWalRcvFlush(bool dying);
-: 122:static void XLogWalRcvSendReply(bool force, bool requestReply);
-: 123:static void XLogWalRcvSendHSFeedback(bool immed);
-: 124:static void ProcessWalSndrMessage(XLogRecPtr walEnd, TimestampTz sendTime);
-: 125:
-: 126:/* Signal handlers */
-: 127:static void WalRcvSigHupHandler(SIGNAL_ARGS);
-: 128:static void WalRcvSigUsr1Handler(SIGNAL_ARGS);
-: 129:static void WalRcvShutdownHandler(SIGNAL_ARGS);
-: 130:static void WalRcvQuickDieHandler(SIGNAL_ARGS);
-: 131:
-: 132:
-: 133:/*
-: 134: * Process any interrupts the walreceiver process may have received.
-: 135: * This should be called any time the process's latch has become set.
-: 136: *
-: 137: * Currently, only SIGTERM is of interest. We can't just exit(1) within the
-: 138: * SIGTERM signal handler, because the signal might arrive in the middle of
-: 139: * some critical operation, like while we're holding a spinlock. Instead, the
-: 140: * signal handler sets a flag variable as well as setting the process's latch.
-: 141: * We must check the flag (by calling ProcessWalRcvInterrupts) anytime the
-: 142: * latch has become set. Operations that could block for a long time, such as
-: 143: * reading from a remote server, must pay attention to the latch too; see
-: 144: * libpqrcv_PQgetResult for example.
-: 145: */
-: 146:void
function ProcessWalRcvInterrupts called 1310 returned 99% blocks executed 73%
1310: 147:ProcessWalRcvInterrupts(void)
-: 148:{
-: 149: /*
-: 150: * Although walreceiver interrupt handling doesn't use the same scheme as
-: 151: * regular backends, call CHECK_FOR_INTERRUPTS() to make sure we receive
-: 152: * any incoming signals on Win32.
-: 153: */
1310: 154: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 155:
1310: 156: if (got_SIGTERM)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 157: {
18: 158: ereport(FATAL,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 0%
call 6 never executed
-: 159: (errcode(ERRCODE_ADMIN_SHUTDOWN),
-: 160: errmsg("terminating walreceiver process due to administrator command")));
-: 161: }
1292: 162:}
-: 163:
-: 164:
-: 165:/* Main entry point for walreceiver process */
-: 166:void
function WalReceiverMain called 94 returned 0% blocks executed 70%
94: 167:WalReceiverMain(void)
-: 168:{
-: 169: char conninfo[MAXCONNINFO];
-: 170: char *tmp_conninfo;
-: 171: char slotname[NAMEDATALEN];
-: 172: XLogRecPtr startpoint;
-: 173: TimeLineID startpointTLI;
-: 174: TimeLineID primaryTLI;
-: 175: bool first_stream;
94: 176: WalRcvData *walrcv = WalRcv;
-: 177: TimestampTz last_recv_timestamp;
-: 178: TimestampTz now;
-: 179: bool ping_sent;
-: 180: char *err;
94: 181: char *sender_host = NULL;
94: 182: int sender_port = 0;
-: 183:
-: 184: /*
-: 185: * WalRcv should be set up already (if we are a backend, we inherit this
-: 186: * by fork() or EXEC_BACKEND mechanism from the postmaster).
-: 187: */
94: 188: Assert(walrcv != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 189:
94: 190: now = GetCurrentTimestamp();
call 0 returned 100%
-: 191:
-: 192: /*
-: 193: * Mark walreceiver as running in shared memory.
-: 194: *
-: 195: * Do this as early as possible, so that if we fail later on, we'll set
-: 196: * state to STOPPED. If we die before this, the startup process will keep
-: 197: * waiting for us to start up, until it times out.
-: 198: */
94: 199: SpinLockAcquire(&walrcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
94: 200: Assert(walrcv->pid == 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
94: 201: switch (walrcv->walRcvState)
branch 0 taken 0%
branch 1 taken 0%
branch 2 taken 100%
branch 3 taken 0%
-: 202: {
-: 203: case WALRCV_STOPPING:
-: 204: /* If we've already been requested to stop, don't start up. */
#####: 205: walrcv->walRcvState = WALRCV_STOPPED;
-: 206: /* fall through */
-: 207:
-: 208: case WALRCV_STOPPED:
#####: 209: SpinLockRelease(&walrcv->mutex);
call 0 never executed
#####: 210: proc_exit(1);
call 0 never executed
-: 211: break;
-: 212:
-: 213: case WALRCV_STARTING:
-: 214: /* The usual case */
94: 215: break;
-: 216:
-: 217: case WALRCV_WAITING:
-: 218: case WALRCV_STREAMING:
-: 219: case WALRCV_RESTARTING:
-: 220: default:
-: 221: /* Shouldn't happen */
#####: 222: SpinLockRelease(&walrcv->mutex);
call 0 never executed
#####: 223: elog(PANIC, "walreceiver still running according to shared memory state");
call 0 never executed
call 1 never executed
call 2 never executed
-: 224: }
-: 225: /* Advertise our PID so that the startup process can kill us */
94: 226: walrcv->pid = MyProcPid;
94: 227: walrcv->walRcvState = WALRCV_STREAMING;
-: 228:
-: 229: /* Fetch information required to start streaming */
94: 230: walrcv->ready_to_display = false;
94: 231: strlcpy(conninfo, (char *) walrcv->conninfo, MAXCONNINFO);
call 0 returned 100%
94: 232: strlcpy(slotname, (char *) walrcv->slotname, NAMEDATALEN);
call 0 returned 100%
94: 233: startpoint = walrcv->receiveStart;
94: 234: startpointTLI = walrcv->receiveStartTLI;
-: 235:
-: 236: /* Initialise to a sanish value */
94: 237: walrcv->lastMsgSendTime =
94: 238: walrcv->lastMsgReceiptTime = walrcv->latestWalEndTime = now;
-: 239:
-: 240: /* Report the latch to use to awaken this process */
94: 241: walrcv->latch = &MyProc->procLatch;
-: 242:
94: 243: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
-: 244:
-: 245: /* Arrange to clean up at walreceiver exit */
94: 246: on_shmem_exit(WalRcvDie, 0);
call 0 returned 100%
-: 247:
-: 248: /* Properly accept or ignore signals the postmaster might send us */
94: 249: pqsignal(SIGHUP, WalRcvSigHupHandler); /* set flag to read config file */
call 0 returned 100%
94: 250: pqsignal(SIGINT, SIG_IGN);
call 0 returned 100%
94: 251: pqsignal(SIGTERM, WalRcvShutdownHandler); /* request shutdown */
call 0 returned 100%
94: 252: pqsignal(SIGQUIT, WalRcvQuickDieHandler); /* hard crash time */
call 0 returned 100%
94: 253: pqsignal(SIGALRM, SIG_IGN);
call 0 returned 100%
94: 254: pqsignal(SIGPIPE, SIG_IGN);
call 0 returned 100%
94: 255: pqsignal(SIGUSR1, WalRcvSigUsr1Handler);
call 0 returned 100%
94: 256: pqsignal(SIGUSR2, SIG_IGN);
call 0 returned 100%
-: 257:
-: 258: /* Reset some signals that are accepted by postmaster but not here */
94: 259: pqsignal(SIGCHLD, SIG_DFL);
call 0 returned 100%
-: 260:
-: 261: /* We allow SIGQUIT (quickdie) at all times */
94: 262: sigdelset(&BlockSig, SIGQUIT);
call 0 returned 100%
-: 263:
-: 264: /* Load the libpq-specific functions */
94: 265: load_file("libpqwalreceiver", false);
call 0 returned 100%
94: 266: if (WalReceiverFunctions == NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 267: elog(ERROR, "libpqwalreceiver didn't initialize correctly");
call 0 never executed
call 1 never executed
call 2 never executed
-: 268:
-: 269: /* Unblock signals (they were blocked when the postmaster forked us) */
94: 270: PG_SETMASK(&UnBlockSig);
call 0 returned 100%
-: 271:
-: 272: /* Establish the connection to the primary for XLOG streaming */
94: 273: wrconn = walrcv_connect(conninfo, false, cluster_name[0] ? cluster_name : "walreceiver", &err);
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
call 2 returned 100%
94: 274: if (!wrconn)
branch 0 taken 47% (fallthrough)
branch 1 taken 53%
44: 275: ereport(ERROR,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 0%
call 5 never executed
-: 276: (errmsg("could not connect to the primary server: %s", err)));
-: 277:
-: 278: /*
-: 279: * Save user-visible connection string. This clobbers the original
-: 280: * conninfo, for security. Also save host and port of the sender server
-: 281: * this walreceiver is connected to.
-: 282: */
50: 283: tmp_conninfo = walrcv_get_conninfo(wrconn);
call 0 returned 100%
50: 284: walrcv_get_senderinfo(wrconn, &sender_host, &sender_port);
call 0 returned 100%
50: 285: SpinLockAcquire(&walrcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
50: 286: memset(walrcv->conninfo, 0, MAXCONNINFO);
50: 287: if (tmp_conninfo)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
50: 288: strlcpy((char *) walrcv->conninfo, tmp_conninfo, MAXCONNINFO);
call 0 returned 100%
-: 289:
50: 290: memset(walrcv->sender_host, 0, NI_MAXHOST);
50: 291: if (sender_host)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
50: 292: strlcpy((char *) walrcv->sender_host, sender_host, NI_MAXHOST);
call 0 returned 100%
-: 293:
50: 294: walrcv->sender_port = sender_port;
50: 295: walrcv->ready_to_display = true;
50: 296: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
-: 297:
50: 298: if (tmp_conninfo)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
50: 299: pfree(tmp_conninfo);
call 0 returned 100%
-: 300:
50: 301: if (sender_host)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
50: 302: pfree(sender_host);
call 0 returned 100%
-: 303:
50: 304: first_stream = true;
-: 305: for (;;)
-: 306: {
-: 307: char *primary_sysid;
-: 308: char standby_sysid[32];
-: 309: WalRcvStreamOptions options;
-: 310:
-: 311: /*
-: 312: * Check that we're connected to a valid server using the
-: 313: * IDENTIFY_SYSTEM replication command.
-: 314: */
55: 315: primary_sysid = walrcv_identify_system(wrconn, &primaryTLI);
call 0 returned 100%
-: 316:
55: 317: snprintf(standby_sysid, sizeof(standby_sysid), UINT64_FORMAT,
call 0 returned 100%
call 1 returned 100%
-: 318: GetSystemIdentifier());
55: 319: if (strcmp(primary_sysid, standby_sysid) != 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 320: {
#####: 321: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 322: (errmsg("database system identifier differs between the primary and standby"),
-: 323: errdetail("The primary's identifier is %s, the standby's identifier is %s.",
-: 324: primary_sysid, standby_sysid)));
-: 325: }
-: 326:
-: 327: /*
-: 328: * Confirm that the current timeline of the primary is the same or
-: 329: * ahead of ours.
-: 330: */
55: 331: if (primaryTLI < startpointTLI)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 332: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 333: (errmsg("highest timeline %u of the primary is behind recovery timeline %u",
-: 334: primaryTLI, startpointTLI)));
-: 335:
-: 336: /*
-: 337: * Get any missing history files. We do this always, even when we're
-: 338: * not interested in that timeline, so that if we're promoted to
-: 339: * become the master later on, we don't select the same timeline that
-: 340: * was already used in the current master. This isn't bullet-proof -
-: 341: * you'll need some external software to manage your cluster if you
-: 342: * need to ensure that a unique timeline id is chosen in every case,
-: 343: * but let's avoid the confusion of timeline id collisions where we
-: 344: * can.
-: 345: */
55: 346: WalRcvFetchTimeLineHistoryFiles(startpointTLI, primaryTLI);
call 0 returned 100%
-: 347:
-: 348: /*
-: 349: * Start streaming.
-: 350: *
-: 351: * We'll try to start at the requested starting point and timeline,
-: 352: * even if it's different from the server's latest timeline. In case
-: 353: * we've already reached the end of the old timeline, the server will
-: 354: * finish the streaming immediately, and we will go back to await
-: 355: * orders from the startup process. If recovery_target_timeline is
-: 356: * 'latest', the startup process will scan pg_wal and find the new
-: 357: * history file, bump recovery target timeline, and ask us to restart
-: 358: * on the new timeline.
-: 359: */
55: 360: options.logical = false;
55: 361: options.startpoint = startpoint;
55: 362: options.slotname = slotname[0] != '\0' ? slotname : NULL;
branch 0 taken 5% (fallthrough)
branch 1 taken 95%
55: 363: options.proto.physical.startpointTLI = startpointTLI;
55: 364: ThisTimeLineID = startpointTLI;
55: 365: if (walrcv_startstreaming(wrconn, &options))
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
-: 366: {
55: 367: if (first_stream)
branch 0 taken 91% (fallthrough)
branch 1 taken 9%
50: 368: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 369: (errmsg("started streaming WAL from primary at %X/%X on timeline %u",
-: 370: (uint32) (startpoint >> 32), (uint32) startpoint,
-: 371: startpointTLI)));
-: 372: else
5: 373: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 374: (errmsg("restarted WAL streaming at %X/%X on timeline %u",
-: 375: (uint32) (startpoint >> 32), (uint32) startpoint,
-: 376: startpointTLI)));
55: 377: first_stream = false;
-: 378:
-: 379: /* Initialize LogstreamResult and buffers for processing messages */
55: 380: LogstreamResult.Write = LogstreamResult.Flush = GetXLogReplayRecPtr(NULL);
call 0 returned 100%
55: 381: initStringInfo(&reply_message);
call 0 returned 100%
55: 382: initStringInfo(&incoming_message);
call 0 returned 100%
-: 383:
-: 384: /* Initialize the last recv timestamp */
55: 385: last_recv_timestamp = GetCurrentTimestamp();
call 0 returned 100%
55: 386: ping_sent = false;
-: 387:
-: 388: /* Loop until end-of-streaming or error */
-: 389: for (;;)
-: 390: {
-: 391: char *buf;
-: 392: int len;
851: 393: bool endofwal = false;
851: 394: pgsocket wait_fd = PGINVALID_SOCKET;
-: 395: int rc;
-: 396:
-: 397: /*
-: 398: * Exit walreceiver if we're not in recovery. This should not
-: 399: * happen, but cross-check the status here.
-: 400: */
851: 401: if (!RecoveryInProgress())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 402: ereport(FATAL,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 403: (errmsg("cannot continue WAL streaming, recovery has already ended")));
-: 404:
-: 405: /* Process any requests or signals received recently */
851: 406: ProcessWalRcvInterrupts();
call 0 returned 99%
-: 407:
850: 408: if (got_SIGHUP)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 409: {
5: 410: got_SIGHUP = false;
5: 411: ProcessConfigFile(PGC_SIGHUP);
call 0 returned 100%
5: 412: XLogWalRcvSendHSFeedback(true);
call 0 returned 100%
-: 413: }
-: 414:
-: 415: /* See if we can read data immediately */
850: 416: len = walrcv_receive(wrconn, &buf, &wait_fd);
call 0 returned 98%
831: 417: if (len != 0)
branch 0 taken 28% (fallthrough)
branch 1 taken 72%
-: 418: {
-: 419: /*
-: 420: * Process the received data, and any subsequent data we
-: 421: * can read without blocking.
-: 422: */
-: 423: for (;;)
-: 424: {
545: 425: if (len > 0)
branch 0 taken 57% (fallthrough)
branch 1 taken 43%
-: 426: {
-: 427: /*
-: 428: * Something was received from master, so reset
-: 429: * timeout
-: 430: */
311: 431: last_recv_timestamp = GetCurrentTimestamp();
call 0 returned 100%
311: 432: ping_sent = false;
311: 433: XLogWalRcvProcessMsg(buf[0], &buf[1], len - 1);
call 0 returned 100%
-: 434: }
234: 435: else if (len == 0)
branch 0 taken 92% (fallthrough)
branch 1 taken 8%
216: 436: break;
18: 437: else if (len < 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 438: {
18: 439: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 100%
-: 440: (errmsg("replication terminated by primary server"),
-: 441: errdetail("End of WAL reached on timeline %u at %X/%X.",
-: 442: startpointTLI,
-: 443: (uint32) (LogstreamResult.Write >> 32), (uint32) LogstreamResult.Write)));
18: 444: endofwal = true;
18: 445: break;
-: 446: }
311: 447: len = walrcv_receive(wrconn, &buf, &wait_fd);
call 0 returned 100%
311: 448: }
-: 449:
-: 450: /* Let the master know that we received some data. */
234: 451: XLogWalRcvSendReply(false, false);
call 0 returned 100%
-: 452:
-: 453: /*
-: 454: * If we've written some records, flush them to disk and
-: 455: * let the startup process and primary server know about
-: 456: * them.
-: 457: */
234: 458: XLogWalRcvFlush(false);
call 0 returned 100%
-: 459: }
-: 460:
-: 461: /* Check if we need to exit the streaming loop. */
831: 462: if (endofwal)
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
18: 463: break;
-: 464:
-: 465: /*
-: 466: * Ideally we would reuse a WaitEventSet object repeatedly
-: 467: * here to avoid the overheads of WaitLatchOrSocket on epoll
-: 468: * systems, but we can't be sure that libpq (or any other
-: 469: * walreceiver implementation) has the same socket (even if
-: 470: * the fd is the same number, it may have been closed and
-: 471: * reopened since the last time). In future, if there is a
-: 472: * function for removing sockets from WaitEventSet, then we
-: 473: * could add and remove just the socket each time, potentially
-: 474: * avoiding some system calls.
-: 475: */
813: 476: Assert(wait_fd != PGINVALID_SOCKET);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
813: 477: rc = WaitLatchOrSocket(walrcv->latch,
call 0 returned 100%
-: 478: WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
-: 479: WL_TIMEOUT | WL_LATCH_SET,
-: 480: wait_fd,
-: 481: NAPTIME_PER_CYCLE,
-: 482: WAIT_EVENT_WAL_RECEIVER_MAIN);
813: 483: if (rc & WL_LATCH_SET)
branch 0 taken 24% (fallthrough)
branch 1 taken 76%
-: 484: {
192: 485: ResetLatch(walrcv->latch);
call 0 returned 100%
192: 486: ProcessWalRcvInterrupts();
call 0 returned 91%
-: 487:
175: 488: if (walrcv->force_reply)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 489: {
-: 490: /*
-: 491: * The recovery process has asked us to send apply
-: 492: * feedback now. Make sure the flag is really set to
-: 493: * false in shared memory before sending the reply, so
-: 494: * we don't miss a new request for a reply.
-: 495: */
175: 496: walrcv->force_reply = false;
175: 497: pg_memory_barrier();
call 0 returned 100%
175: 498: XLogWalRcvSendReply(true, false);
call 0 returned 100%
-: 499: }
-: 500: }
796: 501: if (rc & WL_TIMEOUT)
branch 0 taken 38% (fallthrough)
branch 1 taken 62%
-: 502: {
-: 503: /*
-: 504: * We didn't receive anything new. If we haven't heard
-: 505: * anything from the server for more than
-: 506: * wal_receiver_timeout / 2, ping the server. Also, if
-: 507: * it's been longer than wal_receiver_status_interval
-: 508: * since the last update we sent, send a status update to
-: 509: * the master anyway, to report any progress in applying
-: 510: * WAL.
-: 511: */
302: 512: bool requestReply = false;
-: 513:
-: 514: /*
-: 515: * Check if time since last receive from standby has
-: 516: * reached the configured limit.
-: 517: */
302: 518: if (wal_receiver_timeout > 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 519: {
302: 520: TimestampTz now = GetCurrentTimestamp();
call 0 returned 100%
-: 521: TimestampTz timeout;
-: 522:
302: 523: timeout =
302: 524: TimestampTzPlusMilliseconds(last_recv_timestamp,
-: 525: wal_receiver_timeout);
-: 526:
302: 527: if (now >= timeout)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 528: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 529: (errmsg("terminating walreceiver due to timeout")));
-: 530:
-: 531: /*
-: 532: * We didn't receive anything new, for half of
-: 533: * receiver replication timeout. Ping the server.
-: 534: */
302: 535: if (!ping_sent)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 536: {
302: 537: timeout = TimestampTzPlusMilliseconds(last_recv_timestamp,
-: 538: (wal_receiver_timeout / 2));
302: 539: if (now >= timeout)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 540: {
#####: 541: requestReply = true;
#####: 542: ping_sent = true;
-: 543: }
-: 544: }
-: 545: }
-: 546:
302: 547: XLogWalRcvSendReply(requestReply, requestReply);
call 0 returned 100%
302: 548: XLogWalRcvSendHSFeedback(false);
call 0 returned 100%
-: 549: }
796: 550: }
-: 551:
-: 552: /*
-: 553: * The backend finished streaming. Exit streaming COPY-mode from
-: 554: * our side, too.
-: 555: */
18: 556: walrcv_endstreaming(wrconn, &primaryTLI);
call 0 returned 28%
-: 557:
-: 558: /*
-: 559: * If the server had switched to a new timeline that we didn't
-: 560: * know about when we began streaming, fetch its timeline history
-: 561: * file now.
-: 562: */
5: 563: WalRcvFetchTimeLineHistoryFiles(startpointTLI, primaryTLI);
call 0 returned 100%
-: 564: }
-: 565: else
#####: 566: ereport(LOG,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 567: (errmsg("primary server contains no more WAL on requested timeline %u",
-: 568: startpointTLI)));
-: 569:
-: 570: /*
-: 571: * End of WAL reached on the requested timeline. Close the last
-: 572: * segment, and await for new orders from the startup process.
-: 573: */
5: 574: if (recvFile >= 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 575: {
-: 576: char xlogfname[MAXFNAMELEN];
-: 577:
5: 578: XLogWalRcvFlush(false);
call 0 returned 100%
5: 579: if (close(recvFile) != 0)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 580: ereport(PANIC,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 581: (errcode_for_file_access(),
-: 582: errmsg("could not close log segment %s: %m",
-: 583: XLogFileNameP(recvFileTLI, recvSegNo))));
-: 584:
-: 585: /*
-: 586: * Create .done file forcibly to prevent the streamed segment from
-: 587: * being archived later.
-: 588: */
5: 589: XLogFileName(xlogfname, recvFileTLI, recvSegNo, wal_segment_size);
call 0 returned 100%
5: 590: if (XLogArchiveMode != ARCHIVE_MODE_ALWAYS)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
5: 591: XLogArchiveForceDone(xlogfname);
call 0 returned 100%
-: 592: else
#####: 593: XLogArchiveNotify(xlogfname);
call 0 never executed
-: 594: }
5: 595: recvFile = -1;
-: 596:
5: 597: elog(DEBUG1, "walreceiver ended streaming and awaits new instructions");
call 0 returned 100%
call 1 returned 100%
5: 598: WalRcvWaitForStartPosition(&startpoint, &startpointTLI);
call 0 returned 100%
5: 599: }
-: 600: /* not reached */
-: 601:}
-: 602:
-: 603:/*
-: 604: * Wait for startup process to set receiveStart and receiveStartTLI.
-: 605: */
-: 606:static void
function WalRcvWaitForStartPosition called 5 returned 100% blocks executed 69%
5: 607:WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
-: 608:{
5: 609: WalRcvData *walrcv = WalRcv;
-: 610: int state;
-: 611:
5: 612: SpinLockAcquire(&walrcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
5: 613: state = walrcv->walRcvState;
5: 614: if (state != WALRCV_STREAMING)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 615: {
#####: 616: SpinLockRelease(&walrcv->mutex);
call 0 never executed
#####: 617: if (state == WALRCV_STOPPING)
branch 0 never executed
branch 1 never executed
#####: 618: proc_exit(0);
call 0 never executed
-: 619: else
#####: 620: elog(FATAL, "unexpected walreceiver state");
call 0 never executed
call 1 never executed
call 2 never executed
-: 621: }
5: 622: walrcv->walRcvState = WALRCV_WAITING;
5: 623: walrcv->receiveStart = InvalidXLogRecPtr;
5: 624: walrcv->receiveStartTLI = 0;
5: 625: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
-: 626:
5: 627: if (update_process_title)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
5: 628: set_ps_display("idle", false);
call 0 returned 100%
-: 629:
-: 630: /*
-: 631: * nudge startup process to notice that we've stopped streaming and are
-: 632: * now waiting for instructions.
-: 633: */
5: 634: WakeupRecovery();
call 0 returned 100%
-: 635: for (;;)
-: 636: {
10: 637: ResetLatch(walrcv->latch);
call 0 returned 100%
-: 638:
10: 639: ProcessWalRcvInterrupts();
call 0 returned 100%
-: 640:
10: 641: SpinLockAcquire(&walrcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
10: 642: Assert(walrcv->walRcvState == WALRCV_RESTARTING ||
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
branch 4 never executed
branch 5 never executed
call 6 never executed
-: 643: walrcv->walRcvState == WALRCV_WAITING ||
-: 644: walrcv->walRcvState == WALRCV_STOPPING);
10: 645: if (walrcv->walRcvState == WALRCV_RESTARTING)
branch 0 taken 50% (fallthrough)
branch 1 taken 50%
-: 646: {
-: 647: /* we don't expect primary_conninfo to change */
5: 648: *startpoint = walrcv->receiveStart;
5: 649: *startpointTLI = walrcv->receiveStartTLI;
5: 650: walrcv->walRcvState = WALRCV_STREAMING;
5: 651: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
5: 652: break;
-: 653: }
5: 654: if (walrcv->walRcvState == WALRCV_STOPPING)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 655: {
-: 656: /*
-: 657: * We should've received SIGTERM if the startup process wants us
-: 658: * to die, but might as well check it here too.
-: 659: */
#####: 660: SpinLockRelease(&walrcv->mutex);
call 0 never executed
#####: 661: exit(1);
call 0 never executed
-: 662: }
5: 663: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
-: 664:
5: 665: (void) WaitLatch(walrcv->latch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
call 0 returned 100%
-: 666: WAIT_EVENT_WAL_RECEIVER_WAIT_START);
5: 667: }
-: 668:
5: 669: if (update_process_title)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 670: {
-: 671: char activitymsg[50];
-: 672:
10: 673: snprintf(activitymsg, sizeof(activitymsg), "restarting at %X/%X",
call 0 returned 100%
5: 674: (uint32) (*startpoint >> 32),
5: 675: (uint32) *startpoint);
5: 676: set_ps_display(activitymsg, false);
call 0 returned 100%
-: 677: }
5: 678:}
-: 679:
-: 680:/*
-: 681: * Fetch any missing timeline history files between 'first' and 'last'
-: 682: * (inclusive) from the server.
-: 683: */
-: 684:static void
function WalRcvFetchTimeLineHistoryFiles called 60 returned 100% blocks executed 73%
60: 685:WalRcvFetchTimeLineHistoryFiles(TimeLineID first, TimeLineID last)
-: 686:{
-: 687: TimeLineID tli;
-: 688:
130: 689: for (tli = first; tli <= last; tli++)
branch 0 taken 54%
branch 1 taken 46% (fallthrough)
-: 690: {
-: 691: /* there's no history file for timeline 1 */
70: 692: if (tli != 1 && !existsTimeLineHistory(tli))
branch 0 taken 29% (fallthrough)
branch 1 taken 71%
call 2 returned 100%
branch 3 taken 25% (fallthrough)
branch 4 taken 75%
-: 693: {
-: 694: char *fname;
-: 695: char *content;
-: 696: int len;
-: 697: char expectedfname[MAXFNAMELEN];
-: 698:
5: 699: ereport(LOG,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
-: 700: (errmsg("fetching timeline history file for timeline %u from primary server",
-: 701: tli)));
-: 702:
5: 703: walrcv_readtimelinehistoryfile(wrconn, tli, &fname, &content, &len);
call 0 returned 100%
-: 704:
-: 705: /*
-: 706: * Check that the filename on the master matches what we
-: 707: * calculated ourselves. This is just a sanity check, it should
-: 708: * always match.
-: 709: */
5: 710: TLHistoryFileName(expectedfname, tli);
call 0 returned 100%
5: 711: if (strcmp(fname, expectedfname) != 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 712: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 713: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 714: errmsg_internal("primary reported unexpected file name for timeline history file of timeline %u",
-: 715: tli)));
-: 716:
-: 717: /*
-: 718: * Write the file to pg_wal.
-: 719: */
5: 720: writeTimeLineHistoryFile(tli, content, len);
call 0 returned 100%
-: 721:
5: 722: pfree(fname);
call 0 returned 100%
5: 723: pfree(content);
call 0 returned 100%
-: 724: }
-: 725: }
60: 726:}
-: 727:
-: 728:/*
-: 729: * Mark us as STOPPED in shared memory at exit.
-: 730: */
-: 731:static void
function WalRcvDie called 94 returned 100% blocks executed 83%
94: 732:WalRcvDie(int code, Datum arg)
-: 733:{
94: 734: WalRcvData *walrcv = WalRcv;
-: 735:
-: 736: /* Ensure that all WAL records received are flushed to disk */
94: 737: XLogWalRcvFlush(true);
call 0 returned 100%
-: 738:
-: 739: /* Mark ourselves inactive in shared memory */
94: 740: SpinLockAcquire(&walrcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
94: 741: Assert(walrcv->walRcvState == WALRCV_STREAMING ||
branch 0 taken 14% (fallthrough)
branch 1 taken 86%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 100% (fallthrough)
branch 5 taken 0%
branch 6 taken 100% (fallthrough)
branch 7 taken 0%
branch 8 taken 0% (fallthrough)
branch 9 taken 100%
call 10 never executed
-: 742: walrcv->walRcvState == WALRCV_RESTARTING ||
-: 743: walrcv->walRcvState == WALRCV_STARTING ||
-: 744: walrcv->walRcvState == WALRCV_WAITING ||
-: 745: walrcv->walRcvState == WALRCV_STOPPING);
94: 746: Assert(walrcv->pid == MyProcPid);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
94: 747: walrcv->walRcvState = WALRCV_STOPPED;
94: 748: walrcv->pid = 0;
94: 749: walrcv->ready_to_display = false;
94: 750: walrcv->latch = NULL;
94: 751: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
-: 752:
-: 753: /* Terminate the connection gracefully. */
94: 754: if (wrconn != NULL)
branch 0 taken 53% (fallthrough)
branch 1 taken 47%
50: 755: walrcv_disconnect(wrconn);
call 0 returned 100%
-: 756:
-: 757: /* Wake up the startup process to notice promptly that we're gone */
94: 758: WakeupRecovery();
call 0 returned 100%
94: 759:}
-: 760:
-: 761:/* SIGHUP: set flag to re-read config file at next convenient time */
-: 762:static void
function WalRcvSigHupHandler called 5 returned 100% blocks executed 100%
5: 763:WalRcvSigHupHandler(SIGNAL_ARGS)
-: 764:{
5: 765: got_SIGHUP = true;
5: 766:}
-: 767:
-: 768:
-: 769:/* SIGUSR1: used by latch mechanism */
-: 770:static void
function WalRcvSigUsr1Handler called 180 returned 100% blocks executed 100%
180: 771:WalRcvSigUsr1Handler(SIGNAL_ARGS)
-: 772:{
180: 773: int save_errno = errno;
call 0 returned 100%
-: 774:
180: 775: latch_sigusr1_handler();
call 0 returned 100%
-: 776:
180: 777: errno = save_errno;
call 0 returned 100%
180: 778:}
-: 779:
-: 780:/* SIGTERM: set flag for ProcessWalRcvInterrupts */
-: 781:static void
function WalRcvShutdownHandler called 18 returned 100% blocks executed 100%
18: 782:WalRcvShutdownHandler(SIGNAL_ARGS)
-: 783:{
18: 784: int save_errno = errno;
call 0 returned 100%
-: 785:
18: 786: got_SIGTERM = true;
-: 787:
18: 788: if (WalRcv->latch)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
18: 789: SetLatch(WalRcv->latch);
call 0 returned 100%
-: 790:
18: 791: errno = save_errno;
call 0 returned 100%
18: 792:}
-: 793:
-: 794:/*
-: 795: * WalRcvQuickDieHandler() occurs when signalled SIGQUIT by the postmaster.
-: 796: *
-: 797: * Some backend has bought the farm, so we need to stop what we're doing and
-: 798: * exit.
-: 799: */
-: 800:static void
function WalRcvQuickDieHandler called 0 returned 0% blocks executed 0%
#####: 801:WalRcvQuickDieHandler(SIGNAL_ARGS)
-: 802:{
-: 803: /*
-: 804: * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
-: 805: * because shared memory may be corrupted, so we don't want to try to
-: 806: * clean up our transaction. Just nail the windows shut and get out of
-: 807: * town. The callbacks wouldn't be safe to run from a signal handler,
-: 808: * anyway.
-: 809: *
-: 810: * Note we use _exit(2) not _exit(0). This is to force the postmaster
-: 811: * into a system reset cycle if someone sends a manual SIGQUIT to a random
-: 812: * backend. This is necessary precisely because we don't clean up our
-: 813: * shared memory state. (The "dead man switch" mechanism in pmsignal.c
-: 814: * should ensure the postmaster sees this as a crash, too, but no harm in
-: 815: * being doubly sure.)
-: 816: */
#####: 817: _exit(2);
-: 818:}
-: 819:
-: 820:/*
-: 821: * Accept the message from XLOG stream, and process it.
-: 822: */
-: 823:static void
function XLogWalRcvProcessMsg called 311 returned 100% blocks executed 27%
311: 824:XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len)
-: 825:{
-: 826: int hdrlen;
-: 827: XLogRecPtr dataStart;
-: 828: XLogRecPtr walEnd;
-: 829: TimestampTz sendTime;
-: 830: bool replyRequested;
-: 831:
311: 832: resetStringInfo(&incoming_message);
call 0 returned 100%
-: 833:
311: 834: switch (type)
branch 0 taken 100%
branch 1 taken 0%
branch 2 taken 0%
-: 835: {
-: 836: case 'w': /* WAL records */
-: 837: {
-: 838: /* copy message to StringInfo */
311: 839: hdrlen = sizeof(int64) + sizeof(int64) + sizeof(int64);
311: 840: if (len < hdrlen)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 841: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 842: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 843: errmsg_internal("invalid WAL message received from primary")));
311: 844: appendBinaryStringInfo(&incoming_message, buf, hdrlen);
call 0 returned 100%
-: 845:
-: 846: /* read the fields */
311: 847: dataStart = pq_getmsgint64(&incoming_message);
call 0 returned 100%
311: 848: walEnd = pq_getmsgint64(&incoming_message);
call 0 returned 100%
311: 849: sendTime = pq_getmsgint64(&incoming_message);
call 0 returned 100%
311: 850: ProcessWalSndrMessage(walEnd, sendTime);
call 0 returned 100%
-: 851:
311: 852: buf += hdrlen;
311: 853: len -= hdrlen;
311: 854: XLogWalRcvWrite(buf, len, dataStart);
call 0 returned 100%
311: 855: break;
-: 856: }
-: 857: case 'k': /* Keepalive */
-: 858: {
-: 859: /* copy message to StringInfo */
#####: 860: hdrlen = sizeof(int64) + sizeof(int64) + sizeof(char);
#####: 861: if (len != hdrlen)
branch 0 never executed
branch 1 never executed
#####: 862: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 863: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 864: errmsg_internal("invalid keepalive message received from primary")));
#####: 865: appendBinaryStringInfo(&incoming_message, buf, hdrlen);
call 0 never executed
-: 866:
-: 867: /* read the fields */
#####: 868: walEnd = pq_getmsgint64(&incoming_message);
call 0 never executed
#####: 869: sendTime = pq_getmsgint64(&incoming_message);
call 0 never executed
#####: 870: replyRequested = pq_getmsgbyte(&incoming_message);
call 0 never executed
-: 871:
#####: 872: ProcessWalSndrMessage(walEnd, sendTime);
call 0 never executed
-: 873:
-: 874: /* If the primary requested a reply, send one immediately */
#####: 875: if (replyRequested)
branch 0 never executed
branch 1 never executed
#####: 876: XLogWalRcvSendReply(true, false);
call 0 never executed
#####: 877: break;
-: 878: }
-: 879: default:
#####: 880: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 881: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 882: errmsg_internal("invalid replication message type %d",
-: 883: type)));
-: 884: }
311: 885:}
-: 886:
-: 887:/*
-: 888: * Write XLOG data to disk.
-: 889: */
-: 890:static void
function XLogWalRcvWrite called 311 returned 100% blocks executed 27%
311: 891:XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr)
-: 892:{
-: 893: int startoff;
-: 894: int byteswritten;
-: 895:
933: 896: while (nbytes > 0)
branch 0 taken 50%
branch 1 taken 50% (fallthrough)
-: 897: {
-: 898: int segbytes;
-: 899:
311: 900: if (recvFile < 0 || !XLByteInSeg(recptr, recvSegNo, wal_segment_size))
branch 0 taken 86% (fallthrough)
branch 1 taken 14%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
-: 901: {
-: 902: bool use_existent;
-: 903:
-: 904: /*
-: 905: * fsync() and close current file before we switch to next one. We
-: 906: * would otherwise have to reopen this file to fsync it later
-: 907: */
42: 908: if (recvFile >= 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 909: {
-: 910: char xlogfname[MAXFNAMELEN];
-: 911:
#####: 912: XLogWalRcvFlush(false);
call 0 never executed
-: 913:
-: 914: /*
-: 915: * XLOG segment files will be re-read by recovery in startup
-: 916: * process soon, so we don't advise the OS to release cache
-: 917: * pages associated with the file like XLogFileClose() does.
-: 918: */
#####: 919: if (close(recvFile) != 0)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 920: ereport(PANIC,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 921: (errcode_for_file_access(),
-: 922: errmsg("could not close log segment %s: %m",
-: 923: XLogFileNameP(recvFileTLI, recvSegNo))));
-: 924:
-: 925: /*
-: 926: * Create .done file forcibly to prevent the streamed segment
-: 927: * from being archived later.
-: 928: */
#####: 929: XLogFileName(xlogfname, recvFileTLI, recvSegNo, wal_segment_size);
call 0 never executed
#####: 930: if (XLogArchiveMode != ARCHIVE_MODE_ALWAYS)
branch 0 never executed
branch 1 never executed
#####: 931: XLogArchiveForceDone(xlogfname);
call 0 never executed
-: 932: else
#####: 933: XLogArchiveNotify(xlogfname);
call 0 never executed
-: 934: }
42: 935: recvFile = -1;
-: 936:
-: 937: /* Create/use new log file */
42: 938: XLByteToSeg(recptr, recvSegNo, wal_segment_size);
42: 939: use_existent = true;
42: 940: recvFile = XLogFileInit(recvSegNo, &use_existent, true);
call 0 returned 100%
42: 941: recvFileTLI = ThisTimeLineID;
42: 942: recvOff = 0;
-: 943: }
-: 944:
-: 945: /* Calculate the start offset of the received logs */
311: 946: startoff = XLogSegmentOffset(recptr, wal_segment_size);
-: 947:
311: 948: if (startoff + nbytes > wal_segment_size)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 949: segbytes = wal_segment_size - startoff;
-: 950: else
311: 951: segbytes = nbytes;
-: 952:
-: 953: /* Need to seek in the file? */
311: 954: if (recvOff != startoff)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 955: {
#####: 956: if (lseek(recvFile, (off_t) startoff, SEEK_SET) < 0)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 957: ereport(PANIC,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 958: (errcode_for_file_access(),
-: 959: errmsg("could not seek in log segment %s to offset %u: %m",
-: 960: XLogFileNameP(recvFileTLI, recvSegNo),
-: 961: startoff)));
#####: 962: recvOff = startoff;
-: 963: }
-: 964:
-: 965: /* OK to write the logs */
311: 966: errno = 0;
call 0 returned 100%
-: 967:
311: 968: byteswritten = write(recvFile, buf, segbytes);
call 0 returned 100%
311: 969: if (byteswritten <= 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 970: {
-: 971: /* if write didn't set errno, assume no disk space */
#####: 972: if (errno == 0)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 973: errno = ENOSPC;
call 0 never executed
#####: 974: ereport(PANIC,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 975: (errcode_for_file_access(),
-: 976: errmsg("could not write to log segment %s "
-: 977: "at offset %u, length %lu: %m",
-: 978: XLogFileNameP(recvFileTLI, recvSegNo),
-: 979: recvOff, (unsigned long) segbytes)));
-: 980: }
-: 981:
-: 982: /* Update state for write */
311: 983: recptr += byteswritten;
-: 984:
311: 985: recvOff += byteswritten;
311: 986: nbytes -= byteswritten;
311: 987: buf += byteswritten;
-: 988:
311: 989: LogstreamResult.Write = recptr;
-: 990: }
311: 991:}
-: 992:
-: 993:/*
-: 994: * Flush the log to disk.
-: 995: *
-: 996: * If we're in the midst of dying, it's unwise to do anything that might throw
-: 997: * an error, so we skip sending a reply in that case.
-: 998: */
-: 999:static void
function XLogWalRcvFlush called 333 returned 100% blocks executed 95%
333: 1000:XLogWalRcvFlush(bool dying)
-: 1001:{
333: 1002: if (LogstreamResult.Flush < LogstreamResult.Write)
branch 0 taken 64% (fallthrough)
branch 1 taken 36%
-: 1003: {
213: 1004: WalRcvData *walrcv = WalRcv;
-: 1005:
213: 1006: issue_xlog_fsync(recvFile, recvSegNo);
call 0 returned 100%
-: 1007:
213: 1008: LogstreamResult.Flush = LogstreamResult.Write;
-: 1009:
-: 1010: /* Update shared-memory status */
213: 1011: SpinLockAcquire(&walrcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
213: 1012: if (walrcv->receivedUpto < LogstreamResult.Flush)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1013: {
213: 1014: walrcv->latestChunkStart = walrcv->receivedUpto;
213: 1015: walrcv->receivedUpto = LogstreamResult.Flush;
213: 1016: walrcv->receivedTLI = ThisTimeLineID;
-: 1017: }
213: 1018: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
-: 1019:
-: 1020: /* Signal the startup process and walsender that new WAL has arrived */
213: 1021: WakeupRecovery();
call 0 returned 100%
213: 1022: if (AllowCascadeReplication())
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
213: 1023: WalSndWakeup();
call 0 returned 100%
-: 1024:
-: 1025: /* Report XLOG streaming progress in PS display */
213: 1026: if (update_process_title)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1027: {
-: 1028: char activitymsg[50];
-: 1029:
426: 1030: snprintf(activitymsg, sizeof(activitymsg), "streaming %X/%X",
call 0 returned 100%
213: 1031: (uint32) (LogstreamResult.Write >> 32),
213: 1032: (uint32) LogstreamResult.Write);
213: 1033: set_ps_display(activitymsg, false);
call 0 returned 100%
-: 1034: }
-: 1035:
-: 1036: /* Also let the master know that we made some progress */
213: 1037: if (!dying)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1038: {
213: 1039: XLogWalRcvSendReply(false, false);
call 0 returned 100%
213: 1040: XLogWalRcvSendHSFeedback(false);
call 0 returned 100%
-: 1041: }
-: 1042: }
333: 1043:}
-: 1044:
-: 1045:/*
-: 1046: * Send reply message to primary, indicating our current WAL locations, oldest
-: 1047: * xmin and the current time.
-: 1048: *
-: 1049: * If 'force' is not set, the message is only sent if enough time has
-: 1050: * passed since last status update to reach wal_receiver_status_interval.
-: 1051: * If wal_receiver_status_interval is disabled altogether and 'force' is
-: 1052: * false, this is a no-op.
-: 1053: *
-: 1054: * If 'requestReply' is true, requests the server to reply immediately upon
-: 1055: * receiving this message. This is used for heartbeats, when approaching
-: 1056: * wal_receiver_timeout.
-: 1057: */
-: 1058:static void
function XLogWalRcvSendReply called 924 returned 100% blocks executed 92%
924: 1059:XLogWalRcvSendReply(bool force, bool requestReply)
-: 1060:{
-: 1061: static XLogRecPtr writePtr = 0;
-: 1062: static XLogRecPtr flushPtr = 0;
-: 1063: XLogRecPtr applyPtr;
-: 1064: static TimestampTz sendTime = 0;
-: 1065: TimestampTz now;
-: 1066:
-: 1067: /*
-: 1068: * If the user doesn't want status to be reported to the master, be sure
-: 1069: * to exit before doing anything at all.
-: 1070: */
924: 1071: if (!force && wal_receiver_status_interval <= 0)
branch 0 taken 81% (fallthrough)
branch 1 taken 19%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 1072: return;
-: 1073:
-: 1074: /* Get current timestamp. */
924: 1075: now = GetCurrentTimestamp();
call 0 returned 100%
-: 1076:
-: 1077: /*
-: 1078: * We can compare the write and flush positions to the last message we
-: 1079: * sent without taking any lock, but the apply position requires a spin
-: 1080: * lock, so we don't check that unless something else has changed or 10
-: 1081: * seconds have passed. This means that the apply WAL location will
-: 1082: * appear, from the master's point of view, to lag slightly, but since
-: 1083: * this is only for reporting purposes and only on idle systems, that's
-: 1084: * probably OK.
-: 1085: */
924: 1086: if (!force
branch 0 taken 81% (fallthrough)
branch 1 taken 19%
749: 1087: && writePtr == LogstreamResult.Write
branch 0 taken 68% (fallthrough)
branch 1 taken 32%
510: 1088: && flushPtr == LogstreamResult.Flush
branch 0 taken 58% (fallthrough)
branch 1 taken 42%
297: 1089: && !TimestampDifferenceExceeds(sendTime, now,
call 0 returned 100%
branch 1 taken 99% (fallthrough)
branch 2 taken 1%
-: 1090: wal_receiver_status_interval * 1000))
296: 1091: return;
628: 1092: sendTime = now;
-: 1093:
-: 1094: /* Construct a new message */
628: 1095: writePtr = LogstreamResult.Write;
628: 1096: flushPtr = LogstreamResult.Flush;
628: 1097: applyPtr = GetXLogReplayRecPtr(NULL);
call 0 returned 100%
-: 1098:
628: 1099: resetStringInfo(&reply_message);
call 0 returned 100%
628: 1100: pq_sendbyte(&reply_message, 'r');
call 0 returned 100%
628: 1101: pq_sendint64(&reply_message, writePtr);
call 0 returned 100%
628: 1102: pq_sendint64(&reply_message, flushPtr);
call 0 returned 100%
628: 1103: pq_sendint64(&reply_message, applyPtr);
call 0 returned 100%
628: 1104: pq_sendint64(&reply_message, GetCurrentTimestamp());
call 0 returned 100%
call 1 returned 100%
628: 1105: pq_sendbyte(&reply_message, requestReply ? 1 : 0);
call 0 returned 100%
-: 1106:
-: 1107: /* Send it */
628: 1108: elog(DEBUG2, "sending write %X/%X flush %X/%X apply %X/%X%s",
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 returned 100%
-: 1109: (uint32) (writePtr >> 32), (uint32) writePtr,
-: 1110: (uint32) (flushPtr >> 32), (uint32) flushPtr,
-: 1111: (uint32) (applyPtr >> 32), (uint32) applyPtr,
-: 1112: requestReply ? " (reply requested)" : "");
-: 1113:
628: 1114: walrcv_send(wrconn, reply_message.data, reply_message.len);
call 0 returned 100%
-: 1115:}
-: 1116:
-: 1117:/*
-: 1118: * Send hot standby feedback message to primary, plus the current time,
-: 1119: * in case they don't have a watch.
-: 1120: *
-: 1121: * If the user disables feedback, send one final message to tell sender
-: 1122: * to forget about the xmin on this standby. We also send this message
-: 1123: * on first connect because a previous connection might have set xmin
-: 1124: * on a replication slot. (If we're not using a slot it's harmless to
-: 1125: * send a feedback message explicitly setting InvalidTransactionId).
-: 1126: */
-: 1127:static void
function XLogWalRcvSendHSFeedback called 520 returned 100% blocks executed 93%
520: 1128:XLogWalRcvSendHSFeedback(bool immed)
-: 1129:{
-: 1130: TimestampTz now;
-: 1131: FullTransactionId nextFullXid;
-: 1132: TransactionId nextXid;
-: 1133: uint32 xmin_epoch,
-: 1134: catalog_xmin_epoch;
-: 1135: TransactionId xmin,
-: 1136: catalog_xmin;
-: 1137: static TimestampTz sendTime = 0;
-: 1138:
-: 1139: /* initially true so we always send at least one feedback message */
-: 1140: static bool master_has_standby_xmin = true;
-: 1141:
-: 1142: /*
-: 1143: * If the user doesn't want status to be reported to the master, be sure
-: 1144: * to exit before doing anything at all.
-: 1145: */
990: 1146: if ((wal_receiver_status_interval <= 0 || !hot_standby_feedback) &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 90% (fallthrough)
branch 3 taken 10%
branch 4 taken 89% (fallthrough)
branch 5 taken 11%
470: 1147: !master_has_standby_xmin)
884: 1148: return;
-: 1149:
-: 1150: /* Get current timestamp. */
100: 1151: now = GetCurrentTimestamp();
call 0 returned 100%
-: 1152:
100: 1153: if (!immed)
branch 0 taken 96% (fallthrough)
branch 1 taken 4%
-: 1154: {
-: 1155: /*
-: 1156: * Send feedback at most once per wal_receiver_status_interval.
-: 1157: */
96: 1158: if (!TimestampDifferenceExceeds(sendTime, now,
call 0 returned 100%
branch 1 taken 46% (fallthrough)
branch 2 taken 54%
-: 1159: wal_receiver_status_interval * 1000))
44: 1160: return;
52: 1161: sendTime = now;
-: 1162: }
-: 1163:
-: 1164: /*
-: 1165: * If Hot Standby is not yet accepting connections there is nothing to
-: 1166: * send. Check this after the interval has expired to reduce number of
-: 1167: * calls.
-: 1168: *
-: 1169: * Bailing out here also ensures that we don't send feedback until we've
-: 1170: * read our own replication slot state, so we don't tell the master to
-: 1171: * discard needed xmin or catalog_xmin from any slots that may exist on
-: 1172: * this replica.
-: 1173: */
56: 1174: if (!HotStandbyActive())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 1175: return;
-: 1176:
-: 1177: /*
-: 1178: * Make the expensive call to get the oldest xmin once we are certain
-: 1179: * everything else has been checked.
-: 1180: */
56: 1181: if (hot_standby_feedback)
branch 0 taken 11% (fallthrough)
branch 1 taken 89%
-: 1182: {
-: 1183: TransactionId slot_xmin;
-: 1184:
-: 1185: /*
-: 1186: * Usually GetOldestXmin() would include both global replication slot
-: 1187: * xmin and catalog_xmin in its calculations, but we want to derive
-: 1188: * separate values for each of those. So we ask for an xmin that
-: 1189: * excludes the catalog_xmin.
-: 1190: */
6: 1191: xmin = GetOldestXmin(NULL,
call 0 returned 100%
-: 1192: PROCARRAY_FLAGS_DEFAULT | PROCARRAY_SLOTS_XMIN);
-: 1193:
6: 1194: ProcArrayGetReplicationSlotXmin(&slot_xmin, &catalog_xmin);
call 0 returned 100%
-: 1195:
8: 1196: if (TransactionIdIsValid(slot_xmin) &&
branch 0 taken 33% (fallthrough)
branch 1 taken 67%
branch 2 taken 50% (fallthrough)
branch 3 taken 50%
2: 1197: TransactionIdPrecedes(slot_xmin, xmin))
call 0 returned 100%
1: 1198: xmin = slot_xmin;
-: 1199: }
-: 1200: else
-: 1201: {
50: 1202: xmin = InvalidTransactionId;
50: 1203: catalog_xmin = InvalidTransactionId;
-: 1204: }
-: 1205:
-: 1206: /*
-: 1207: * Get epoch and adjust if nextXid and oldestXmin are different sides of
-: 1208: * the epoch boundary.
-: 1209: */
56: 1210: nextFullXid = ReadNextFullTransactionId();
call 0 returned 100%
56: 1211: nextXid = XidFromFullTransactionId(nextFullXid);
56: 1212: xmin_epoch = EpochFromFullTransactionId(nextFullXid);
56: 1213: catalog_xmin_epoch = xmin_epoch;
56: 1214: if (nextXid < xmin)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1215: xmin_epoch--;
56: 1216: if (nextXid < catalog_xmin)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1217: catalog_xmin_epoch--;
-: 1218:
56: 1219: elog(DEBUG2, "sending hot standby feedback xmin %u epoch %u catalog_xmin %u catalog_xmin_epoch %u",
call 0 returned 100%
call 1 returned 100%
-: 1220: xmin, xmin_epoch, catalog_xmin, catalog_xmin_epoch);
-: 1221:
-: 1222: /* Construct the message and send it. */
56: 1223: resetStringInfo(&reply_message);
call 0 returned 100%
56: 1224: pq_sendbyte(&reply_message, 'h');
call 0 returned 100%
56: 1225: pq_sendint64(&reply_message, GetCurrentTimestamp());
call 0 returned 100%
call 1 returned 100%
56: 1226: pq_sendint32(&reply_message, xmin);
call 0 returned 100%
56: 1227: pq_sendint32(&reply_message, xmin_epoch);
call 0 returned 100%
56: 1228: pq_sendint32(&reply_message, catalog_xmin);
call 0 returned 100%
56: 1229: pq_sendint32(&reply_message, catalog_xmin_epoch);
call 0 returned 100%
56: 1230: walrcv_send(wrconn, reply_message.data, reply_message.len);
call 0 returned 100%
56: 1231: if (TransactionIdIsValid(xmin) || TransactionIdIsValid(catalog_xmin))
branch 0 taken 89% (fallthrough)
branch 1 taken 11%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
6: 1232: master_has_standby_xmin = true;
-: 1233: else
50: 1234: master_has_standby_xmin = false;
-: 1235:}
-: 1236:
-: 1237:/*
-: 1238: * Update shared memory status upon receiving a message from primary.
-: 1239: *
-: 1240: * 'walEnd' and 'sendTime' are the end-of-WAL and timestamp of the latest
-: 1241: * message, reported by primary.
-: 1242: */
-: 1243:static void
function ProcessWalSndrMessage called 311 returned 100% blocks executed 80%
311: 1244:ProcessWalSndrMessage(XLogRecPtr walEnd, TimestampTz sendTime)
-: 1245:{
311: 1246: WalRcvData *walrcv = WalRcv;
-: 1247:
311: 1248: TimestampTz lastMsgReceiptTime = GetCurrentTimestamp();
call 0 returned 100%
-: 1249:
-: 1250: /* Update shared-memory status */
311: 1251: SpinLockAcquire(&walrcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
311: 1252: if (walrcv->latestWalEnd < walEnd)
branch 0 taken 77% (fallthrough)
branch 1 taken 23%
240: 1253: walrcv->latestWalEndTime = sendTime;
311: 1254: walrcv->latestWalEnd = walEnd;
311: 1255: walrcv->lastMsgSendTime = sendTime;
311: 1256: walrcv->lastMsgReceiptTime = lastMsgReceiptTime;
311: 1257: SpinLockRelease(&walrcv->mutex);
call 0 returned 100%
-: 1258:
311: 1259: if (log_min_messages <= DEBUG2)
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
-: 1260: {
-: 1261: char *sendtime;
-: 1262: char *receipttime;
-: 1263: int applyDelay;
-: 1264:
-: 1265: /* Copy because timestamptz_to_str returns a static buffer */
7: 1266: sendtime = pstrdup(timestamptz_to_str(sendTime));
call 0 returned 100%
call 1 returned 100%
7: 1267: receipttime = pstrdup(timestamptz_to_str(lastMsgReceiptTime));
call 0 returned 100%
call 1 returned 100%
7: 1268: applyDelay = GetReplicationApplyDelay();
call 0 returned 100%
-: 1269:
-: 1270: /* apply delay is not available */
7: 1271: if (applyDelay == -1)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1272: elog(DEBUG2, "sendtime %s receipttime %s replication apply delay (N/A) transfer latency %d ms",
call 0 never executed
call 1 never executed
call 2 never executed
-: 1273: sendtime,
-: 1274: receipttime,
-: 1275: GetReplicationTransferLatency());
-: 1276: else
7: 1277: elog(DEBUG2, "sendtime %s receipttime %s replication apply delay %d ms transfer latency %d ms",
call 0 returned 100%
call 1 returned 100%
call 2 returned 100%
-: 1278: sendtime,
-: 1279: receipttime,
-: 1280: applyDelay,
-: 1281: GetReplicationTransferLatency());
-: 1282:
7: 1283: pfree(sendtime);
call 0 returned 100%
7: 1284: pfree(receipttime);
call 0 returned 100%
-: 1285: }
311: 1286:}
-: 1287:
-: 1288:/*
-: 1289: * Wake up the walreceiver main loop.
-: 1290: *
-: 1291: * This is called by the startup process whenever interesting xlog records
-: 1292: * are applied, so that walreceiver can check if it needs to send an apply
-: 1293: * notification back to the master which may be waiting in a COMMIT with
-: 1294: * synchronous_commit = remote_apply.
-: 1295: */
-: 1296:void
function WalRcvForceReply called 186 returned 100% blocks executed 88%
186: 1297:WalRcvForceReply(void)
-: 1298:{
-: 1299: Latch *latch;
-: 1300:
186: 1301: WalRcv->force_reply = true;
-: 1302: /* fetching the latch pointer might not be atomic, so use spinlock */
186: 1303: SpinLockAcquire(&WalRcv->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
186: 1304: latch = WalRcv->latch;
186: 1305: SpinLockRelease(&WalRcv->mutex);
call 0 returned 100%
186: 1306: if (latch)
branch 0 taken 69% (fallthrough)
branch 1 taken 31%
128: 1307: SetLatch(latch);
call 0 returned 100%
186: 1308:}
-: 1309:
-: 1310:/*
-: 1311: * Return a string constant representing the state. This is used
-: 1312: * in system functions and views, and should *not* be translated.
-: 1313: */
-: 1314:static const char *
function WalRcvGetStateString called 0 returned 0% blocks executed 0%
#####: 1315:WalRcvGetStateString(WalRcvState state)
-: 1316:{
#####: 1317: switch (state)
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
branch 4 never executed
branch 5 never executed
branch 6 never executed
-: 1318: {
-: 1319: case WALRCV_STOPPED:
#####: 1320: return "stopped";
-: 1321: case WALRCV_STARTING:
#####: 1322: return "starting";
-: 1323: case WALRCV_STREAMING:
#####: 1324: return "streaming";
-: 1325: case WALRCV_WAITING:
#####: 1326: return "waiting";
-: 1327: case WALRCV_RESTARTING:
#####: 1328: return "restarting";
-: 1329: case WALRCV_STOPPING:
#####: 1330: return "stopping";
-: 1331: }
#####: 1332: return "UNKNOWN";
-: 1333:}
-: 1334:
-: 1335:/*
-: 1336: * Returns activity of WAL receiver, including pid, state and xlog locations
-: 1337: * received from the WAL sender of another server.
-: 1338: */
-: 1339:Datum
function pg_stat_get_wal_receiver called 0 returned 0% blocks executed 0%
#####: 1340:pg_stat_get_wal_receiver(PG_FUNCTION_ARGS)
-: 1341:{
-: 1342: TupleDesc tupdesc;
-: 1343: Datum *values;
-: 1344: bool *nulls;
-: 1345: int pid;
-: 1346: bool ready_to_display;
-: 1347: WalRcvState state;
-: 1348: XLogRecPtr receive_start_lsn;
-: 1349: TimeLineID receive_start_tli;
-: 1350: XLogRecPtr received_lsn;
-: 1351: TimeLineID received_tli;
-: 1352: TimestampTz last_send_time;
-: 1353: TimestampTz last_receipt_time;
-: 1354: XLogRecPtr latest_end_lsn;
-: 1355: TimestampTz latest_end_time;
-: 1356: char sender_host[NI_MAXHOST];
#####: 1357: int sender_port = 0;
-: 1358: char slotname[NAMEDATALEN];
-: 1359: char conninfo[MAXCONNINFO];
-: 1360:
-: 1361: /* Take a lock to ensure value consistency */
#####: 1362: SpinLockAcquire(&WalRcv->mutex);
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
#####: 1363: pid = (int) WalRcv->pid;
#####: 1364: ready_to_display = WalRcv->ready_to_display;
#####: 1365: state = WalRcv->walRcvState;
#####: 1366: receive_start_lsn = WalRcv->receiveStart;
#####: 1367: receive_start_tli = WalRcv->receiveStartTLI;
#####: 1368: received_lsn = WalRcv->receivedUpto;
#####: 1369: received_tli = WalRcv->receivedTLI;
#####: 1370: last_send_time = WalRcv->lastMsgSendTime;
#####: 1371: last_receipt_time = WalRcv->lastMsgReceiptTime;
#####: 1372: latest_end_lsn = WalRcv->latestWalEnd;
#####: 1373: latest_end_time = WalRcv->latestWalEndTime;
#####: 1374: strlcpy(slotname, (char *) WalRcv->slotname, sizeof(slotname));
call 0 never executed
#####: 1375: strlcpy(sender_host, (char *) WalRcv->sender_host, sizeof(sender_host));
call 0 never executed
#####: 1376: sender_port = WalRcv->sender_port;
#####: 1377: strlcpy(conninfo, (char *) WalRcv->conninfo, sizeof(conninfo));
call 0 never executed
#####: 1378: SpinLockRelease(&WalRcv->mutex);
call 0 never executed
-: 1379:
-: 1380: /*
-: 1381: * No WAL receiver (or not ready yet), just return a tuple with NULL
-: 1382: * values
-: 1383: */
#####: 1384: if (pid == 0 || !ready_to_display)
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
#####: 1385: PG_RETURN_NULL();
-: 1386:
-: 1387: /* determine result type */
#####: 1388: if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 1389: elog(ERROR, "return type must be a row type");
call 0 never executed
call 1 never executed
call 2 never executed
-: 1390:
#####: 1391: values = palloc0(sizeof(Datum) * tupdesc->natts);
call 0 never executed
#####: 1392: nulls = palloc0(sizeof(bool) * tupdesc->natts);
call 0 never executed
-: 1393:
-: 1394: /* Fetch values */
#####: 1395: values[0] = Int32GetDatum(pid);
-: 1396:
#####: 1397: if (!is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_STATS))
call 0 never executed
call 1 never executed
branch 2 never executed
branch 3 never executed
-: 1398: {
-: 1399: /*
-: 1400: * Only superusers and members of pg_read_all_stats can see details.
-: 1401: * Other users only get the pid value to know whether it is a WAL
-: 1402: * receiver, but no details.
-: 1403: */
#####: 1404: MemSet(&nulls[1], true, sizeof(bool) * (tupdesc->natts - 1));
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
branch 4 never executed
branch 5 never executed
branch 6 never executed
branch 7 never executed
branch 8 never executed
branch 9 never executed
-: 1405: }
-: 1406: else
-: 1407: {
#####: 1408: values[1] = CStringGetTextDatum(WalRcvGetStateString(state));
call 0 never executed
call 1 never executed
-: 1409:
#####: 1410: if (XLogRecPtrIsInvalid(receive_start_lsn))
branch 0 never executed
branch 1 never executed
#####: 1411: nulls[2] = true;
-: 1412: else
#####: 1413: values[2] = LSNGetDatum(receive_start_lsn);
#####: 1414: values[3] = Int32GetDatum(receive_start_tli);
#####: 1415: if (XLogRecPtrIsInvalid(received_lsn))
branch 0 never executed
branch 1 never executed
#####: 1416: nulls[4] = true;
-: 1417: else
#####: 1418: values[4] = LSNGetDatum(received_lsn);
#####: 1419: values[5] = Int32GetDatum(received_tli);
#####: 1420: if (last_send_time == 0)
branch 0 never executed
branch 1 never executed
#####: 1421: nulls[6] = true;
-: 1422: else
#####: 1423: values[6] = TimestampTzGetDatum(last_send_time);
#####: 1424: if (last_receipt_time == 0)
branch 0 never executed
branch 1 never executed
#####: 1425: nulls[7] = true;
-: 1426: else
#####: 1427: values[7] = TimestampTzGetDatum(last_receipt_time);
#####: 1428: if (XLogRecPtrIsInvalid(latest_end_lsn))
branch 0 never executed
branch 1 never executed
#####: 1429: nulls[8] = true;
-: 1430: else
#####: 1431: values[8] = LSNGetDatum(latest_end_lsn);
#####: 1432: if (latest_end_time == 0)
branch 0 never executed
branch 1 never executed
#####: 1433: nulls[9] = true;
-: 1434: else
#####: 1435: values[9] = TimestampTzGetDatum(latest_end_time);
#####: 1436: if (*slotname == '\0')
branch 0 never executed
branch 1 never executed
#####: 1437: nulls[10] = true;
-: 1438: else
#####: 1439: values[10] = CStringGetTextDatum(slotname);
call 0 never executed
#####: 1440: if (*sender_host == '\0')
branch 0 never executed
branch 1 never executed
#####: 1441: nulls[11] = true;
-: 1442: else
#####: 1443: values[11] = CStringGetTextDatum(sender_host);
call 0 never executed
#####: 1444: if (sender_port == 0)
branch 0 never executed
branch 1 never executed
#####: 1445: nulls[12] = true;
-: 1446: else
#####: 1447: values[12] = Int32GetDatum(sender_port);
#####: 1448: if (*conninfo == '\0')
branch 0 never executed
branch 1 never executed
#####: 1449: nulls[13] = true;
-: 1450: else
#####: 1451: values[13] = CStringGetTextDatum(conninfo);
call 0 never executed
-: 1452: }
-: 1453:
-: 1454: /* Returns the record as Datum */
#####: 1455: PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
call 0 never executed
call 1 never executed
-: 1456:}
base_code_coverage/walsender.c.gcov 0000664 0001750 0000000 00000612417 13560201435 017131 0 ustar vignesh root -: 0:Source:walsender.c
-: 0:Graph:./walsender.gcno
-: 0:Data:./walsender.gcda
-: 0:Runs:6536
-: 0:Programs:1
-: 1:/*-------------------------------------------------------------------------
-: 2: *
-: 3: * walsender.c
-: 4: *
-: 5: * The WAL sender process (walsender) is new as of Postgres 9.0. It takes
-: 6: * care of sending XLOG from the primary server to a single recipient.
-: 7: * (Note that there can be more than one walsender process concurrently.)
-: 8: * It is started by the postmaster when the walreceiver of a standby server
-: 9: * connects to the primary server and requests XLOG streaming replication.
-: 10: *
-: 11: * A walsender is similar to a regular backend, ie. there is a one-to-one
-: 12: * relationship between a connection and a walsender process, but instead
-: 13: * of processing SQL queries, it understands a small set of special
-: 14: * replication-mode commands. The START_REPLICATION command begins streaming
-: 15: * WAL to the client. While streaming, the walsender keeps reading XLOG
-: 16: * records from the disk and sends them to the standby server over the
-: 17: * COPY protocol, until either side ends the replication by exiting COPY
-: 18: * mode (or until the connection is closed).
-: 19: *
-: 20: * Normal termination is by SIGTERM, which instructs the walsender to
-: 21: * close the connection and exit(0) at the next convenient moment. Emergency
-: 22: * termination is by SIGQUIT; like any backend, the walsender will simply
-: 23: * abort and exit on SIGQUIT. A close of the connection and a FATAL error
-: 24: * are treated as not a crash but approximately normal termination;
-: 25: * the walsender will exit quickly without sending any more XLOG records.
-: 26: *
-: 27: * If the server is shut down, checkpointer sends us
-: 28: * PROCSIG_WALSND_INIT_STOPPING after all regular backends have exited. If
-: 29: * the backend is idle or runs an SQL query this causes the backend to
-: 30: * shutdown, if logical replication is in progress all existing WAL records
-: 31: * are processed followed by a shutdown. Otherwise this causes the walsender
-: 32: * to switch to the "stopping" state. In this state, the walsender will reject
-: 33: * any further replication commands. The checkpointer begins the shutdown
-: 34: * checkpoint once all walsenders are confirmed as stopping. When the shutdown
-: 35: * checkpoint finishes, the postmaster sends us SIGUSR2. This instructs
-: 36: * walsender to send any outstanding WAL, including the shutdown checkpoint
-: 37: * record, wait for it to be replicated to the standby, and then exit.
-: 38: *
-: 39: *
-: 40: * Portions Copyright (c) 2010-2019, PostgreSQL Global Development Group
-: 41: *
-: 42: * IDENTIFICATION
-: 43: * src/backend/replication/walsender.c
-: 44: *
-: 45: *-------------------------------------------------------------------------
-: 46: */
-: 47:#include "postgres.h"
-: 48:
-: 49:#include <signal.h>
-: 50:#include <unistd.h>
-: 51:
-: 52:#include "access/printtup.h"
-: 53:#include "access/timeline.h"
-: 54:#include "access/transam.h"
-: 55:#include "access/xact.h"
-: 56:#include "access/xlog_internal.h"
-: 57:#include "access/xlogutils.h"
-: 58:
-: 59:#include "catalog/pg_authid.h"
-: 60:#include "catalog/pg_type.h"
-: 61:#include "commands/dbcommands.h"
-: 62:#include "commands/defrem.h"
-: 63:#include "funcapi.h"
-: 64:#include "libpq/libpq.h"
-: 65:#include "libpq/pqformat.h"
-: 66:#include "miscadmin.h"
-: 67:#include "nodes/replnodes.h"
-: 68:#include "pgstat.h"
-: 69:#include "replication/basebackup.h"
-: 70:#include "replication/decode.h"
-: 71:#include "replication/logical.h"
-: 72:#include "replication/logicalfuncs.h"
-: 73:#include "replication/slot.h"
-: 74:#include "replication/snapbuild.h"
-: 75:#include "replication/syncrep.h"
-: 76:#include "replication/walreceiver.h"
-: 77:#include "replication/walsender.h"
-: 78:#include "replication/walsender_private.h"
-: 79:#include "storage/condition_variable.h"
-: 80:#include "storage/fd.h"
-: 81:#include "storage/ipc.h"
-: 82:#include "storage/pmsignal.h"
-: 83:#include "storage/proc.h"
-: 84:#include "storage/procarray.h"
-: 85:#include "tcop/dest.h"
-: 86:#include "tcop/tcopprot.h"
-: 87:#include "utils/builtins.h"
-: 88:#include "utils/guc.h"
-: 89:#include "utils/memutils.h"
-: 90:#include "utils/pg_lsn.h"
-: 91:#include "utils/portal.h"
-: 92:#include "utils/ps_status.h"
-: 93:#include "utils/timeout.h"
-: 94:#include "utils/timestamp.h"
-: 95:
-: 96:/*
-: 97: * Maximum data payload in a WAL data message. Must be >= XLOG_BLCKSZ.
-: 98: *
-: 99: * We don't have a good idea of what a good value would be; there's some
-: 100: * overhead per message in both walsender and walreceiver, but on the other
-: 101: * hand sending large batches makes walsender less responsive to signals
-: 102: * because signals are checked only between messages. 128kB (with
-: 103: * default 8k blocks) seems like a reasonable guess for now.
-: 104: */
-: 105:#define MAX_SEND_SIZE (XLOG_BLCKSZ * 16)
-: 106:
-: 107:/* Array of WalSnds in shared memory */
-: 108:WalSndCtlData *WalSndCtl = NULL;
-: 109:
-: 110:/* My slot in the shared memory array */
-: 111:WalSnd *MyWalSnd = NULL;
-: 112:
-: 113:/* Global state */
-: 114:bool am_walsender = false; /* Am I a walsender process? */
-: 115:bool am_cascading_walsender = false; /* Am I cascading WAL to another
-: 116: * standby? */
-: 117:bool am_db_walsender = false; /* Connected to a database? */
-: 118:
-: 119:/* User-settable parameters for walsender */
-: 120:int max_wal_senders = 0; /* the maximum number of concurrent
-: 121: * walsenders */
-: 122:int wal_sender_timeout = 60 * 1000; /* maximum time to send one WAL
-: 123: * data message */
-: 124:bool log_replication_commands = false;
-: 125:
-: 126:/*
-: 127: * State for WalSndWakeupRequest
-: 128: */
-: 129:bool wake_wal_senders = false;
-: 130:
-: 131:static WALOpenSegment *sendSeg = NULL;
-: 132:static WALSegmentContext *sendCxt = NULL;
-: 133:
-: 134:/*
-: 135: * These variables keep track of the state of the timeline we're currently
-: 136: * sending. sendTimeLine identifies the timeline. If sendTimeLineIsHistoric,
-: 137: * the timeline is not the latest timeline on this server, and the server's
-: 138: * history forked off from that timeline at sendTimeLineValidUpto.
-: 139: */
-: 140:static TimeLineID sendTimeLine = 0;
-: 141:static TimeLineID sendTimeLineNextTLI = 0;
-: 142:static bool sendTimeLineIsHistoric = false;
-: 143:static XLogRecPtr sendTimeLineValidUpto = InvalidXLogRecPtr;
-: 144:
-: 145:/*
-: 146: * How far have we sent WAL already? This is also advertised in
-: 147: * MyWalSnd->sentPtr. (Actually, this is the next WAL location to send.)
-: 148: */
-: 149:static XLogRecPtr sentPtr = 0;
-: 150:
-: 151:/* Buffers for constructing outgoing messages and processing reply messages. */
-: 152:static StringInfoData output_message;
-: 153:static StringInfoData reply_message;
-: 154:static StringInfoData tmpbuf;
-: 155:
-: 156:/* Timestamp of last ProcessRepliesIfAny(). */
-: 157:static TimestampTz last_processing = 0;
-: 158:
-: 159:/*
-: 160: * Timestamp of last ProcessRepliesIfAny() that saw a reply from the
-: 161: * standby. Set to 0 if wal_sender_timeout doesn't need to be active.
-: 162: */
-: 163:static TimestampTz last_reply_timestamp = 0;
-: 164:
-: 165:/* Have we sent a heartbeat message asking for reply, since last reply? */
-: 166:static bool waiting_for_ping_response = false;
-: 167:
-: 168:/*
-: 169: * While streaming WAL in Copy mode, streamingDoneSending is set to true
-: 170: * after we have sent CopyDone. We should not send any more CopyData messages
-: 171: * after that. streamingDoneReceiving is set to true when we receive CopyDone
-: 172: * from the other end. When both become true, it's time to exit Copy mode.
-: 173: */
-: 174:static bool streamingDoneSending;
-: 175:static bool streamingDoneReceiving;
-: 176:
-: 177:/* Are we there yet? */
-: 178:static bool WalSndCaughtUp = false;
-: 179:
-: 180:/* Flags set by signal handlers for later service in main loop */
-: 181:static volatile sig_atomic_t got_SIGUSR2 = false;
-: 182:static volatile sig_atomic_t got_STOPPING = false;
-: 183:
-: 184:/*
-: 185: * This is set while we are streaming. When not set
-: 186: * PROCSIG_WALSND_INIT_STOPPING signal will be handled like SIGTERM. When set,
-: 187: * the main loop is responsible for checking got_STOPPING and terminating when
-: 188: * it's set (after streaming any remaining WAL).
-: 189: */
-: 190:static volatile sig_atomic_t replication_active = false;
-: 191:
-: 192:static LogicalDecodingContext *logical_decoding_ctx = NULL;
-: 193:static XLogRecPtr logical_startptr = InvalidXLogRecPtr;
-: 194:
-: 195:/* A sample associating a WAL location with the time it was written. */
-: 196:typedef struct
-: 197:{
-: 198: XLogRecPtr lsn;
-: 199: TimestampTz time;
-: 200:} WalTimeSample;
-: 201:
-: 202:/* The size of our buffer of time samples. */
-: 203:#define LAG_TRACKER_BUFFER_SIZE 8192
-: 204:
-: 205:/* A mechanism for tracking replication lag. */
-: 206:typedef struct
-: 207:{
-: 208: XLogRecPtr last_lsn;
-: 209: WalTimeSample buffer[LAG_TRACKER_BUFFER_SIZE];
-: 210: int write_head;
-: 211: int read_heads[NUM_SYNC_REP_WAIT_MODE];
-: 212: WalTimeSample last_read[NUM_SYNC_REP_WAIT_MODE];
-: 213:} LagTracker;
-: 214:
-: 215:static LagTracker *lag_tracker;
-: 216:
-: 217:/* Signal handlers */
-: 218:static void WalSndLastCycleHandler(SIGNAL_ARGS);
-: 219:
-: 220:/* Prototypes for private functions */
-: 221:typedef void (*WalSndSendDataCallback) (void);
-: 222:static void WalSndLoop(WalSndSendDataCallback send_data);
-: 223:static void InitWalSenderSlot(void);
-: 224:static void WalSndKill(int code, Datum arg);
-: 225:static void WalSndShutdown(void) pg_attribute_noreturn();
-: 226:static void XLogSendPhysical(void);
-: 227:static void XLogSendLogical(void);
-: 228:static void WalSndDone(WalSndSendDataCallback send_data);
-: 229:static XLogRecPtr GetStandbyFlushRecPtr(void);
-: 230:static void IdentifySystem(void);
-: 231:static void CreateReplicationSlot(CreateReplicationSlotCmd *cmd);
-: 232:static void DropReplicationSlot(DropReplicationSlotCmd *cmd);
-: 233:static void StartReplication(StartReplicationCmd *cmd);
-: 234:static void StartLogicalReplication(StartReplicationCmd *cmd);
-: 235:static void ProcessStandbyMessage(void);
-: 236:static void ProcessStandbyReplyMessage(void);
-: 237:static void ProcessStandbyHSFeedbackMessage(void);
-: 238:static void ProcessRepliesIfAny(void);
-: 239:static void WalSndKeepalive(bool requestReply);
-: 240:static void WalSndKeepaliveIfNecessary(void);
-: 241:static void WalSndCheckTimeOut(void);
-: 242:static long WalSndComputeSleeptime(TimestampTz now);
-: 243:static void WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
-: 244:static void WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
-: 245:static void WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid);
-: 246:static XLogRecPtr WalSndWaitForWal(XLogRecPtr loc);
-: 247:static void LagTrackerWrite(XLogRecPtr lsn, TimestampTz local_flush_time);
-: 248:static TimeOffset LagTrackerRead(int head, XLogRecPtr lsn, TimestampTz now);
-: 249:static bool TransactionIdInRecentPast(TransactionId xid, uint32 epoch);
-: 250:
-: 251:static void XLogRead(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count);
-: 252:
-: 253:
-: 254:/* Initialize walsender process before entering the main command loop */
-: 255:void
function InitWalSender called 229 returned 100% blocks executed 100%
229: 256:InitWalSender(void)
-: 257:{
229: 258: am_cascading_walsender = RecoveryInProgress();
call 0 returned 100%
-: 259:
-: 260: /* Create a per-walsender data structure in shared memory */
229: 261: InitWalSenderSlot();
call 0 returned 100%
-: 262:
-: 263: /*
-: 264: * We don't currently need any ResourceOwner in a walsender process, but
-: 265: * if we did, we could call CreateAuxProcessResourceOwner here.
-: 266: */
-: 267:
-: 268: /*
-: 269: * Let postmaster know that we're a WAL sender. Once we've declared us as
-: 270: * a WAL sender process, postmaster will let us outlive the bgwriter and
-: 271: * kill us last in the shutdown sequence, so we get a chance to stream all
-: 272: * remaining WAL at shutdown, including the shutdown checkpoint. Note that
-: 273: * there's no going back, and we mustn't write any WAL records after this.
-: 274: */
229: 275: MarkPostmasterChildWalSender();
call 0 returned 100%
229: 276: SendPostmasterSignal(PMSIGNAL_ADVANCE_STATE_MACHINE);
call 0 returned 100%
-: 277:
-: 278: /* Initialize empty timestamp buffer for lag tracking. */
229: 279: lag_tracker = MemoryContextAllocZero(TopMemoryContext, sizeof(LagTracker));
call 0 returned 100%
-: 280:
-: 281: /* Make sure we can remember the current read position in XLOG. */
229: 282: sendSeg = (WALOpenSegment *)
229: 283: MemoryContextAlloc(TopMemoryContext, sizeof(WALOpenSegment));
call 0 returned 100%
229: 284: sendCxt = (WALSegmentContext *)
229: 285: MemoryContextAlloc(TopMemoryContext, sizeof(WALSegmentContext));
call 0 returned 100%
229: 286: WALOpenSegmentInit(sendSeg, sendCxt, wal_segment_size, NULL);
call 0 returned 100%
229: 287:}
-: 288:
-: 289:/*
-: 290: * Clean up after an error.
-: 291: *
-: 292: * WAL sender processes don't use transactions like regular backends do.
-: 293: * This function does any cleanup required after an error in a WAL sender
-: 294: * process, similar to what transaction abort does in a regular backend.
-: 295: */
-: 296:void
function WalSndErrorCleanup called 8 returned 100% blocks executed 71%
8: 297:WalSndErrorCleanup(void)
-: 298:{
8: 299: LWLockReleaseAll();
call 0 returned 100%
8: 300: ConditionVariableCancelSleep();
call 0 returned 100%
8: 301: pgstat_report_wait_end();
call 0 returned 100%
-: 302:
8: 303: if (sendSeg->ws_file >= 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 304: {
#####: 305: close(sendSeg->ws_file);
call 0 never executed
#####: 306: sendSeg->ws_file = -1;
-: 307: }
-: 308:
8: 309: if (MyReplicationSlot != NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 310: ReplicationSlotRelease();
call 0 never executed
-: 311:
8: 312: ReplicationSlotCleanup();
call 0 returned 100%
-: 313:
8: 314: replication_active = false;
-: 315:
8: 316: if (got_STOPPING || got_SIGUSR2)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 317: proc_exit(0);
call 0 never executed
-: 318:
-: 319: /* Revert back to startup state */
8: 320: WalSndSetState(WALSNDSTATE_STARTUP);
call 0 returned 100%
8: 321:}
-: 322:
-: 323:/*
-: 324: * Handle a client's connection abort in an orderly manner.
-: 325: */
-: 326:static void
function WalSndShutdown called 0 returned 0% blocks executed 0%
#####: 327:WalSndShutdown(void)
-: 328:{
-: 329: /*
-: 330: * Reset whereToSendOutput to prevent ereport from attempting to send any
-: 331: * more messages to the standby.
-: 332: */
#####: 333: if (whereToSendOutput == DestRemote)
branch 0 never executed
branch 1 never executed
#####: 334: whereToSendOutput = DestNone;
-: 335:
#####: 336: proc_exit(0);
-: 337: abort(); /* keep the compiler quiet */
-: 338:}
-: 339:
-: 340:/*
-: 341: * Handle the IDENTIFY_SYSTEM command.
-: 342: */
-: 343:static void
function IdentifySystem called 165 returned 100% blocks executed 84%
165: 344:IdentifySystem(void)
-: 345:{
-: 346: char sysid[32];
-: 347: char xloc[MAXFNAMELEN];
-: 348: XLogRecPtr logptr;
165: 349: char *dbname = NULL;
-: 350: DestReceiver *dest;
-: 351: TupOutputState *tstate;
-: 352: TupleDesc tupdesc;
-: 353: Datum values[4];
-: 354: bool nulls[4];
-: 355:
-: 356: /*
-: 357: * Reply with a result set with one row, four columns. First col is system
-: 358: * ID, second is timeline ID, third is current xlog location and the
-: 359: * fourth contains the database name if we are connected to one.
-: 360: */
-: 361:
165: 362: snprintf(sysid, sizeof(sysid), UINT64_FORMAT,
call 0 returned 100%
call 1 returned 100%
-: 363: GetSystemIdentifier());
-: 364:
165: 365: am_cascading_walsender = RecoveryInProgress();
call 0 returned 100%
165: 366: if (am_cascading_walsender)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
-: 367: {
-: 368: /* this also updates ThisTimeLineID */
5: 369: logptr = GetStandbyFlushRecPtr();
call 0 returned 100%
-: 370: }
-: 371: else
160: 372: logptr = GetFlushRecPtr();
call 0 returned 100%
-: 373:
165: 374: snprintf(xloc, sizeof(xloc), "%X/%X", (uint32) (logptr >> 32), (uint32) logptr);
call 0 returned 100%
-: 375:
165: 376: if (MyDatabaseId != InvalidOid)
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
-: 377: {
24: 378: MemoryContext cur = CurrentMemoryContext;
-: 379:
-: 380: /* syscache access needs a transaction env. */
24: 381: StartTransactionCommand();
call 0 returned 100%
-: 382: /* make dbname live outside TX context */
24: 383: MemoryContextSwitchTo(cur);
call 0 returned 100%
24: 384: dbname = get_database_name(MyDatabaseId);
call 0 returned 100%
24: 385: CommitTransactionCommand();
call 0 returned 100%
-: 386: /* CommitTransactionCommand switches to TopMemoryContext */
24: 387: MemoryContextSwitchTo(cur);
call 0 returned 100%
-: 388: }
-: 389:
165: 390: dest = CreateDestReceiver(DestRemoteSimple);
call 0 returned 100%
165: 391: MemSet(nulls, false, sizeof(nulls));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
branch 4 never executed
branch 5 never executed
branch 6 never executed
branch 7 never executed
branch 8 never executed
branch 9 never executed
-: 392:
-: 393: /* need a tuple descriptor representing four columns */
165: 394: tupdesc = CreateTemplateTupleDesc(4);
call 0 returned 100%
165: 395: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "systemid",
call 0 returned 100%
-: 396: TEXTOID, -1, 0);
165: 397: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "timeline",
call 0 returned 100%
-: 398: INT4OID, -1, 0);
165: 399: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "xlogpos",
call 0 returned 100%
-: 400: TEXTOID, -1, 0);
165: 401: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 4, "dbname",
call 0 returned 100%
-: 402: TEXTOID, -1, 0);
-: 403:
-: 404: /* prepare for projection of tuples */
165: 405: tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
call 0 returned 100%
-: 406:
-: 407: /* column 1: system identifier */
165: 408: values[0] = CStringGetTextDatum(sysid);
call 0 returned 100%
-: 409:
-: 410: /* column 2: timeline */
165: 411: values[1] = Int32GetDatum(ThisTimeLineID);
-: 412:
-: 413: /* column 3: wal location */
165: 414: values[2] = CStringGetTextDatum(xloc);
call 0 returned 100%
-: 415:
-: 416: /* column 4: database name, or NULL if none */
165: 417: if (dbname)
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
24: 418: values[3] = CStringGetTextDatum(dbname);
call 0 returned 100%
-: 419: else
141: 420: nulls[3] = true;
-: 421:
-: 422: /* send it to dest */
165: 423: do_tup_output(tstate, values, nulls);
call 0 returned 100%
-: 424:
165: 425: end_tup_output(tstate);
call 0 returned 100%
165: 426:}
-: 427:
-: 428:
-: 429:/*
-: 430: * Handle TIMELINE_HISTORY command.
-: 431: */
-: 432:static void
function SendTimeLineHistory called 5 returned 100% blocks executed 51%
5: 433:SendTimeLineHistory(TimeLineHistoryCmd *cmd)
-: 434:{
-: 435: StringInfoData buf;
-: 436: char histfname[MAXFNAMELEN];
-: 437: char path[MAXPGPATH];
-: 438: int fd;
-: 439: off_t histfilelen;
-: 440: off_t bytesleft;
-: 441: Size len;
-: 442:
-: 443: /*
-: 444: * Reply with a result set with one row, and two columns. The first col is
-: 445: * the name of the history file, 2nd is the contents.
-: 446: */
-: 447:
5: 448: TLHistoryFileName(histfname, cmd->timeline);
call 0 returned 100%
5: 449: TLHistoryFilePath(path, cmd->timeline);
call 0 returned 100%
-: 450:
-: 451: /* Send a RowDescription message */
5: 452: pq_beginmessage(&buf, 'T');
call 0 returned 100%
5: 453: pq_sendint16(&buf, 2); /* 2 fields */
call 0 returned 100%
-: 454:
-: 455: /* first field */
5: 456: pq_sendstring(&buf, "filename"); /* col name */
call 0 returned 100%
5: 457: pq_sendint32(&buf, 0); /* table oid */
call 0 returned 100%
5: 458: pq_sendint16(&buf, 0); /* attnum */
call 0 returned 100%
5: 459: pq_sendint32(&buf, TEXTOID); /* type oid */
call 0 returned 100%
5: 460: pq_sendint16(&buf, -1); /* typlen */
call 0 returned 100%
5: 461: pq_sendint32(&buf, 0); /* typmod */
call 0 returned 100%
5: 462: pq_sendint16(&buf, 0); /* format code */
call 0 returned 100%
-: 463:
-: 464: /* second field */
5: 465: pq_sendstring(&buf, "content"); /* col name */
call 0 returned 100%
5: 466: pq_sendint32(&buf, 0); /* table oid */
call 0 returned 100%
5: 467: pq_sendint16(&buf, 0); /* attnum */
call 0 returned 100%
5: 468: pq_sendint32(&buf, BYTEAOID); /* type oid */
call 0 returned 100%
5: 469: pq_sendint16(&buf, -1); /* typlen */
call 0 returned 100%
5: 470: pq_sendint32(&buf, 0); /* typmod */
call 0 returned 100%
5: 471: pq_sendint16(&buf, 0); /* format code */
call 0 returned 100%
5: 472: pq_endmessage(&buf);
call 0 returned 100%
-: 473:
-: 474: /* Send a DataRow message */
5: 475: pq_beginmessage(&buf, 'D');
call 0 returned 100%
5: 476: pq_sendint16(&buf, 2); /* # of columns */
call 0 returned 100%
5: 477: len = strlen(histfname);
5: 478: pq_sendint32(&buf, len); /* col1 len */
call 0 returned 100%
5: 479: pq_sendbytes(&buf, histfname, len);
call 0 returned 100%
-: 480:
5: 481: fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
call 0 returned 100%
5: 482: if (fd < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 483: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 484: (errcode_for_file_access(),
-: 485: errmsg("could not open file \"%s\": %m", path)));
-: 486:
-: 487: /* Determine file length and send it to client */
5: 488: histfilelen = lseek(fd, 0, SEEK_END);
call 0 returned 100%
5: 489: if (histfilelen < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 490: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 491: (errcode_for_file_access(),
-: 492: errmsg("could not seek to end of file \"%s\": %m", path)));
5: 493: if (lseek(fd, 0, SEEK_SET) != 0)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 494: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 495: (errcode_for_file_access(),
-: 496: errmsg("could not seek to beginning of file \"%s\": %m", path)));
-: 497:
5: 498: pq_sendint32(&buf, histfilelen); /* col2 len */
call 0 returned 100%
-: 499:
5: 500: bytesleft = histfilelen;
15: 501: while (bytesleft > 0)
branch 0 taken 50%
branch 1 taken 50% (fallthrough)
-: 502: {
-: 503: PGAlignedBlock rbuf;
-: 504: int nread;
-: 505:
5: 506: pgstat_report_wait_start(WAIT_EVENT_WALSENDER_TIMELINE_HISTORY_READ);
call 0 returned 100%
5: 507: nread = read(fd, rbuf.data, sizeof(rbuf));
call 0 returned 100%
5: 508: pgstat_report_wait_end();
call 0 returned 100%
5: 509: if (nread < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 510: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 511: (errcode_for_file_access(),
-: 512: errmsg("could not read file \"%s\": %m",
-: 513: path)));
5: 514: else if (nread == 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 515: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 516: (errcode(ERRCODE_DATA_CORRUPTED),
-: 517: errmsg("could not read file \"%s\": read %d of %zu",
-: 518: path, nread, (Size) bytesleft)));
-: 519:
5: 520: pq_sendbytes(&buf, rbuf.data, nread);
call 0 returned 100%
5: 521: bytesleft -= nread;
-: 522: }
-: 523:
5: 524: if (CloseTransientFile(fd) != 0)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 525: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 526: (errcode_for_file_access(),
-: 527: errmsg("could not close file \"%s\": %m", path)));
-: 528:
5: 529: pq_endmessage(&buf);
call 0 returned 100%
5: 530:}
-: 531:
-: 532:/*
-: 533: * Handle START_REPLICATION command.
-: 534: *
-: 535: * At the moment, this never returns, but an ereport(ERROR) will take us back
-: 536: * to the main loop.
-: 537: */
-: 538:static void
function StartReplication called 89 returned 52% blocks executed 59%
89: 539:StartReplication(StartReplicationCmd *cmd)
-: 540:{
-: 541: StringInfoData buf;
-: 542: XLogRecPtr FlushPtr;
-: 543:
89: 544: if (ThisTimeLineID == 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 545: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 546: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 547: errmsg("IDENTIFY_SYSTEM has not been run before START_REPLICATION")));
-: 548:
-: 549: /*
-: 550: * We assume here that we're logging enough information in the WAL for
-: 551: * log-shipping, since this is checked in PostmasterMain().
-: 552: *
-: 553: * NOTE: wal_level can only change at shutdown, so in most cases it is
-: 554: * difficult for there to be WAL data that we can still see that was
-: 555: * written at wal_level='minimal'.
-: 556: */
-: 557:
89: 558: if (cmd->slotname)
branch 0 taken 49% (fallthrough)
branch 1 taken 51%
-: 559: {
44: 560: ReplicationSlotAcquire(cmd->slotname, true);
call 0 returned 98%
43: 561: if (SlotIsLogical(MyReplicationSlot))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 562: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 563: (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-: 564: (errmsg("cannot use a logical replication slot for physical replication"))));
-: 565: }
-: 566:
-: 567: /*
-: 568: * Select the timeline. If it was given explicitly by the client, use
-: 569: * that. Otherwise use the timeline of the last replayed record, which is
-: 570: * kept in ThisTimeLineID.
-: 571: */
88: 572: if (am_cascading_walsender)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
-: 573: {
-: 574: /* this also updates ThisTimeLineID */
3: 575: FlushPtr = GetStandbyFlushRecPtr();
call 0 returned 100%
-: 576: }
-: 577: else
85: 578: FlushPtr = GetFlushRecPtr();
call 0 returned 100%
-: 579:
88: 580: if (cmd->timeline != 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 581: {
-: 582: XLogRecPtr switchpoint;
-: 583:
88: 584: sendTimeLine = cmd->timeline;
88: 585: if (sendTimeLine == ThisTimeLineID)
branch 0 taken 94% (fallthrough)
branch 1 taken 6%
-: 586: {
83: 587: sendTimeLineIsHistoric = false;
83: 588: sendTimeLineValidUpto = InvalidXLogRecPtr;
-: 589: }
-: 590: else
-: 591: {
-: 592: List *timeLineHistory;
-: 593:
5: 594: sendTimeLineIsHistoric = true;
-: 595:
-: 596: /*
-: 597: * Check that the timeline the client requested exists, and the
-: 598: * requested start location is on that timeline.
-: 599: */
5: 600: timeLineHistory = readTimeLineHistory(ThisTimeLineID);
call 0 returned 100%
5: 601: switchpoint = tliSwitchPoint(cmd->timeline, timeLineHistory,
call 0 returned 100%
-: 602: &sendTimeLineNextTLI);
5: 603: list_free_deep(timeLineHistory);
call 0 returned 100%
-: 604:
-: 605: /*
-: 606: * Found the requested timeline in the history. Check that
-: 607: * requested startpoint is on that timeline in our history.
-: 608: *
-: 609: * This is quite loose on purpose. We only check that we didn't
-: 610: * fork off the requested timeline before the switchpoint. We
-: 611: * don't check that we switched *to* it before the requested
-: 612: * starting point. This is because the client can legitimately
-: 613: * request to start replication from the beginning of the WAL
-: 614: * segment that contains switchpoint, but on the new timeline, so
-: 615: * that it doesn't end up with a partial segment. If you ask for
-: 616: * too old a starting point, you'll get an error later when we
-: 617: * fail to find the requested WAL segment in pg_wal.
-: 618: *
-: 619: * XXX: we could be more strict here and only allow a startpoint
-: 620: * that's older than the switchpoint, if it's still in the same
-: 621: * WAL segment.
-: 622: */
10: 623: if (!XLogRecPtrIsInvalid(switchpoint) &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
5: 624: switchpoint < cmd->startpoint)
-: 625: {
#####: 626: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 627: (errmsg("requested starting point %X/%X on timeline %u is not in this server's history",
-: 628: (uint32) (cmd->startpoint >> 32),
-: 629: (uint32) (cmd->startpoint),
-: 630: cmd->timeline),
-: 631: errdetail("This server's history forked from timeline %u at %X/%X.",
-: 632: cmd->timeline,
-: 633: (uint32) (switchpoint >> 32),
-: 634: (uint32) (switchpoint))));
-: 635: }
5: 636: sendTimeLineValidUpto = switchpoint;
-: 637: }
-: 638: }
-: 639: else
-: 640: {
#####: 641: sendTimeLine = ThisTimeLineID;
#####: 642: sendTimeLineValidUpto = InvalidXLogRecPtr;
#####: 643: sendTimeLineIsHistoric = false;
-: 644: }
-: 645:
88: 646: streamingDoneSending = streamingDoneReceiving = false;
-: 647:
-: 648: /* If there is nothing to stream, don't even enter COPY mode */
88: 649: if (!sendTimeLineIsHistoric || cmd->startpoint < sendTimeLineValidUpto)
branch 0 taken 6% (fallthrough)
branch 1 taken 94%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
-: 650: {
-: 651: /*
-: 652: * When we first start replication the standby will be behind the
-: 653: * primary. For some applications, for example synchronous
-: 654: * replication, it is important to have a clear state for this initial
-: 655: * catchup mode, so we can trigger actions when we change streaming
-: 656: * state later. We may stay in this state for a long time, which is
-: 657: * exactly why we want to be able to monitor whether or not we are
-: 658: * still here.
-: 659: */
88: 660: WalSndSetState(WALSNDSTATE_CATCHUP);
call 0 returned 100%
-: 661:
-: 662: /* Send a CopyBothResponse message, and start streaming */
88: 663: pq_beginmessage(&buf, 'W');
call 0 returned 100%
88: 664: pq_sendbyte(&buf, 0);
call 0 returned 100%
88: 665: pq_sendint16(&buf, 0);
call 0 returned 100%
88: 666: pq_endmessage(&buf);
call 0 returned 100%
88: 667: pq_flush();
call 0 returned 100%
-: 668:
-: 669: /*
-: 670: * Don't allow a request to stream from a future point in WAL that
-: 671: * hasn't been flushed to disk in this server yet.
-: 672: */
88: 673: if (FlushPtr < cmd->startpoint)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 674: {
#####: 675: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 676: (errmsg("requested starting point %X/%X is ahead of the WAL flush position of this server %X/%X",
-: 677: (uint32) (cmd->startpoint >> 32),
-: 678: (uint32) (cmd->startpoint),
-: 679: (uint32) (FlushPtr >> 32),
-: 680: (uint32) (FlushPtr))));
-: 681: }
-: 682:
-: 683: /* Start streaming from the requested point */
88: 684: sentPtr = cmd->startpoint;
-: 685:
-: 686: /* Initialize shared memory status, too */
88: 687: SpinLockAcquire(&MyWalSnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
88: 688: MyWalSnd->sentPtr = sentPtr;
88: 689: SpinLockRelease(&MyWalSnd->mutex);
call 0 returned 100%
-: 690:
88: 691: SyncRepInitConfig();
call 0 returned 100%
-: 692:
-: 693: /* Main loop of walsender */
88: 694: replication_active = true;
-: 695:
88: 696: WalSndLoop(XLogSendPhysical);
call 0 returned 52%
-: 697:
46: 698: replication_active = false;
46: 699: if (got_STOPPING)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 700: proc_exit(0);
call 0 never executed
46: 701: WalSndSetState(WALSNDSTATE_STARTUP);
call 0 returned 100%
-: 702:
46: 703: Assert(streamingDoneSending && streamingDoneReceiving);
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
call 4 never executed
-: 704: }
-: 705:
46: 706: if (cmd->slotname)
branch 0 taken 85% (fallthrough)
branch 1 taken 15%
39: 707: ReplicationSlotRelease();
call 0 returned 100%
-: 708:
-: 709: /*
-: 710: * Copy is finished now. Send a single-row result set indicating the next
-: 711: * timeline.
-: 712: */
46: 713: if (sendTimeLineIsHistoric)
branch 0 taken 11% (fallthrough)
branch 1 taken 89%
-: 714: {
-: 715: char startpos_str[8 + 1 + 8 + 1];
-: 716: DestReceiver *dest;
-: 717: TupOutputState *tstate;
-: 718: TupleDesc tupdesc;
-: 719: Datum values[2];
-: 720: bool nulls[2];
-: 721:
10: 722: snprintf(startpos_str, sizeof(startpos_str), "%X/%X",
call 0 returned 100%
5: 723: (uint32) (sendTimeLineValidUpto >> 32),
-: 724: (uint32) sendTimeLineValidUpto);
-: 725:
5: 726: dest = CreateDestReceiver(DestRemoteSimple);
call 0 returned 100%
5: 727: MemSet(nulls, false, sizeof(nulls));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
branch 4 never executed
branch 5 never executed
branch 6 never executed
branch 7 never executed
branch 8 never executed
branch 9 never executed
-: 728:
-: 729: /*
-: 730: * Need a tuple descriptor representing two columns. int8 may seem
-: 731: * like a surprising data type for this, but in theory int4 would not
-: 732: * be wide enough for this, as TimeLineID is unsigned.
-: 733: */
5: 734: tupdesc = CreateTemplateTupleDesc(2);
call 0 returned 100%
5: 735: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "next_tli",
call 0 returned 100%
-: 736: INT8OID, -1, 0);
5: 737: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "next_tli_startpos",
call 0 returned 100%
-: 738: TEXTOID, -1, 0);
-: 739:
-: 740: /* prepare for projection of tuple */
5: 741: tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
call 0 returned 100%
-: 742:
5: 743: values[0] = Int64GetDatum((int64) sendTimeLineNextTLI);
5: 744: values[1] = CStringGetTextDatum(startpos_str);
call 0 returned 100%
-: 745:
-: 746: /* send it to dest */
5: 747: do_tup_output(tstate, values, nulls);
call 0 returned 100%
-: 748:
5: 749: end_tup_output(tstate);
call 0 returned 100%
-: 750: }
-: 751:
-: 752: /* Send CommandComplete message */
46: 753: pq_puttextmessage('C', "START_STREAMING");
call 0 returned 100%
46: 754:}
-: 755:
-: 756:/*
-: 757: * read_page callback for logical decoding contexts, as a walsender process.
-: 758: *
-: 759: * Inside the walsender we can do better than logical_read_local_xlog_page,
-: 760: * which has to do a plain sleep/busy loop, because the walsender's latch gets
-: 761: * set every time WAL is flushed.
-: 762: */
-: 763:static int
function logical_read_xlog_page called 14792 returned 99% blocks executed 100%
14792: 764:logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
-: 765: XLogRecPtr targetRecPtr, char *cur_page)
-: 766:{
-: 767: XLogRecPtr flushptr;
-: 768: int count;
-: 769:
14792: 770: XLogReadDetermineTimeline(state, targetPagePtr, reqLen);
call 0 returned 100%
14792: 771: sendTimeLineIsHistoric = (state->currTLI != ThisTimeLineID);
14792: 772: sendTimeLine = state->currTLI;
14792: 773: sendTimeLineValidUpto = state->currTLIValidUntil;
14792: 774: sendTimeLineNextTLI = state->nextTLI;
-: 775:
-: 776: /* make sure we have enough WAL available */
14792: 777: flushptr = WalSndWaitForWal(targetPagePtr + reqLen);
call 0 returned 99%
-: 778:
-: 779: /* fail if not (implies we are going to shut down) */
14776: 780: if (flushptr < targetPagePtr + reqLen)
branch 0 taken 48% (fallthrough)
branch 1 taken 52%
7164: 781: return -1;
-: 782:
7612: 783: if (targetPagePtr + XLOG_BLCKSZ <= flushptr)
branch 0 taken 97% (fallthrough)
branch 1 taken 3%
7375: 784: count = XLOG_BLCKSZ; /* more than one block available */
-: 785: else
237: 786: count = flushptr - targetPagePtr; /* part of the page available */
-: 787:
-: 788: /* now actually read the data, we know it's there */
7612: 789: XLogRead(sendCxt, cur_page, targetPagePtr, XLOG_BLCKSZ);
call 0 returned 100%
-: 790:
7612: 791: return count;
-: 792:}
-: 793:
-: 794:/*
-: 795: * Process extra options given to CREATE_REPLICATION_SLOT.
-: 796: */
-: 797:static void
function parseCreateReplSlotOptions called 98 returned 100% blocks executed 48%
98: 798:parseCreateReplSlotOptions(CreateReplicationSlotCmd *cmd,
-: 799: bool *reserve_wal,
-: 800: CRSSnapshotAction *snapshot_action)
-: 801:{
-: 802: ListCell *lc;
98: 803: bool snapshot_action_given = false;
98: 804: bool reserve_wal_given = false;
-: 805:
-: 806: /* Parse options */
195: 807: foreach(lc, cmd->options)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 50% (fallthrough)
branch 3 taken 50%
branch 4 taken 50%
branch 5 taken 50% (fallthrough)
-: 808: {
97: 809: DefElem *defel = (DefElem *) lfirst(lc);
-: 810:
97: 811: if (strcmp(defel->defname, "export_snapshot") == 0)
branch 0 taken 19% (fallthrough)
branch 1 taken 81%
-: 812: {
18: 813: if (snapshot_action_given || cmd->kind != REPLICATION_KIND_LOGICAL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 814: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 815: (errcode(ERRCODE_SYNTAX_ERROR),
-: 816: errmsg("conflicting or redundant options")));
-: 817:
18: 818: snapshot_action_given = true;
18: 819: *snapshot_action = defGetBoolean(defel) ? CRS_EXPORT_SNAPSHOT :
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 820: CRS_NOEXPORT_SNAPSHOT;
-: 821: }
79: 822: else if (strcmp(defel->defname, "use_snapshot") == 0)
branch 0 taken 47% (fallthrough)
branch 1 taken 53%
-: 823: {
37: 824: if (snapshot_action_given || cmd->kind != REPLICATION_KIND_LOGICAL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 825: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 826: (errcode(ERRCODE_SYNTAX_ERROR),
-: 827: errmsg("conflicting or redundant options")));
-: 828:
37: 829: snapshot_action_given = true;
37: 830: *snapshot_action = CRS_USE_SNAPSHOT;
-: 831: }
42: 832: else if (strcmp(defel->defname, "reserve_wal") == 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 833: {
42: 834: if (reserve_wal_given || cmd->kind != REPLICATION_KIND_PHYSICAL)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 835: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 836: (errcode(ERRCODE_SYNTAX_ERROR),
-: 837: errmsg("conflicting or redundant options")));
-: 838:
42: 839: reserve_wal_given = true;
42: 840: *reserve_wal = true;
-: 841: }
-: 842: else
#####: 843: elog(ERROR, "unrecognized option: %s", defel->defname);
call 0 never executed
call 1 never executed
call 2 never executed
-: 844: }
98: 845:}
-: 846:
-: 847:/*
-: 848: * Create a new replication slot.
-: 849: */
-: 850:static void
function CreateReplicationSlot called 98 returned 99% blocks executed 59%
98: 851:CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
-: 852:{
98: 853: const char *snapshot_name = NULL;
-: 854: char xloc[MAXFNAMELEN];
-: 855: char *slot_name;
98: 856: bool reserve_wal = false;
98: 857: CRSSnapshotAction snapshot_action = CRS_EXPORT_SNAPSHOT;
-: 858: DestReceiver *dest;
-: 859: TupOutputState *tstate;
-: 860: TupleDesc tupdesc;
-: 861: Datum values[4];
-: 862: bool nulls[4];
-: 863:
98: 864: Assert(!MyReplicationSlot);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 865:
98: 866: parseCreateReplSlotOptions(cmd, &reserve_wal, &snapshot_action);
call 0 returned 100%
-: 867:
-: 868: /* setup state for XLogRead */
98: 869: sendTimeLineIsHistoric = false;
98: 870: sendTimeLine = ThisTimeLineID;
-: 871:
98: 872: if (cmd->kind == REPLICATION_KIND_PHYSICAL)
branch 0 taken 44% (fallthrough)
branch 1 taken 56%
-: 873: {
43: 874: ReplicationSlotCreate(cmd->slotname, false,
branch 0 taken 93% (fallthrough)
branch 1 taken 7%
call 2 returned 98%
43: 875: cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT);
-: 876: }
-: 877: else
-: 878: {
55: 879: CheckLogicalDecodingRequirements();
call 0 returned 100%
-: 880:
-: 881: /*
-: 882: * Initially create persistent slot as ephemeral - that allows us to
-: 883: * nicely handle errors during initialization because it'll get
-: 884: * dropped if this transaction fails. We'll make it persistent at the
-: 885: * end. Temporary slots can be created as temporary from beginning as
-: 886: * they get dropped on error as well.
-: 887: */
55: 888: ReplicationSlotCreate(cmd->slotname, true,
branch 0 taken 67% (fallthrough)
branch 1 taken 33%
call 2 returned 100%
55: 889: cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL);
-: 890: }
-: 891:
97: 892: if (cmd->kind == REPLICATION_KIND_LOGICAL)
branch 0 taken 57% (fallthrough)
branch 1 taken 43%
-: 893: {
-: 894: LogicalDecodingContext *ctx;
55: 895: bool need_full_snapshot = false;
-: 896:
-: 897: /*
-: 898: * Do options check early so that we can bail before calling the
-: 899: * DecodingContextFindStartpoint which can take long time.
-: 900: */
55: 901: if (snapshot_action == CRS_EXPORT_SNAPSHOT)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 902: {
#####: 903: if (IsTransactionBlock())
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 904: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 905: /*- translator: %s is a CREATE_REPLICATION_SLOT statement */
-: 906: (errmsg("%s must not be called inside a transaction",
-: 907: "CREATE_REPLICATION_SLOT ... EXPORT_SNAPSHOT")));
-: 908:
#####: 909: need_full_snapshot = true;
-: 910: }
55: 911: else if (snapshot_action == CRS_USE_SNAPSHOT)
branch 0 taken 67% (fallthrough)
branch 1 taken 33%
-: 912: {
37: 913: if (!IsTransactionBlock())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 914: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 915: /*- translator: %s is a CREATE_REPLICATION_SLOT statement */
-: 916: (errmsg("%s must be called inside a transaction",
-: 917: "CREATE_REPLICATION_SLOT ... USE_SNAPSHOT")));
-: 918:
37: 919: if (XactIsoLevel != XACT_REPEATABLE_READ)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 920: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 921: /*- translator: %s is a CREATE_REPLICATION_SLOT statement */
-: 922: (errmsg("%s must be called in REPEATABLE READ isolation mode transaction",
-: 923: "CREATE_REPLICATION_SLOT ... USE_SNAPSHOT")));
-: 924:
37: 925: if (FirstSnapshotSet)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 926: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 927: /*- translator: %s is a CREATE_REPLICATION_SLOT statement */
-: 928: (errmsg("%s must be called before any query",
-: 929: "CREATE_REPLICATION_SLOT ... USE_SNAPSHOT")));
-: 930:
37: 931: if (IsSubTransaction())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 932: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 933: /*- translator: %s is a CREATE_REPLICATION_SLOT statement */
-: 934: (errmsg("%s must not be called in a subtransaction",
-: 935: "CREATE_REPLICATION_SLOT ... USE_SNAPSHOT")));
-: 936:
37: 937: need_full_snapshot = true;
-: 938: }
-: 939:
55: 940: ctx = CreateInitDecodingContext(cmd->plugin, NIL, need_full_snapshot,
call 0 returned 100%
-: 941: InvalidXLogRecPtr,
-: 942: logical_read_xlog_page,
-: 943: WalSndPrepareWrite, WalSndWriteData,
-: 944: WalSndUpdateProgress);
-: 945:
-: 946: /*
-: 947: * Signal that we don't need the timeout mechanism. We're just
-: 948: * creating the replication slot and don't yet accept feedback
-: 949: * messages or send keepalives. As we possibly need to wait for
-: 950: * further WAL the walsender would otherwise possibly be killed too
-: 951: * soon.
-: 952: */
55: 953: last_reply_timestamp = 0;
-: 954:
-: 955: /* build initial snapshot, might take a while */
55: 956: DecodingContextFindStartpoint(ctx);
call 0 returned 100%
-: 957:
-: 958: /*
-: 959: * Export or use the snapshot if we've been asked to do so.
-: 960: *
-: 961: * NB. We will convert the snapbuild.c kind of snapshot to normal
-: 962: * snapshot when doing this.
-: 963: */
55: 964: if (snapshot_action == CRS_EXPORT_SNAPSHOT)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 965: {
#####: 966: snapshot_name = SnapBuildExportSnapshot(ctx->snapshot_builder);
call 0 never executed
-: 967: }
55: 968: else if (snapshot_action == CRS_USE_SNAPSHOT)
branch 0 taken 67% (fallthrough)
branch 1 taken 33%
-: 969: {
-: 970: Snapshot snap;
-: 971:
37: 972: snap = SnapBuildInitialSnapshot(ctx->snapshot_builder);
call 0 returned 100%
37: 973: RestoreTransactionSnapshot(snap, MyProc);
call 0 returned 100%
-: 974: }
-: 975:
-: 976: /* don't need the decoding context anymore */
55: 977: FreeDecodingContext(ctx);
call 0 returned 100%
-: 978:
55: 979: if (!cmd->temporary)
branch 0 taken 33% (fallthrough)
branch 1 taken 67%
18: 980: ReplicationSlotPersist();
call 0 returned 100%
-: 981: }
42: 982: else if (cmd->kind == REPLICATION_KIND_PHYSICAL && reserve_wal)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 98% (fallthrough)
branch 3 taken 2%
-: 983: {
41: 984: ReplicationSlotReserveWal();
call 0 returned 100%
-: 985:
41: 986: ReplicationSlotMarkDirty();
call 0 returned 100%
-: 987:
-: 988: /* Write this slot to disk if it's a permanent one. */
41: 989: if (!cmd->temporary)
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
1: 990: ReplicationSlotSave();
call 0 returned 100%
-: 991: }
-: 992:
194: 993: snprintf(xloc, sizeof(xloc), "%X/%X",
call 0 returned 100%
97: 994: (uint32) (MyReplicationSlot->data.confirmed_flush >> 32),
97: 995: (uint32) MyReplicationSlot->data.confirmed_flush);
-: 996:
97: 997: dest = CreateDestReceiver(DestRemoteSimple);
call 0 returned 100%
97: 998: MemSet(nulls, false, sizeof(nulls));
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
branch 4 never executed
branch 5 never executed
branch 6 never executed
branch 7 never executed
branch 8 never executed
branch 9 never executed
-: 999:
-: 1000: /*----------
-: 1001: * Need a tuple descriptor representing four columns:
-: 1002: * - first field: the slot name
-: 1003: * - second field: LSN at which we became consistent
-: 1004: * - third field: exported snapshot's name
-: 1005: * - fourth field: output plugin
-: 1006: *----------
-: 1007: */
97: 1008: tupdesc = CreateTemplateTupleDesc(4);
call 0 returned 100%
97: 1009: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "slot_name",
call 0 returned 100%
-: 1010: TEXTOID, -1, 0);
97: 1011: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "consistent_point",
call 0 returned 100%
-: 1012: TEXTOID, -1, 0);
97: 1013: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "snapshot_name",
call 0 returned 100%
-: 1014: TEXTOID, -1, 0);
97: 1015: TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 4, "output_plugin",
call 0 returned 100%
-: 1016: TEXTOID, -1, 0);
-: 1017:
-: 1018: /* prepare for projection of tuples */
97: 1019: tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
call 0 returned 100%
-: 1020:
-: 1021: /* slot_name */
97: 1022: slot_name = NameStr(MyReplicationSlot->data.name);
97: 1023: values[0] = CStringGetTextDatum(slot_name);
call 0 returned 100%
-: 1024:
-: 1025: /* consistent wal location */
97: 1026: values[1] = CStringGetTextDatum(xloc);
call 0 returned 100%
-: 1027:
-: 1028: /* snapshot name, or NULL if none */
97: 1029: if (snapshot_name != NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1030: values[2] = CStringGetTextDatum(snapshot_name);
call 0 never executed
-: 1031: else
97: 1032: nulls[2] = true;
-: 1033:
-: 1034: /* plugin, or NULL if none */
97: 1035: if (cmd->plugin != NULL)
branch 0 taken 57% (fallthrough)
branch 1 taken 43%
55: 1036: values[3] = CStringGetTextDatum(cmd->plugin);
call 0 returned 100%
-: 1037: else
42: 1038: nulls[3] = true;
-: 1039:
-: 1040: /* send it to dest */
97: 1041: do_tup_output(tstate, values, nulls);
call 0 returned 100%
97: 1042: end_tup_output(tstate);
call 0 returned 100%
-: 1043:
97: 1044: ReplicationSlotRelease();
call 0 returned 100%
97: 1045:}
-: 1046:
-: 1047:/*
-: 1048: * Get rid of a replication slot that is no longer wanted.
-: 1049: */
-: 1050:static void
function DropReplicationSlot called 7 returned 100% blocks executed 100%
7: 1051:DropReplicationSlot(DropReplicationSlotCmd *cmd)
-: 1052:{
7: 1053: ReplicationSlotDrop(cmd->slotname, !cmd->wait);
call 0 returned 100%
7: 1054: EndCommand("DROP_REPLICATION_SLOT", DestRemote);
call 0 returned 100%
7: 1055:}
-: 1056:
-: 1057:/*
-: 1058: * Load previously initiated logical slot and prepare for sending data (via
-: 1059: * WalSndLoop).
-: 1060: */
-: 1061:static void
function StartLogicalReplication called 27 returned 15% blocks executed 68%
27: 1062:StartLogicalReplication(StartReplicationCmd *cmd)
-: 1063:{
-: 1064: StringInfoData buf;
-: 1065:
-: 1066: /* make sure that our requirements are still fulfilled */
27: 1067: CheckLogicalDecodingRequirements();
call 0 returned 100%
-: 1068:
27: 1069: Assert(!MyReplicationSlot);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1070:
27: 1071: ReplicationSlotAcquire(cmd->slotname, true);
call 0 returned 100%
-: 1072:
-: 1073: /*
-: 1074: * Force a disconnect, so that the decoding code doesn't need to care
-: 1075: * about an eventual switch from running in recovery, to running in a
-: 1076: * normal environment. Client code is expected to handle reconnects.
-: 1077: */
27: 1078: if (am_cascading_walsender && !RecoveryInProgress())
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
branch 3 never executed
branch 4 never executed
-: 1079: {
#####: 1080: ereport(LOG,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 1081: (errmsg("terminating walsender process after promotion")));
#####: 1082: got_STOPPING = true;
-: 1083: }
-: 1084:
-: 1085: /*
-: 1086: * Create our decoding context, making it start at the previously ack'ed
-: 1087: * position.
-: 1088: *
-: 1089: * Do this before sending a CopyBothResponse message, so that any errors
-: 1090: * are reported early.
-: 1091: */
27: 1092: logical_decoding_ctx =
27: 1093: CreateDecodingContext(cmd->startpoint, cmd->options, false,
call 0 returned 100%
-: 1094: logical_read_xlog_page,
-: 1095: WalSndPrepareWrite, WalSndWriteData,
-: 1096: WalSndUpdateProgress);
-: 1097:
-: 1098:
27: 1099: WalSndSetState(WALSNDSTATE_CATCHUP);
call 0 returned 100%
-: 1100:
-: 1101: /* Send a CopyBothResponse message, and start streaming */
27: 1102: pq_beginmessage(&buf, 'W');
call 0 returned 100%
27: 1103: pq_sendbyte(&buf, 0);
call 0 returned 100%
27: 1104: pq_sendint16(&buf, 0);
call 0 returned 100%
27: 1105: pq_endmessage(&buf);
call 0 returned 100%
27: 1106: pq_flush();
call 0 returned 100%
-: 1107:
-: 1108:
-: 1109: /* Start reading WAL from the oldest required WAL. */
27: 1110: logical_startptr = MyReplicationSlot->data.restart_lsn;
-: 1111:
-: 1112: /*
-: 1113: * Report the location after which we'll send out further commits as the
-: 1114: * current sentPtr.
-: 1115: */
27: 1116: sentPtr = MyReplicationSlot->data.confirmed_flush;
-: 1117:
-: 1118: /* Also update the sent position status in shared memory */
27: 1119: SpinLockAcquire(&MyWalSnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
27: 1120: MyWalSnd->sentPtr = MyReplicationSlot->data.restart_lsn;
27: 1121: SpinLockRelease(&MyWalSnd->mutex);
call 0 returned 100%
-: 1122:
27: 1123: replication_active = true;
-: 1124:
27: 1125: SyncRepInitConfig();
call 0 returned 100%
-: 1126:
-: 1127: /* Main loop of walsender */
27: 1128: WalSndLoop(XLogSendLogical);
call 0 returned 15%
-: 1129:
4: 1130: FreeDecodingContext(logical_decoding_ctx);
call 0 returned 100%
4: 1131: ReplicationSlotRelease();
call 0 returned 100%
-: 1132:
4: 1133: replication_active = false;
4: 1134: if (got_STOPPING)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1135: proc_exit(0);
call 0 never executed
4: 1136: WalSndSetState(WALSNDSTATE_STARTUP);
call 0 returned 100%
-: 1137:
-: 1138: /* Get out of COPY mode (CommandComplete). */
4: 1139: EndCommand("COPY 0", DestRemote);
call 0 returned 100%
4: 1140:}
-: 1141:
-: 1142:/*
-: 1143: * LogicalDecodingContext 'prepare_write' callback.
-: 1144: *
-: 1145: * Prepare a write into a StringInfo.
-: 1146: *
-: 1147: * Don't do anything lasting in here, it's quite possible that nothing will be done
-: 1148: * with the data.
-: 1149: */
-: 1150:static void
function WalSndPrepareWrite called 969 returned 100% blocks executed 100%
969: 1151:WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write)
-: 1152:{
-: 1153: /* can't have sync rep confused by sending the same LSN several times */
969: 1154: if (!last_write)
branch 0 taken 6% (fallthrough)
branch 1 taken 94%
55: 1155: lsn = InvalidXLogRecPtr;
-: 1156:
969: 1157: resetStringInfo(ctx->out);
call 0 returned 100%
-: 1158:
969: 1159: pq_sendbyte(ctx->out, 'w');
call 0 returned 100%
969: 1160: pq_sendint64(ctx->out, lsn); /* dataStart */
call 0 returned 100%
969: 1161: pq_sendint64(ctx->out, lsn); /* walEnd */
call 0 returned 100%
-: 1162:
-: 1163: /*
-: 1164: * Fill out the sendtime later, just as it's done in XLogSendPhysical, but
-: 1165: * reserve space here.
-: 1166: */
969: 1167: pq_sendint64(ctx->out, 0); /* sendtime */
call 0 returned 100%
969: 1168:}
-: 1169:
-: 1170:/*
-: 1171: * LogicalDecodingContext 'write' callback.
-: 1172: *
-: 1173: * Actually write out data previously prepared by WalSndPrepareWrite out to
-: 1174: * the network. Take as long as needed, but process replies from the other
-: 1175: * side and check timeouts during that.
-: 1176: */
-: 1177:static void
function WalSndWriteData called 969 returned 100% blocks executed 35%
969: 1178:WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid,
-: 1179: bool last_write)
-: 1180:{
-: 1181: TimestampTz now;
-: 1182:
-: 1183: /* output previously gathered data in a CopyData packet */
969: 1184: pq_putmessage_noblock('d', ctx->out->data, ctx->out->len);
call 0 returned 100%
-: 1185:
-: 1186: /*
-: 1187: * Fill the send timestamp last, so that it is taken as late as possible.
-: 1188: * This is somewhat ugly, but the protocol is set as it's already used for
-: 1189: * several releases by streaming physical replication.
-: 1190: */
969: 1191: resetStringInfo(&tmpbuf);
call 0 returned 100%
969: 1192: now = GetCurrentTimestamp();
call 0 returned 100%
969: 1193: pq_sendint64(&tmpbuf, now);
call 0 returned 100%
969: 1194: memcpy(&ctx->out->data[1 + sizeof(int64) + sizeof(int64)],
969: 1195: tmpbuf.data, sizeof(int64));
-: 1196:
969: 1197: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1198:
-: 1199: /* Try to flush pending output to the client */
969: 1200: if (pq_flush_if_writable() != 0)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 1201: WalSndShutdown();
call 0 never executed
-: 1202:
-: 1203: /* Try taking fast path unless we get too close to walsender timeout. */
969: 1204: if (now < TimestampTzPlusMilliseconds(last_reply_timestamp,
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
969: 1205: wal_sender_timeout / 2) &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
969: 1206: !pq_is_send_pending())
call 0 returned 100%
-: 1207: {
1938: 1208: return;
-: 1209: }
-: 1210:
-: 1211: /* If we have pending write here, go to slow path */
-: 1212: for (;;)
-: 1213: {
-: 1214: int wakeEvents;
-: 1215: long sleeptime;
-: 1216:
-: 1217: /* Check for input from the client */
#####: 1218: ProcessRepliesIfAny();
call 0 never executed
-: 1219:
-: 1220: /* die if timeout was reached */
#####: 1221: WalSndCheckTimeOut();
call 0 never executed
-: 1222:
-: 1223: /* Send keepalive if the time has come */
#####: 1224: WalSndKeepaliveIfNecessary();
call 0 never executed
-: 1225:
#####: 1226: if (!pq_is_send_pending())
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 1227: break;
-: 1228:
#####: 1229: sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
call 0 never executed
call 1 never executed
-: 1230:
#####: 1231: wakeEvents = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH |
-: 1232: WL_SOCKET_WRITEABLE | WL_SOCKET_READABLE | WL_TIMEOUT;
-: 1233:
-: 1234: /* Sleep until something happens or we time out */
#####: 1235: (void) WaitLatchOrSocket(MyLatch, wakeEvents,
call 0 never executed
#####: 1236: MyProcPort->sock, sleeptime,
-: 1237: WAIT_EVENT_WAL_SENDER_WRITE_DATA);
-: 1238:
-: 1239: /* Clear any already-pending wakeups */
#####: 1240: ResetLatch(MyLatch);
call 0 never executed
-: 1241:
#####: 1242: CHECK_FOR_INTERRUPTS();
branch 0 never executed
branch 1 never executed
call 2 never executed
-: 1243:
-: 1244: /* Process any requests or signals received recently */
#####: 1245: if (ConfigReloadPending)
branch 0 never executed
branch 1 never executed
-: 1246: {
#####: 1247: ConfigReloadPending = false;
#####: 1248: ProcessConfigFile(PGC_SIGHUP);
call 0 never executed
#####: 1249: SyncRepInitConfig();
call 0 never executed
-: 1250: }
-: 1251:
-: 1252: /* Try to flush pending output to the client */
#####: 1253: if (pq_flush_if_writable() != 0)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 1254: WalSndShutdown();
call 0 never executed
#####: 1255: }
-: 1256:
-: 1257: /* reactivate latch so WalSndLoop knows to continue */
#####: 1258: SetLatch(MyLatch);
call 0 never executed
-: 1259:}
-: 1260:
-: 1261:/*
-: 1262: * LogicalDecodingContext 'update_progress' callback.
-: 1263: *
-: 1264: * Write the current position to the lag tracker (see XLogSendPhysical).
-: 1265: */
-: 1266:static void
function WalSndUpdateProgress called 117 returned 100% blocks executed 100%
117: 1267:WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid)
-: 1268:{
-: 1269: static TimestampTz sendTime = 0;
117: 1270: TimestampTz now = GetCurrentTimestamp();
call 0 returned 100%
-: 1271:
-: 1272: /*
-: 1273: * Track lag no more than once per WALSND_LOGICAL_LAG_TRACK_INTERVAL_MS to
-: 1274: * avoid flooding the lag tracker when we commit frequently.
-: 1275: */
-: 1276:#define WALSND_LOGICAL_LAG_TRACK_INTERVAL_MS 1000
117: 1277: if (!TimestampDifferenceExceeds(sendTime, now,
call 0 returned 100%
branch 1 taken 87% (fallthrough)
branch 2 taken 13%
-: 1278: WALSND_LOGICAL_LAG_TRACK_INTERVAL_MS))
219: 1279: return;
-: 1280:
15: 1281: LagTrackerWrite(lsn, now);
call 0 returned 100%
15: 1282: sendTime = now;
-: 1283:}
-: 1284:
-: 1285:/*
-: 1286: * Wait till WAL < loc is flushed to disk so it can be safely sent to client.
-: 1287: *
-: 1288: * Returns end LSN of flushed WAL. Normally this will be >= loc, but
-: 1289: * if we detect a shutdown request (either from postmaster or client)
-: 1290: * we will return early, so caller must always check.
-: 1291: */
-: 1292:static XLogRecPtr
function WalSndWaitForWal called 14792 returned 99% blocks executed 83%
14792: 1293:WalSndWaitForWal(XLogRecPtr loc)
-: 1294:{
-: 1295: int wakeEvents;
-: 1296: static XLogRecPtr RecentFlushPtr = InvalidXLogRecPtr;
-: 1297:
-: 1298: /*
-: 1299: * Fast path to avoid acquiring the spinlock in case we already know we
-: 1300: * have enough WAL available. This is particularly interesting if we're
-: 1301: * far behind.
-: 1302: */
29506: 1303: if (RecentFlushPtr != InvalidXLogRecPtr &&
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
branch 2 taken 50% (fallthrough)
branch 3 taken 50%
14714: 1304: loc <= RecentFlushPtr)
7379: 1305: return RecentFlushPtr;
-: 1306:
-: 1307: /* Get a more recent flush pointer. */
7413: 1308: if (!RecoveryInProgress())
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
7413: 1309: RecentFlushPtr = GetFlushRecPtr();
call 0 returned 100%
-: 1310: else
#####: 1311: RecentFlushPtr = GetXLogReplayRecPtr(NULL);
call 0 never executed
-: 1312:
-: 1313: for (;;)
-: 1314: {
-: 1315: long sleeptime;
-: 1316:
-: 1317: /* Clear any already-pending wakeups */
7601: 1318: ResetLatch(MyLatch);
call 0 returned 100%
-: 1319:
7601: 1320: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1321:
-: 1322: /* Process any requests or signals received recently */
7601: 1323: if (ConfigReloadPending)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1324: {
#####: 1325: ConfigReloadPending = false;
#####: 1326: ProcessConfigFile(PGC_SIGHUP);
call 0 never executed
#####: 1327: SyncRepInitConfig();
call 0 never executed
-: 1328: }
-: 1329:
-: 1330: /* Check for input from the client */
7601: 1331: ProcessRepliesIfAny();
call 0 returned 99%
-: 1332:
-: 1333: /*
-: 1334: * If we're shutting down, trigger pending WAL to be written out,
-: 1335: * otherwise we'd possibly end up waiting for WAL that never gets
-: 1336: * written, because walwriter has shut down already.
-: 1337: */
7585: 1338: if (got_STOPPING)
branch 0 taken 94% (fallthrough)
branch 1 taken 6%
7160: 1339: XLogBackgroundFlush();
call 0 returned 100%
-: 1340:
-: 1341: /* Update our idea of the currently flushed position. */
7585: 1342: if (!RecoveryInProgress())
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
7585: 1343: RecentFlushPtr = GetFlushRecPtr();
call 0 returned 100%
-: 1344: else
#####: 1345: RecentFlushPtr = GetXLogReplayRecPtr(NULL);
call 0 never executed
-: 1346:
-: 1347: /*
-: 1348: * If postmaster asked us to stop, don't wait anymore.
-: 1349: *
-: 1350: * It's important to do this check after the recomputation of
-: 1351: * RecentFlushPtr, so we can send all remaining data before shutting
-: 1352: * down.
-: 1353: */
7585: 1354: if (got_STOPPING)
branch 0 taken 94% (fallthrough)
branch 1 taken 6%
7160: 1355: break;
-: 1356:
-: 1357: /*
-: 1358: * We only send regular messages to the client for full decoded
-: 1359: * transactions, but a synchronous replication and walsender shutdown
-: 1360: * possibly are waiting for a later location. So we send pings
-: 1361: * containing the flush location every now and then.
-: 1362: */
665: 1363: if (MyWalSnd->flush < sentPtr &&
branch 0 taken 56% (fallthrough)
branch 1 taken 44%
branch 2 taken 55% (fallthrough)
branch 3 taken 45%
372: 1364: MyWalSnd->write < sentPtr &&
branch 0 taken 70% (fallthrough)
branch 1 taken 30%
132: 1365: !waiting_for_ping_response)
-: 1366: {
92: 1367: WalSndKeepalive(false);
call 0 returned 100%
92: 1368: waiting_for_ping_response = true;
-: 1369: }
-: 1370:
-: 1371: /* check whether we're done */
425: 1372: if (loc <= RecentFlushPtr)
branch 0 taken 55% (fallthrough)
branch 1 taken 45%
233: 1373: break;
-: 1374:
-: 1375: /* Waiting for new WAL. Since we need to wait, we're now caught up. */
192: 1376: WalSndCaughtUp = true;
-: 1377:
-: 1378: /*
-: 1379: * Try to flush any pending output to the client.
-: 1380: */
192: 1381: if (pq_flush_if_writable() != 0)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 1382: WalSndShutdown();
call 0 never executed
-: 1383:
-: 1384: /*
-: 1385: * If we have received CopyDone from the client, sent CopyDone
-: 1386: * ourselves, and the output buffer is empty, it's time to exit
-: 1387: * streaming, so fail the current WAL fetch request.
-: 1388: */
196: 1389: if (streamingDoneReceiving && streamingDoneSending &&
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 100% (fallthrough)
branch 5 taken 0%
4: 1390: !pq_is_send_pending())
call 0 returned 100%
4: 1391: break;
-: 1392:
-: 1393: /* die if timeout was reached */
188: 1394: WalSndCheckTimeOut();
call 0 returned 100%
-: 1395:
-: 1396: /* Send keepalive if the time has come */
188: 1397: WalSndKeepaliveIfNecessary();
call 0 returned 100%
-: 1398:
-: 1399: /*
-: 1400: * Sleep until something happens or we time out. Also wait for the
-: 1401: * socket becoming writable, if there's still pending output.
-: 1402: * Otherwise we might sit on sendable output data while waiting for
-: 1403: * new WAL to be generated. (But if we have nothing to send, we don't
-: 1404: * want to wake on socket-writable.)
-: 1405: */
188: 1406: sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
call 0 returned 100%
call 1 returned 100%
-: 1407:
188: 1408: wakeEvents = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH |
-: 1409: WL_SOCKET_READABLE | WL_TIMEOUT;
-: 1410:
188: 1411: if (pq_is_send_pending())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 1412: wakeEvents |= WL_SOCKET_WRITEABLE;
-: 1413:
188: 1414: (void) WaitLatchOrSocket(MyLatch, wakeEvents,
call 0 returned 100%
188: 1415: MyProcPort->sock, sleeptime,
-: 1416: WAIT_EVENT_WAL_SENDER_WAIT_WAL);
188: 1417: }
-: 1418:
-: 1419: /* reactivate latch so WalSndLoop knows to continue */
7397: 1420: SetLatch(MyLatch);
call 0 returned 100%
7397: 1421: return RecentFlushPtr;
-: 1422:}
-: 1423:
-: 1424:/*
-: 1425: * Execute an incoming replication command.
-: 1426: *
-: 1427: * Returns true if the cmd_string was recognized as WalSender command, false
-: 1428: * if not.
-: 1429: */
-: 1430:bool
function exec_replication_command called 809 returned 91% blocks executed 63%
809: 1431:exec_replication_command(const char *cmd_string)
-: 1432:{
-: 1433: int parse_rc;
-: 1434: Node *cmd_node;
-: 1435: MemoryContext cmd_context;
-: 1436: MemoryContext old_context;
-: 1437:
-: 1438: /*
-: 1439: * If WAL sender has been told that shutdown is getting close, switch its
-: 1440: * status accordingly to handle the next replication commands correctly.
-: 1441: */
809: 1442: if (got_STOPPING)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1443: WalSndSetState(WALSNDSTATE_STOPPING);
call 0 never executed
-: 1444:
-: 1445: /*
-: 1446: * Throw error if in stopping mode. We need prevent commands that could
-: 1447: * generate WAL while the shutdown checkpoint is being written. To be
-: 1448: * safe, we just prohibit all new commands.
-: 1449: */
809: 1450: if (MyWalSnd->state == WALSNDSTATE_STOPPING)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1451: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 1452: (errmsg("cannot execute new commands while WAL sender is in stopping mode")));
-: 1453:
-: 1454: /*
-: 1455: * CREATE_REPLICATION_SLOT ... LOGICAL exports a snapshot until the next
-: 1456: * command arrives. Clean up the old stuff if there's anything.
-: 1457: */
809: 1458: SnapBuildClearExportedSnapshot();
call 0 returned 100%
-: 1459:
809: 1460: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1461:
809: 1462: cmd_context = AllocSetContextCreate(CurrentMemoryContext,
call 0 returned 100%
-: 1463: "Replication command context",
-: 1464: ALLOCSET_DEFAULT_SIZES);
809: 1465: old_context = MemoryContextSwitchTo(cmd_context);
call 0 returned 100%
-: 1466:
809: 1467: replication_scanner_init(cmd_string);
call 0 returned 100%
809: 1468: parse_rc = replication_yyparse();
call 0 returned 100%
809: 1469: if (parse_rc != 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1470: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 1471: (errcode(ERRCODE_SYNTAX_ERROR),
-: 1472: (errmsg_internal("replication command parser returned %d",
-: 1473: parse_rc))));
-: 1474:
809: 1475: cmd_node = replication_parse_result;
-: 1476:
-: 1477: /*
-: 1478: * Log replication command if log_replication_commands is enabled. Even
-: 1479: * when it's disabled, log the command with DEBUG1 level for backward
-: 1480: * compatibility. Note that SQL commands are not logged here, and will be
-: 1481: * logged later if log_statement is enabled.
-: 1482: */
809: 1483: if (cmd_node->type != T_SQLCmd)
branch 0 taken 74% (fallthrough)
branch 1 taken 26%
601: 1484: ereport(log_replication_commands ? LOG : DEBUG1,
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
call 2 returned 100%
branch 3 taken 100% (fallthrough)
branch 4 taken 0%
call 5 returned 100%
call 6 returned 100%
-: 1485: (errmsg("received replication command: %s", cmd_string)));
-: 1486:
-: 1487: /*
-: 1488: * CREATE_REPLICATION_SLOT ... LOGICAL exports a snapshot. If it was
-: 1489: * called outside of transaction the snapshot should be cleared here.
-: 1490: */
809: 1491: if (!IsTransactionBlock())
call 0 returned 100%
branch 1 taken 77% (fallthrough)
branch 2 taken 23%
626: 1492: SnapBuildClearExportedSnapshot();
call 0 returned 100%
-: 1493:
-: 1494: /*
-: 1495: * For aborted transactions, don't allow anything except pure SQL, the
-: 1496: * exec_simple_query() will handle it correctly.
-: 1497: */
809: 1498: if (IsAbortedTransactionBlockState() && !IsA(cmd_node, SQLCmd))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
branch 3 never executed
branch 4 never executed
#####: 1499: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 1500: (errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
-: 1501: errmsg("current transaction is aborted, "
-: 1502: "commands ignored until end of transaction block")));
-: 1503:
809: 1504: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 1505:
-: 1506: /*
-: 1507: * Allocate buffers that will be used for each outgoing and incoming
-: 1508: * message. We do this just once per command to reduce palloc overhead.
-: 1509: */
809: 1510: initStringInfo(&output_message);
call 0 returned 100%
809: 1511: initStringInfo(&reply_message);
call 0 returned 100%
809: 1512: initStringInfo(&tmpbuf);
call 0 returned 100%
-: 1513:
-: 1514: /* Report to pgstat that this process is running */
809: 1515: pgstat_report_activity(STATE_RUNNING, NULL);
call 0 returned 100%
-: 1516:
809: 1517: switch (cmd_node->type)
branch 0 taken 20%
branch 1 taken 6%
branch 2 taken 12%
branch 3 taken 1%
branch 4 taken 14%
branch 5 taken 1%
branch 6 taken 20%
branch 7 taken 26%
branch 8 taken 0%
-: 1518: {
-: 1519: case T_IdentifySystemCmd:
165: 1520: IdentifySystem();
call 0 returned 100%
165: 1521: break;
-: 1522:
-: 1523: case T_BaseBackupCmd:
49: 1524: PreventInTransactionBlock(true, "BASE_BACKUP");
call 0 returned 100%
49: 1525: SendBaseBackup((BaseBackupCmd *) cmd_node);
call 0 returned 88%
43: 1526: break;
-: 1527:
-: 1528: case T_CreateReplicationSlotCmd:
98: 1529: CreateReplicationSlot((CreateReplicationSlotCmd *) cmd_node);
call 0 returned 99%
97: 1530: break;
-: 1531:
-: 1532: case T_DropReplicationSlotCmd:
7: 1533: DropReplicationSlot((DropReplicationSlotCmd *) cmd_node);
call 0 returned 100%
7: 1534: break;
-: 1535:
-: 1536: case T_StartReplicationCmd:
-: 1537: {
116: 1538: StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
-: 1539:
116: 1540: PreventInTransactionBlock(true, "START_REPLICATION");
call 0 returned 100%
-: 1541:
116: 1542: if (cmd->kind == REPLICATION_KIND_PHYSICAL)
branch 0 taken 77% (fallthrough)
branch 1 taken 23%
89: 1543: StartReplication(cmd);
call 0 returned 52%
-: 1544: else
27: 1545: StartLogicalReplication(cmd);
call 0 returned 15%
50: 1546: break;
-: 1547: }
-: 1548:
-: 1549: case T_TimeLineHistoryCmd:
5: 1550: PreventInTransactionBlock(true, "TIMELINE_HISTORY");
call 0 returned 100%
5: 1551: SendTimeLineHistory((TimeLineHistoryCmd *) cmd_node);
call 0 returned 100%
5: 1552: break;
-: 1553:
-: 1554: case T_VariableShowStmt:
-: 1555: {
161: 1556: DestReceiver *dest = CreateDestReceiver(DestRemoteSimple);
call 0 returned 100%
161: 1557: VariableShowStmt *n = (VariableShowStmt *) cmd_node;
-: 1558:
-: 1559: /* syscache access needs a transaction environment */
161: 1560: StartTransactionCommand();
call 0 returned 100%
161: 1561: GetPGVariable(n->name, dest);
call 0 returned 100%
161: 1562: CommitTransactionCommand();
call 0 returned 100%
-: 1563: }
161: 1564: break;
-: 1565:
-: 1566: case T_SQLCmd:
208: 1567: if (MyDatabaseId == InvalidOid)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1568: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 1569: (errmsg("cannot execute SQL commands in WAL sender for physical replication")));
-: 1570:
-: 1571: /* Report to pgstat that this process is now idle */
208: 1572: pgstat_report_activity(STATE_IDLE, NULL);
call 0 returned 100%
-: 1573:
-: 1574: /* Tell the caller that this wasn't a WalSender command. */
208: 1575: return false;
-: 1576:
-: 1577: default:
#####: 1578: elog(ERROR, "unrecognized replication command node tag: %u",
call 0 never executed
call 1 never executed
call 2 never executed
-: 1579: cmd_node->type);
-: 1580: }
-: 1581:
-: 1582: /* done */
528: 1583: MemoryContextSwitchTo(old_context);
call 0 returned 100%
528: 1584: MemoryContextDelete(cmd_context);
call 0 returned 100%
-: 1585:
-: 1586: /* Send CommandComplete message */
528: 1587: EndCommand("SELECT", DestRemote);
call 0 returned 100%
-: 1588:
-: 1589: /* Report to pgstat that this process is now idle */
528: 1590: pgstat_report_activity(STATE_IDLE, NULL);
call 0 returned 100%
-: 1591:
528: 1592: return true;
-: 1593:}
-: 1594:
-: 1595:/*
-: 1596: * Process any incoming messages while streaming. Also checks if the remote
-: 1597: * end has closed the connection.
-: 1598: */
-: 1599:static void
function ProcessRepliesIfAny called 24971 returned 99% blocks executed 60%
24971: 1600:ProcessRepliesIfAny(void)
-: 1601:{
-: 1602: unsigned char firstchar;
-: 1603: int r;
24971: 1604: bool received = false;
-: 1605:
24971: 1606: last_processing = GetCurrentTimestamp();
call 0 returned 100%
-: 1607:
-: 1608: for (;;)
-: 1609: {
26397: 1610: pq_startmsgread();
call 0 returned 100%
26397: 1611: r = pq_getbyte_if_available(&firstchar);
call 0 returned 100%
26397: 1612: if (r < 0)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 1613: {
-: 1614: /* unexpected error or EOF */
11: 1615: ereport(COMMERROR,
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
call 3 returned 100%
call 4 returned 100%
call 5 returned 100%
-: 1616: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 1617: errmsg("unexpected EOF on standby connection")));
11: 1618: proc_exit(0);
call 0 returned 0%
-: 1619: }
26386: 1620: if (r == 0)
branch 0 taken 94% (fallthrough)
branch 1 taken 6%
-: 1621: {
-: 1622: /* no data available without blocking */
24922: 1623: pq_endmsgread();
call 0 returned 100%
24922: 1624: break;
-: 1625: }
-: 1626:
-: 1627: /* Read the message contents */
1464: 1628: resetStringInfo(&reply_message);
call 0 returned 100%
1464: 1629: if (pq_getmessage(&reply_message, 0))
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 1630: {
#####: 1631: ereport(COMMERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 1632: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 1633: errmsg("unexpected EOF on standby connection")));
#####: 1634: proc_exit(0);
call 0 never executed
-: 1635: }
-: 1636:
-: 1637: /*
-: 1638: * If we already received a CopyDone from the frontend, the frontend
-: 1639: * should not send us anything until we've closed our end of the COPY.
-: 1640: * XXX: In theory, the frontend could already send the next command
-: 1641: * before receiving the CopyDone, but libpq doesn't currently allow
-: 1642: * that.
-: 1643: */
1464: 1644: if (streamingDoneReceiving && firstchar != 'X')
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 1645: ereport(FATAL,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 1646: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 1647: errmsg("unexpected standby message type \"%c\", after receiving CopyDone",
-: 1648: firstchar)));
-: 1649:
-: 1650: /* Handle the very limited subset of commands expected in this phase */
1464: 1651: switch (firstchar)
branch 0 taken 94%
branch 1 taken 4%
branch 2 taken 3%
branch 3 taken 0%
-: 1652: {
-: 1653: /*
-: 1654: * 'd' means a standby reply wrapped in a CopyData packet.
-: 1655: */
-: 1656: case 'd':
1373: 1657: ProcessStandbyMessage();
call 0 returned 100%
1373: 1658: received = true;
1373: 1659: break;
-: 1660:
-: 1661: /*
-: 1662: * CopyDone means the standby requested to finish streaming.
-: 1663: * Reply with CopyDone, if we had not sent that already.
-: 1664: */
-: 1665: case 'c':
53: 1666: if (!streamingDoneSending)
branch 0 taken 91% (fallthrough)
branch 1 taken 9%
-: 1667: {
48: 1668: pq_putmessage_noblock('c', NULL, 0);
call 0 returned 100%
48: 1669: streamingDoneSending = true;
-: 1670: }
-: 1671:
53: 1672: streamingDoneReceiving = true;
53: 1673: received = true;
53: 1674: break;
-: 1675:
-: 1676: /*
-: 1677: * 'X' means that the standby is closing down the socket.
-: 1678: */
-: 1679: case 'X':
38: 1680: proc_exit(0);
call 0 returned 0%
-: 1681:
-: 1682: default:
#####: 1683: ereport(FATAL,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 1684: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 1685: errmsg("invalid standby message type \"%c\"",
-: 1686: firstchar)));
-: 1687: }
1426: 1688: }
-: 1689:
-: 1690: /*
-: 1691: * Save the last reply timestamp if we've received at least one reply.
-: 1692: */
24922: 1693: if (received)
branch 0 taken 5% (fallthrough)
branch 1 taken 95%
-: 1694: {
1225: 1695: last_reply_timestamp = last_processing;
1225: 1696: waiting_for_ping_response = false;
-: 1697: }
24922: 1698:}
-: 1699:
-: 1700:/*
-: 1701: * Process a status update message received from standby.
-: 1702: */
-: 1703:static void
function ProcessStandbyMessage called 1373 returned 100% blocks executed 50%
1373: 1704:ProcessStandbyMessage(void)
-: 1705:{
-: 1706: char msgtype;
-: 1707:
-: 1708: /*
-: 1709: * Check message type from the first byte.
-: 1710: */
1373: 1711: msgtype = pq_getmsgbyte(&reply_message);
call 0 returned 100%
-: 1712:
1373: 1713: switch (msgtype)
branch 0 taken 97%
branch 1 taken 3%
branch 2 taken 0%
-: 1714: {
-: 1715: case 'r':
1337: 1716: ProcessStandbyReplyMessage();
call 0 returned 100%
1337: 1717: break;
-: 1718:
-: 1719: case 'h':
36: 1720: ProcessStandbyHSFeedbackMessage();
call 0 returned 100%
36: 1721: break;
-: 1722:
-: 1723: default:
#####: 1724: ereport(COMMERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
-: 1725: (errcode(ERRCODE_PROTOCOL_VIOLATION),
-: 1726: errmsg("unexpected message type \"%c\"", msgtype)));
#####: 1727: proc_exit(0);
call 0 never executed
-: 1728: }
1373: 1729:}
-: 1730:
-: 1731:/*
-: 1732: * Remember that a walreceiver just confirmed receipt of lsn `lsn`.
-: 1733: */
-: 1734:static void
function PhysicalConfirmReceivedLocation called 43 returned 100% blocks executed 85%
43: 1735:PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
-: 1736:{
43: 1737: bool changed = false;
43: 1738: ReplicationSlot *slot = MyReplicationSlot;
-: 1739:
43: 1740: Assert(lsn != InvalidXLogRecPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
43: 1741: SpinLockAcquire(&slot->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
43: 1742: if (slot->data.restart_lsn != lsn)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1743: {
43: 1744: changed = true;
43: 1745: slot->data.restart_lsn = lsn;
-: 1746: }
43: 1747: SpinLockRelease(&slot->mutex);
call 0 returned 100%
-: 1748:
43: 1749: if (changed)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 1750: {
43: 1751: ReplicationSlotMarkDirty();
call 0 returned 100%
43: 1752: ReplicationSlotsComputeRequiredLSN();
call 0 returned 100%
-: 1753: }
-: 1754:
-: 1755: /*
-: 1756: * One could argue that the slot should be saved to disk now, but that'd
-: 1757: * be energy wasted - the worst lost information can do here is give us
-: 1758: * wrong information in a statistics view - we'll just potentially be more
-: 1759: * conservative in removing files.
-: 1760: */
43: 1761:}
-: 1762:
-: 1763:/*
-: 1764: * Regular reply from standby advising of WAL locations on standby server.
-: 1765: */
-: 1766:static void
function ProcessStandbyReplyMessage called 1337 returned 100% blocks executed 79%
1337: 1767:ProcessStandbyReplyMessage(void)
-: 1768:{
-: 1769: XLogRecPtr writePtr,
-: 1770: flushPtr,
-: 1771: applyPtr;
-: 1772: bool replyRequested;
-: 1773: TimeOffset writeLag,
-: 1774: flushLag,
-: 1775: applyLag;
-: 1776: bool clearLagTimes;
-: 1777: TimestampTz now;
-: 1778: TimestampTz replyTime;
-: 1779:
-: 1780: static bool fullyAppliedLastTime = false;
-: 1781:
-: 1782: /* the caller already consumed the msgtype byte */
1337: 1783: writePtr = pq_getmsgint64(&reply_message);
call 0 returned 100%
1337: 1784: flushPtr = pq_getmsgint64(&reply_message);
call 0 returned 100%
1337: 1785: applyPtr = pq_getmsgint64(&reply_message);
call 0 returned 100%
1337: 1786: replyTime = pq_getmsgint64(&reply_message);
call 0 returned 100%
1337: 1787: replyRequested = pq_getmsgbyte(&reply_message);
call 0 returned 100%
-: 1788:
1337: 1789: if (log_min_messages <= DEBUG2)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1790: {
-: 1791: char *replyTimeStr;
-: 1792:
-: 1793: /* Copy because timestamptz_to_str returns a static buffer */
#####: 1794: replyTimeStr = pstrdup(timestamptz_to_str(replyTime));
call 0 never executed
call 1 never executed
-: 1795:
#####: 1796: elog(DEBUG2, "write %X/%X flush %X/%X apply %X/%X%s reply_time %s",
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
-: 1797: (uint32) (writePtr >> 32), (uint32) writePtr,
-: 1798: (uint32) (flushPtr >> 32), (uint32) flushPtr,
-: 1799: (uint32) (applyPtr >> 32), (uint32) applyPtr,
-: 1800: replyRequested ? " (reply requested)" : "",
-: 1801: replyTimeStr);
-: 1802:
#####: 1803: pfree(replyTimeStr);
call 0 never executed
-: 1804: }
-: 1805:
-: 1806: /* See if we can compute the round-trip lag for these positions. */
1337: 1807: now = GetCurrentTimestamp();
call 0 returned 100%
1337: 1808: writeLag = LagTrackerRead(SYNC_REP_WAIT_WRITE, writePtr, now);
call 0 returned 100%
1337: 1809: flushLag = LagTrackerRead(SYNC_REP_WAIT_FLUSH, flushPtr, now);
call 0 returned 100%
1337: 1810: applyLag = LagTrackerRead(SYNC_REP_WAIT_APPLY, applyPtr, now);
call 0 returned 100%
-: 1811:
-: 1812: /*
-: 1813: * If the standby reports that it has fully replayed the WAL in two
-: 1814: * consecutive reply messages, then the second such message must result
-: 1815: * from wal_receiver_status_interval expiring on the standby. This is a
-: 1816: * convenient time to forget the lag times measured when it last
-: 1817: * wrote/flushed/applied a WAL record, to avoid displaying stale lag data
-: 1818: * until more WAL traffic arrives.
-: 1819: */
1337: 1820: clearLagTimes = false;
1337: 1821: if (applyPtr == sentPtr)
branch 0 taken 20% (fallthrough)
branch 1 taken 80%
-: 1822: {
266: 1823: if (fullyAppliedLastTime)
branch 0 taken 38% (fallthrough)
branch 1 taken 62%
102: 1824: clearLagTimes = true;
266: 1825: fullyAppliedLastTime = true;
-: 1826: }
-: 1827: else
1071: 1828: fullyAppliedLastTime = false;
-: 1829:
-: 1830: /* Send a reply if the standby requested one. */
1337: 1831: if (replyRequested)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 1832: WalSndKeepalive(false);
call 0 never executed
-: 1833:
-: 1834: /*
-: 1835: * Update shared state for this WalSender process based on reply data from
-: 1836: * standby.
-: 1837: */
-: 1838: {
1337: 1839: WalSnd *walsnd = MyWalSnd;
-: 1840:
1337: 1841: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
1337: 1842: walsnd->write = writePtr;
1337: 1843: walsnd->flush = flushPtr;
1337: 1844: walsnd->apply = applyPtr;
1337: 1845: if (writeLag != -1 || clearLagTimes)
branch 0 taken 83% (fallthrough)
branch 1 taken 17%
branch 2 taken 8% (fallthrough)
branch 3 taken 92%
319: 1846: walsnd->writeLag = writeLag;
1337: 1847: if (flushLag != -1 || clearLagTimes)
branch 0 taken 20% (fallthrough)
branch 1 taken 80%
branch 2 taken 32% (fallthrough)
branch 3 taken 68%
1157: 1848: walsnd->flushLag = flushLag;
1337: 1849: if (applyLag != -1 || clearLagTimes)
branch 0 taken 65% (fallthrough)
branch 1 taken 35%
branch 2 taken 10% (fallthrough)
branch 3 taken 90%
556: 1850: walsnd->applyLag = applyLag;
1337: 1851: walsnd->replyTime = replyTime;
1337: 1852: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 1853: }
-: 1854:
1337: 1855: if (!am_cascading_walsender)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
1320: 1856: SyncRepReleaseWaiters();
call 0 returned 100%
-: 1857:
-: 1858: /*
-: 1859: * Advance our local xmin horizon when the client confirmed a flush.
-: 1860: */
1337: 1861: if (MyReplicationSlot && flushPtr != InvalidXLogRecPtr)
branch 0 taken 70% (fallthrough)
branch 1 taken 30%
branch 2 taken 99% (fallthrough)
branch 3 taken 1%
-: 1862: {
926: 1863: if (SlotIsLogical(MyReplicationSlot))
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
883: 1864: LogicalConfirmReceivedLocation(flushPtr);
call 0 returned 100%
-: 1865: else
43: 1866: PhysicalConfirmReceivedLocation(flushPtr);
call 0 returned 100%
-: 1867: }
1337: 1868:}
-: 1869:
-: 1870:/* compute new replication slot xmin horizon if needed */
-: 1871:static void
function PhysicalReplicationSlotNewXmin called 0 returned 0% blocks executed 0%
#####: 1872:PhysicalReplicationSlotNewXmin(TransactionId feedbackXmin, TransactionId feedbackCatalogXmin)
-: 1873:{
#####: 1874: bool changed = false;
#####: 1875: ReplicationSlot *slot = MyReplicationSlot;
-: 1876:
#####: 1877: SpinLockAcquire(&slot->mutex);
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
#####: 1878: MyPgXact->xmin = InvalidTransactionId;
-: 1879:
-: 1880: /*
-: 1881: * For physical replication we don't need the interlock provided by xmin
-: 1882: * and effective_xmin since the consequences of a missed increase are
-: 1883: * limited to query cancellations, so set both at once.
-: 1884: */
#####: 1885: if (!TransactionIdIsNormal(slot->data.xmin) ||
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
#####: 1886: !TransactionIdIsNormal(feedbackXmin) ||
branch 0 never executed
branch 1 never executed
#####: 1887: TransactionIdPrecedes(slot->data.xmin, feedbackXmin))
call 0 never executed
-: 1888: {
#####: 1889: changed = true;
#####: 1890: slot->data.xmin = feedbackXmin;
#####: 1891: slot->effective_xmin = feedbackXmin;
-: 1892: }
#####: 1893: if (!TransactionIdIsNormal(slot->data.catalog_xmin) ||
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
#####: 1894: !TransactionIdIsNormal(feedbackCatalogXmin) ||
branch 0 never executed
branch 1 never executed
#####: 1895: TransactionIdPrecedes(slot->data.catalog_xmin, feedbackCatalogXmin))
call 0 never executed
-: 1896: {
#####: 1897: changed = true;
#####: 1898: slot->data.catalog_xmin = feedbackCatalogXmin;
#####: 1899: slot->effective_catalog_xmin = feedbackCatalogXmin;
-: 1900: }
#####: 1901: SpinLockRelease(&slot->mutex);
call 0 never executed
-: 1902:
#####: 1903: if (changed)
branch 0 never executed
branch 1 never executed
-: 1904: {
#####: 1905: ReplicationSlotMarkDirty();
call 0 never executed
#####: 1906: ReplicationSlotsComputeRequiredXmin(false);
call 0 never executed
-: 1907: }
#####: 1908:}
-: 1909:
-: 1910:/*
-: 1911: * Check that the provided xmin/epoch are sane, that is, not in the future
-: 1912: * and not so far back as to be already wrapped around.
-: 1913: *
-: 1914: * Epoch of nextXid should be same as standby, or if the counter has
-: 1915: * wrapped, then one greater than standby.
-: 1916: *
-: 1917: * This check doesn't care about whether clog exists for these xids
-: 1918: * at all.
-: 1919: */
-: 1920:static bool
function TransactionIdInRecentPast called 0 returned 0% blocks executed 0%
#####: 1921:TransactionIdInRecentPast(TransactionId xid, uint32 epoch)
-: 1922:{
-: 1923: FullTransactionId nextFullXid;
-: 1924: TransactionId nextXid;
-: 1925: uint32 nextEpoch;
-: 1926:
#####: 1927: nextFullXid = ReadNextFullTransactionId();
call 0 never executed
#####: 1928: nextXid = XidFromFullTransactionId(nextFullXid);
#####: 1929: nextEpoch = EpochFromFullTransactionId(nextFullXid);
-: 1930:
#####: 1931: if (xid <= nextXid)
branch 0 never executed
branch 1 never executed
-: 1932: {
#####: 1933: if (epoch != nextEpoch)
branch 0 never executed
branch 1 never executed
#####: 1934: return false;
-: 1935: }
-: 1936: else
-: 1937: {
#####: 1938: if (epoch + 1 != nextEpoch)
branch 0 never executed
branch 1 never executed
#####: 1939: return false;
-: 1940: }
-: 1941:
#####: 1942: if (!TransactionIdPrecedesOrEquals(xid, nextXid))
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 1943: return false; /* epoch OK, but it's wrapped around */
-: 1944:
#####: 1945: return true;
-: 1946:}
-: 1947:
-: 1948:/*
-: 1949: * Hot Standby feedback
-: 1950: */
-: 1951:static void
function ProcessStandbyHSFeedbackMessage called 36 returned 100% blocks executed 41%
36: 1952:ProcessStandbyHSFeedbackMessage(void)
-: 1953:{
-: 1954: TransactionId feedbackXmin;
-: 1955: uint32 feedbackEpoch;
-: 1956: TransactionId feedbackCatalogXmin;
-: 1957: uint32 feedbackCatalogEpoch;
-: 1958: TimestampTz replyTime;
-: 1959:
-: 1960: /*
-: 1961: * Decipher the reply message. The caller already consumed the msgtype
-: 1962: * byte. See XLogWalRcvSendHSFeedback() in walreceiver.c for the creation
-: 1963: * of this message.
-: 1964: */
36: 1965: replyTime = pq_getmsgint64(&reply_message);
call 0 returned 100%
36: 1966: feedbackXmin = pq_getmsgint(&reply_message, 4);
call 0 returned 100%
36: 1967: feedbackEpoch = pq_getmsgint(&reply_message, 4);
call 0 returned 100%
36: 1968: feedbackCatalogXmin = pq_getmsgint(&reply_message, 4);
call 0 returned 100%
36: 1969: feedbackCatalogEpoch = pq_getmsgint(&reply_message, 4);
call 0 returned 100%
-: 1970:
36: 1971: if (log_min_messages <= DEBUG2)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 1972: {
-: 1973: char *replyTimeStr;
-: 1974:
-: 1975: /* Copy because timestamptz_to_str returns a static buffer */
#####: 1976: replyTimeStr = pstrdup(timestamptz_to_str(replyTime));
call 0 never executed
call 1 never executed
-: 1977:
#####: 1978: elog(DEBUG2, "hot standby feedback xmin %u epoch %u, catalog_xmin %u epoch %u reply_time %s",
call 0 never executed
call 1 never executed
-: 1979: feedbackXmin,
-: 1980: feedbackEpoch,
-: 1981: feedbackCatalogXmin,
-: 1982: feedbackCatalogEpoch,
-: 1983: replyTimeStr);
-: 1984:
#####: 1985: pfree(replyTimeStr);
call 0 never executed
-: 1986: }
-: 1987:
-: 1988: /*
-: 1989: * Update shared state for this WalSender process based on reply data from
-: 1990: * standby.
-: 1991: */
-: 1992: {
36: 1993: WalSnd *walsnd = MyWalSnd;
-: 1994:
36: 1995: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
36: 1996: walsnd->replyTime = replyTime;
36: 1997: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 1998: }
-: 1999:
-: 2000: /*
-: 2001: * Unset WalSender's xmins if the feedback message values are invalid.
-: 2002: * This happens when the downstream turned hot_standby_feedback off.
-: 2003: */
36: 2004: if (!TransactionIdIsNormal(feedbackXmin)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
36: 2005: && !TransactionIdIsNormal(feedbackCatalogXmin))
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 2006: {
36: 2007: MyPgXact->xmin = InvalidTransactionId;
36: 2008: if (MyReplicationSlot != NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2009: PhysicalReplicationSlotNewXmin(feedbackXmin, feedbackCatalogXmin);
call 0 never executed
36: 2010: return;
-: 2011: }
-: 2012:
-: 2013: /*
-: 2014: * Check that the provided xmin/epoch are sane, that is, not in the future
-: 2015: * and not so far back as to be already wrapped around. Ignore if not.
-: 2016: */
#####: 2017: if (TransactionIdIsNormal(feedbackXmin) &&
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
#####: 2018: !TransactionIdInRecentPast(feedbackXmin, feedbackEpoch))
call 0 never executed
#####: 2019: return;
-: 2020:
#####: 2021: if (TransactionIdIsNormal(feedbackCatalogXmin) &&
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
#####: 2022: !TransactionIdInRecentPast(feedbackCatalogXmin, feedbackCatalogEpoch))
call 0 never executed
#####: 2023: return;
-: 2024:
-: 2025: /*
-: 2026: * Set the WalSender's xmin equal to the standby's requested xmin, so that
-: 2027: * the xmin will be taken into account by GetOldestXmin. This will hold
-: 2028: * back the removal of dead rows and thereby prevent the generation of
-: 2029: * cleanup conflicts on the standby server.
-: 2030: *
-: 2031: * There is a small window for a race condition here: although we just
-: 2032: * checked that feedbackXmin precedes nextXid, the nextXid could have
-: 2033: * gotten advanced between our fetching it and applying the xmin below,
-: 2034: * perhaps far enough to make feedbackXmin wrap around. In that case the
-: 2035: * xmin we set here would be "in the future" and have no effect. No point
-: 2036: * in worrying about this since it's too late to save the desired data
-: 2037: * anyway. Assuming that the standby sends us an increasing sequence of
-: 2038: * xmins, this could only happen during the first reply cycle, else our
-: 2039: * own xmin would prevent nextXid from advancing so far.
-: 2040: *
-: 2041: * We don't bother taking the ProcArrayLock here. Setting the xmin field
-: 2042: * is assumed atomic, and there's no real need to prevent a concurrent
-: 2043: * GetOldestXmin. (If we're moving our xmin forward, this is obviously
-: 2044: * safe, and if we're moving it backwards, well, the data is at risk
-: 2045: * already since a VACUUM could have just finished calling GetOldestXmin.)
-: 2046: *
-: 2047: * If we're using a replication slot we reserve the xmin via that,
-: 2048: * otherwise via the walsender's PGXACT entry. We can only track the
-: 2049: * catalog xmin separately when using a slot, so we store the least of the
-: 2050: * two provided when not using a slot.
-: 2051: *
-: 2052: * XXX: It might make sense to generalize the ephemeral slot concept and
-: 2053: * always use the slot mechanism to handle the feedback xmin.
-: 2054: */
#####: 2055: if (MyReplicationSlot != NULL) /* XXX: persistency configurable? */
branch 0 never executed
branch 1 never executed
#####: 2056: PhysicalReplicationSlotNewXmin(feedbackXmin, feedbackCatalogXmin);
call 0 never executed
-: 2057: else
-: 2058: {
#####: 2059: if (TransactionIdIsNormal(feedbackCatalogXmin)
branch 0 never executed
branch 1 never executed
#####: 2060: && TransactionIdPrecedes(feedbackCatalogXmin, feedbackXmin))
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 2061: MyPgXact->xmin = feedbackCatalogXmin;
-: 2062: else
#####: 2063: MyPgXact->xmin = feedbackXmin;
-: 2064: }
-: 2065:}
-: 2066:
-: 2067:/*
-: 2068: * Compute how long send/receive loops should sleep.
-: 2069: *
-: 2070: * If wal_sender_timeout is enabled we want to wake up in time to send
-: 2071: * keepalives and to abort the connection if wal_sender_timeout has been
-: 2072: * reached.
-: 2073: */
-: 2074:static long
function WalSndComputeSleeptime called 4642 returned 100% blocks executed 100%
4642: 2075:WalSndComputeSleeptime(TimestampTz now)
-: 2076:{
4642: 2077: long sleeptime = 10000; /* 10 s */
-: 2078:
4642: 2079: if (wal_sender_timeout > 0 && last_reply_timestamp > 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
-: 2080: {
-: 2081: TimestampTz wakeup_time;
-: 2082: long sec_to_timeout;
-: 2083: int microsec_to_timeout;
-: 2084:
-: 2085: /*
-: 2086: * At the latest stop sleeping once wal_sender_timeout has been
-: 2087: * reached.
-: 2088: */
4642: 2089: wakeup_time = TimestampTzPlusMilliseconds(last_reply_timestamp,
-: 2090: wal_sender_timeout);
-: 2091:
-: 2092: /*
-: 2093: * If no ping has been sent yet, wakeup when it's time to do so.
-: 2094: * WalSndKeepaliveIfNecessary() wants to send a keepalive once half of
-: 2095: * the timeout passed without a response.
-: 2096: */
4642: 2097: if (!waiting_for_ping_response)
branch 0 taken 20% (fallthrough)
branch 1 taken 80%
934: 2098: wakeup_time = TimestampTzPlusMilliseconds(last_reply_timestamp,
-: 2099: wal_sender_timeout / 2);
-: 2100:
-: 2101: /* Compute relative time until wakeup. */
4642: 2102: TimestampDifference(now, wakeup_time,
call 0 returned 100%
-: 2103: &sec_to_timeout, µsec_to_timeout);
-: 2104:
9284: 2105: sleeptime = sec_to_timeout * 1000 +
4642: 2106: microsec_to_timeout / 1000;
-: 2107: }
-: 2108:
4642: 2109: return sleeptime;
-: 2110:}
-: 2111:
-: 2112:/*
-: 2113: * Check whether there have been responses by the client within
-: 2114: * wal_sender_timeout and shutdown if not. Using last_processing as the
-: 2115: * reference point avoids counting server-side stalls against the client.
-: 2116: * However, a long server-side stall can make WalSndKeepaliveIfNecessary()
-: 2117: * postdate last_processing by more than wal_sender_timeout. If that happens,
-: 2118: * the client must reply almost immediately to avoid a timeout. This rarely
-: 2119: * affects the default configuration, under which clients spontaneously send a
-: 2120: * message every standby_message_timeout = wal_sender_timeout/6 = 10s. We
-: 2121: * could eliminate that problem by recognizing timeout expiration at
-: 2122: * wal_sender_timeout/2 after the keepalive.
-: 2123: */
-: 2124:static void
function WalSndCheckTimeOut called 17443 returned 100% blocks executed 36%
17443: 2125:WalSndCheckTimeOut(void)
-: 2126:{
-: 2127: TimestampTz timeout;
-: 2128:
-: 2129: /* don't bail out if we're doing something that doesn't require timeouts */
17443: 2130: if (last_reply_timestamp <= 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
17443: 2131: return;
-: 2132:
17443: 2133: timeout = TimestampTzPlusMilliseconds(last_reply_timestamp,
-: 2134: wal_sender_timeout);
-: 2135:
17443: 2136: if (wal_sender_timeout > 0 && last_processing >= timeout)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
-: 2137: {
-: 2138: /*
-: 2139: * Since typically expiration of replication timeout means
-: 2140: * communication problem, we don't send the error message to the
-: 2141: * standby.
-: 2142: */
#####: 2143: ereport(COMMERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
-: 2144: (errmsg("terminating walsender process due to replication timeout")));
-: 2145:
#####: 2146: WalSndShutdown();
call 0 never executed
-: 2147: }
-: 2148:}
-: 2149:
-: 2150:/* Main loop of walsender process that streams the WAL over Copy messages. */
-: 2151:static void
function WalSndLoop called 115 returned 43% blocks executed 96%
115: 2152:WalSndLoop(WalSndSendDataCallback send_data)
-: 2153:{
-: 2154: /*
-: 2155: * Initialize the last reply timestamp. That enables timeout processing
-: 2156: * from hereon.
-: 2157: */
115: 2158: last_reply_timestamp = GetCurrentTimestamp();
call 0 returned 100%
115: 2159: waiting_for_ping_response = false;
-: 2160:
-: 2161: /*
-: 2162: * Loop until we reach the end of this timeline or the client requests to
-: 2163: * stop streaming.
-: 2164: */
-: 2165: for (;;)
-: 2166: {
-: 2167: /* Clear any already-pending wakeups */
17370: 2168: ResetLatch(MyLatch);
call 0 returned 100%
-: 2169:
17370: 2170: CHECK_FOR_INTERRUPTS();
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2171:
-: 2172: /* Process any requests or signals received recently */
17370: 2173: if (ConfigReloadPending)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
-: 2174: {
12: 2175: ConfigReloadPending = false;
12: 2176: ProcessConfigFile(PGC_SIGHUP);
call 0 returned 100%
12: 2177: SyncRepInitConfig();
call 0 returned 100%
-: 2178: }
-: 2179:
-: 2180: /* Check for input from the client */
17370: 2181: ProcessRepliesIfAny();
call 0 returned 99%
-: 2182:
-: 2183: /*
-: 2184: * If we have received CopyDone from the client, sent CopyDone
-: 2185: * ourselves, and the output buffer is empty, it's time to exit
-: 2186: * streaming.
-: 2187: */
17428: 2188: if (streamingDoneReceiving && streamingDoneSending &&
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
branch 2 taken 100% (fallthrough)
branch 3 taken 0%
branch 4 taken 55% (fallthrough)
branch 5 taken 45%
91: 2189: !pq_is_send_pending())
call 0 returned 100%
50: 2190: break;
-: 2191:
-: 2192: /*
-: 2193: * If we don't have any pending data in the output buffer, try to send
-: 2194: * some more. If there is some, we don't bother to call send_data
-: 2195: * again until we've flushed it ... but we'd better assume we are not
-: 2196: * caught up.
-: 2197: */
17287: 2198: if (!pq_is_send_pending())
call 0 returned 100%
branch 1 taken 95% (fallthrough)
branch 2 taken 5%
16452: 2199: send_data();
call 0 returned 99%
-: 2200: else
835: 2201: WalSndCaughtUp = false;
-: 2202:
-: 2203: /* Try to flush pending output to the client */
17271: 2204: if (pq_flush_if_writable() != 0)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 2205: WalSndShutdown();
call 0 never executed
-: 2206:
-: 2207: /* If nothing remains to be sent right now ... */
17271: 2208: if (WalSndCaughtUp && !pq_is_send_pending())
branch 0 taken 39% (fallthrough)
branch 1 taken 61%
call 2 returned 100%
branch 3 taken 99% (fallthrough)
branch 4 taken 1%
-: 2209: {
-: 2210: /*
-: 2211: * If we're in catchup state, move to streaming. This is an
-: 2212: * important state change for users to know about, since before
-: 2213: * this point data loss might occur if the primary dies and we
-: 2214: * need to failover to the standby. The state change is also
-: 2215: * important for synchronous replication, since commits that
-: 2216: * started to wait at that point might wait for some time.
-: 2217: */
6720: 2218: if (MyWalSnd->state == WALSNDSTATE_CATCHUP)
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
-: 2219: {
115: 2220: ereport(DEBUG1,
call 0 returned 100%
branch 1 taken 1% (fallthrough)
branch 2 taken 99%
call 3 returned 100%
call 4 returned 100%
-: 2221: (errmsg("\"%s\" has now caught up with upstream server",
-: 2222: application_name)));
115: 2223: WalSndSetState(WALSNDSTATE_STREAMING);
call 0 returned 100%
-: 2224: }
-: 2225:
-: 2226: /*
-: 2227: * When SIGUSR2 arrives, we send any outstanding logs up to the
-: 2228: * shutdown checkpoint record (i.e., the latest record), wait for
-: 2229: * them to be replicated to the standby, and exit. This may be a
-: 2230: * normal termination at shutdown, or a promotion, the walsender
-: 2231: * is not sure which.
-: 2232: */
6720: 2233: if (got_SIGUSR2)
branch 0 taken 53% (fallthrough)
branch 1 taken 47%
3594: 2234: WalSndDone(send_data);
call 0 returned 99%
-: 2235: }
-: 2236:
-: 2237: /* Check for replication timeout. */
17255: 2238: WalSndCheckTimeOut();
call 0 returned 100%
-: 2239:
-: 2240: /* Send keepalive if the time has come */
17255: 2241: WalSndKeepaliveIfNecessary();
call 0 returned 100%
-: 2242:
-: 2243: /*
-: 2244: * We don't block if not caught up, unless there is unsent data
-: 2245: * pending in which case we'd better block until the socket is
-: 2246: * write-ready. This test is only needed for the case where the
-: 2247: * send_data callback handled a subset of the available data but then
-: 2248: * pq_flush_if_writable flushed it all --- we should immediately try
-: 2249: * to send more.
-: 2250: */
17255: 2251: if ((WalSndCaughtUp && !streamingDoneSending) || pq_is_send_pending())
branch 0 taken 39% (fallthrough)
branch 1 taken 61%
branch 2 taken 35% (fallthrough)
branch 3 taken 65%
call 4 returned 100%
branch 5 taken 1% (fallthrough)
branch 6 taken 99%
-: 2252: {
-: 2253: long sleeptime;
-: 2254: int wakeEvents;
-: 2255:
4454: 2256: wakeEvents = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH | WL_TIMEOUT |
-: 2257: WL_SOCKET_READABLE;
-: 2258:
-: 2259: /*
-: 2260: * Use fresh timestamp, not last_processing, to reduce the chance
-: 2261: * of reaching wal_sender_timeout before sending a keepalive.
-: 2262: */
4454: 2263: sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp());
call 0 returned 100%
call 1 returned 100%
-: 2264:
4454: 2265: if (pq_is_send_pending())
call 0 returned 100%
branch 1 taken 18% (fallthrough)
branch 2 taken 82%
823: 2266: wakeEvents |= WL_SOCKET_WRITEABLE;
-: 2267:
-: 2268: /* Sleep until something happens or we time out */
4454: 2269: (void) WaitLatchOrSocket(MyLatch, wakeEvents,
call 0 returned 100%
4454: 2270: MyProcPort->sock, sleeptime,
-: 2271: WAIT_EVENT_WAL_SENDER_MAIN);
-: 2272: }
17255: 2273: }
50: 2274: return;
-: 2275:}
-: 2276:
-: 2277:/* Initialize a per-walsender data structure for this walsender process */
-: 2278:static void
function InitWalSenderSlot called 229 returned 100% blocks executed 79%
229: 2279:InitWalSenderSlot(void)
-: 2280:{
-: 2281: int i;
-: 2282:
-: 2283: /*
-: 2284: * WalSndCtl should be set up already (we inherit this by fork() or
-: 2285: * EXEC_BACKEND mechanism from the postmaster).
-: 2286: */
229: 2287: Assert(WalSndCtl != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
229: 2288: Assert(MyWalSnd == NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2289:
-: 2290: /*
-: 2291: * Find a free walsender slot and reserve it. This must not fail due to
-: 2292: * the prior check for free WAL senders in InitProcess().
-: 2293: */
684: 2294: for (i = 0; i < max_wal_senders; i++)
branch 0 taken 100%
branch 1 taken 0% (fallthrough)
-: 2295: {
342: 2296: WalSnd *walsnd = &WalSndCtl->walsnds[i];
-: 2297:
342: 2298: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 2299:
342: 2300: if (walsnd->pid != 0)
branch 0 taken 33% (fallthrough)
branch 1 taken 67%
-: 2301: {
113: 2302: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
113: 2303: continue;
-: 2304: }
-: 2305: else
-: 2306: {
-: 2307: /*
-: 2308: * Found a free slot. Reserve it for us.
-: 2309: */
229: 2310: walsnd->pid = MyProcPid;
229: 2311: walsnd->sentPtr = InvalidXLogRecPtr;
229: 2312: walsnd->write = InvalidXLogRecPtr;
229: 2313: walsnd->flush = InvalidXLogRecPtr;
229: 2314: walsnd->apply = InvalidXLogRecPtr;
229: 2315: walsnd->writeLag = -1;
229: 2316: walsnd->flushLag = -1;
229: 2317: walsnd->applyLag = -1;
229: 2318: walsnd->state = WALSNDSTATE_STARTUP;
229: 2319: walsnd->latch = &MyProc->procLatch;
229: 2320: walsnd->replyTime = 0;
229: 2321: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 2322: /* don't need the lock anymore */
229: 2323: MyWalSnd = (WalSnd *) walsnd;
-: 2324:
229: 2325: break;
-: 2326: }
-: 2327: }
-: 2328:
229: 2329: Assert(MyWalSnd != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2330:
-: 2331: /* Arrange to clean up at walsender exit */
229: 2332: on_shmem_exit(WalSndKill, 0);
call 0 returned 100%
229: 2333:}
-: 2334:
-: 2335:/* Destroy the per-walsender data structure for this walsender process */
-: 2336:static void
function WalSndKill called 229 returned 100% blocks executed 75%
229: 2337:WalSndKill(int code, Datum arg)
-: 2338:{
229: 2339: WalSnd *walsnd = MyWalSnd;
-: 2340:
229: 2341: Assert(walsnd != NULL);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2342:
229: 2343: MyWalSnd = NULL;
-: 2344:
229: 2345: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 2346: /* clear latch while holding the spinlock, so it can safely be read */
229: 2347: walsnd->latch = NULL;
-: 2348: /* Mark WalSnd struct as no longer being in use. */
229: 2349: walsnd->pid = 0;
229: 2350: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
229: 2351:}
-: 2352:
-: 2353:/*
-: 2354: * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
-: 2355: *
-: 2356: * XXX probably this should be improved to suck data directly from the
-: 2357: * WAL buffers when possible.
-: 2358: *
-: 2359: * Will open, and keep open, one WAL segment stored in the global file
-: 2360: * descriptor sendFile. This means if XLogRead is used once, there will
-: 2361: * always be one descriptor left open until the process ends, but never
-: 2362: * more than one.
-: 2363: */
-: 2364:static void
function XLogRead called 8247 returned 100% blocks executed 41%
8247: 2365:XLogRead(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count)
-: 2366:{
-: 2367: char *p;
-: 2368: XLogRecPtr recptr;
-: 2369: Size nbytes;
-: 2370: XLogSegNo segno;
-: 2371:
-: 2372:retry:
8247: 2373: p = buf;
8247: 2374: recptr = startptr;
8247: 2375: nbytes = count;
-: 2376:
24741: 2377: while (nbytes > 0)
branch 0 taken 50%
branch 1 taken 50% (fallthrough)
-: 2378: {
-: 2379: uint32 startoff;
-: 2380: int segbytes;
-: 2381: int readbytes;
-: 2382:
8247: 2383: startoff = XLogSegmentOffset(recptr, segcxt->ws_segsize);
-: 2384:
16336: 2385: if (sendSeg->ws_file < 0 ||
branch 0 taken 98% (fallthrough)
branch 1 taken 2%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
8089: 2386: !XLByteInSeg(recptr, sendSeg->ws_segno, segcxt->ws_segsize))
-: 2387: {
-: 2388: char path[MAXPGPATH];
-: 2389:
-: 2390: /* Switch to another logfile segment */
158: 2391: if (sendSeg->ws_file >= 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2392: close(sendSeg->ws_file);
call 0 never executed
-: 2393:
158: 2394: XLByteToSeg(recptr, sendSeg->ws_segno, segcxt->ws_segsize);
-: 2395:
-: 2396: /*-------
-: 2397: * When reading from a historic timeline, and there is a timeline
-: 2398: * switch within this segment, read from the WAL segment belonging
-: 2399: * to the new timeline.
-: 2400: *
-: 2401: * For example, imagine that this server is currently on timeline
-: 2402: * 5, and we're streaming timeline 4. The switch from timeline 4
-: 2403: * to 5 happened at 0/13002088. In pg_wal, we have these files:
-: 2404: *
-: 2405: * ...
-: 2406: * 000000040000000000000012
-: 2407: * 000000040000000000000013
-: 2408: * 000000050000000000000013
-: 2409: * 000000050000000000000014
-: 2410: * ...
-: 2411: *
-: 2412: * In this situation, when requested to send the WAL from
-: 2413: * segment 0x13, on timeline 4, we read the WAL from file
-: 2414: * 000000050000000000000013. Archive recovery prefers files from
-: 2415: * newer timelines, so if the segment was restored from the
-: 2416: * archive on this server, the file belonging to the old timeline,
-: 2417: * 000000040000000000000013, might not exist. Their contents are
-: 2418: * equal up to the switchpoint, because at a timeline switch, the
-: 2419: * used portion of the old segment is copied to the new file.
-: 2420: *-------
-: 2421: */
158: 2422: sendSeg->ws_tli = sendTimeLine;
158: 2423: if (sendTimeLineIsHistoric)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
-: 2424: {
-: 2425: XLogSegNo endSegNo;
-: 2426:
5: 2427: XLByteToSeg(sendTimeLineValidUpto, endSegNo, segcxt->ws_segsize);
5: 2428: if (sendSeg->ws_segno == endSegNo)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
5: 2429: sendSeg->ws_tli = sendTimeLineNextTLI;
-: 2430: }
-: 2431:
158: 2432: XLogFilePath(path, sendSeg->ws_tli, sendSeg->ws_segno, segcxt->ws_segsize);
call 0 returned 100%
-: 2433:
158: 2434: sendSeg->ws_file = BasicOpenFile(path, O_RDONLY | PG_BINARY);
call 0 returned 100%
158: 2435: if (sendSeg->ws_file < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 2436: {
-: 2437: /*
-: 2438: * If the file is not found, assume it's because the standby
-: 2439: * asked for a too old WAL segment that has already been
-: 2440: * removed or recycled.
-: 2441: */
#####: 2442: if (errno == ENOENT)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 2443: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 2444: (errcode_for_file_access(),
-: 2445: errmsg("requested WAL segment %s has already been removed",
-: 2446: XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno))));
-: 2447: else
#####: 2448: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 2449: (errcode_for_file_access(),
-: 2450: errmsg("could not open file \"%s\": %m",
-: 2451: path)));
-: 2452: }
158: 2453: sendSeg->ws_off = 0;
-: 2454: }
-: 2455:
-: 2456: /* Need to seek in the file? */
8247: 2457: if (sendSeg->ws_off != startoff)
branch 0 taken 90% (fallthrough)
branch 1 taken 10%
-: 2458: {
7399: 2459: if (lseek(sendSeg->ws_file, (off_t) startoff, SEEK_SET) < 0)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 2460: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 2461: (errcode_for_file_access(),
-: 2462: errmsg("could not seek in log segment %s to offset %u: %m",
-: 2463: XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
-: 2464: startoff)));
7399: 2465: sendSeg->ws_off = startoff;
-: 2466: }
-: 2467:
-: 2468: /* How many bytes are within this segment? */
8247: 2469: if (nbytes > (segcxt->ws_segsize - startoff))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2470: segbytes = segcxt->ws_segsize - startoff;
-: 2471: else
8247: 2472: segbytes = nbytes;
-: 2473:
8247: 2474: pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
call 0 returned 100%
8247: 2475: readbytes = read(sendSeg->ws_file, p, segbytes);
call 0 returned 100%
8247: 2476: pgstat_report_wait_end();
call 0 returned 100%
8247: 2477: if (readbytes < 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 2478: {
#####: 2479: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 2480: (errcode_for_file_access(),
-: 2481: errmsg("could not read from log segment %s, offset %u, length %zu: %m",
-: 2482: XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
-: 2483: sendSeg->ws_off, (Size) segbytes)));
-: 2484: }
8247: 2485: else if (readbytes == 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 2486: {
#####: 2487: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
call 7 never executed
-: 2488: (errcode(ERRCODE_DATA_CORRUPTED),
-: 2489: errmsg("could not read from log segment %s, offset %u: read %d of %zu",
-: 2490: XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
-: 2491: sendSeg->ws_off, readbytes, (Size) segbytes)));
-: 2492: }
-: 2493:
-: 2494: /* Update state for read */
8247: 2495: recptr += readbytes;
-: 2496:
8247: 2497: sendSeg->ws_off += readbytes;
8247: 2498: nbytes -= readbytes;
8247: 2499: p += readbytes;
-: 2500: }
-: 2501:
-: 2502: /*
-: 2503: * After reading into the buffer, check that what we read was valid. We do
-: 2504: * this after reading, because even though the segment was present when we
-: 2505: * opened it, it might get recycled or removed while we read it. The
-: 2506: * read() succeeds in that case, but the data we tried to read might
-: 2507: * already have been overwritten with new WAL records.
-: 2508: */
8247: 2509: XLByteToSeg(startptr, segno, segcxt->ws_segsize);
8247: 2510: CheckXLogRemoved(segno, ThisTimeLineID);
call 0 returned 100%
-: 2511:
-: 2512: /*
-: 2513: * During recovery, the currently-open WAL file might be replaced with the
-: 2514: * file of the same name retrieved from archive. So we always need to
-: 2515: * check what we read was valid after reading into the buffer. If it's
-: 2516: * invalid, we try to open and read the file again.
-: 2517: */
8247: 2518: if (am_cascading_walsender)
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
-: 2519: {
135: 2520: WalSnd *walsnd = MyWalSnd;
-: 2521: bool reload;
-: 2522:
135: 2523: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
135: 2524: reload = walsnd->needreload;
135: 2525: walsnd->needreload = false;
135: 2526: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 2527:
135: 2528: if (reload && sendSeg->ws_file >= 0)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
branch 2 never executed
branch 3 never executed
-: 2529: {
#####: 2530: close(sendSeg->ws_file);
call 0 never executed
#####: 2531: sendSeg->ws_file = -1;
-: 2532:
#####: 2533: goto retry;
-: 2534: }
-: 2535: }
8247: 2536:}
-: 2537:
-: 2538:/*
-: 2539: * Send out the WAL in its normal physical/stored form.
-: 2540: *
-: 2541: * Read up to MAX_SEND_SIZE bytes of WAL that's been flushed to disk,
-: 2542: * but not yet sent to the client, and buffer it in the libpq output
-: 2543: * buffer.
-: 2544: *
-: 2545: * If there is no unsent WAL remaining, WalSndCaughtUp is set to true,
-: 2546: * otherwise WalSndCaughtUp is set to false.
-: 2547: */
-: 2548:static void
function XLogSendPhysical called 3358 returned 100% blocks executed 83%
3358: 2549:XLogSendPhysical(void)
-: 2550:{
-: 2551: XLogRecPtr SendRqstPtr;
-: 2552: XLogRecPtr startptr;
-: 2553: XLogRecPtr endptr;
-: 2554: Size nbytes;
-: 2555:
-: 2556: /* If requested switch the WAL sender to the stopping state. */
3358: 2557: if (got_STOPPING)
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
82: 2558: WalSndSetState(WALSNDSTATE_STOPPING);
call 0 returned 100%
-: 2559:
3358: 2560: if (streamingDoneSending)
branch 0 taken 70% (fallthrough)
branch 1 taken 30%
-: 2561: {
2352: 2562: WalSndCaughtUp = true;
2352: 2563: return;
-: 2564: }
-: 2565:
-: 2566: /* Figure out how far we can safely send the WAL. */
1006: 2567: if (sendTimeLineIsHistoric)
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
-: 2568: {
-: 2569: /*
-: 2570: * Streaming an old timeline that's in this server's history, but is
-: 2571: * not the one we're currently inserting or replaying. It can be
-: 2572: * streamed up to the point where we switched off that timeline.
-: 2573: */
16: 2574: SendRqstPtr = sendTimeLineValidUpto;
-: 2575: }
990: 2576: else if (am_cascading_walsender)
branch 0 taken 15% (fallthrough)
branch 1 taken 85%
-: 2577: {
-: 2578: /*
-: 2579: * Streaming the latest timeline on a standby.
-: 2580: *
-: 2581: * Attempt to send all WAL that has already been replayed, so that we
-: 2582: * know it's valid. If we're receiving WAL through streaming
-: 2583: * replication, it's also OK to send any WAL that has been received
-: 2584: * but not replayed.
-: 2585: *
-: 2586: * The timeline we're recovering from can change, or we can be
-: 2587: * promoted. In either case, the current timeline becomes historic. We
-: 2588: * need to detect that so that we don't try to stream past the point
-: 2589: * where we switched to another timeline. We check for promotion or
-: 2590: * timeline switch after calculating FlushPtr, to avoid a race
-: 2591: * condition: if the timeline becomes historic just after we checked
-: 2592: * that it was still current, it's still be OK to stream it up to the
-: 2593: * FlushPtr that was calculated before it became historic.
-: 2594: */
149: 2595: bool becameHistoric = false;
-: 2596:
149: 2597: SendRqstPtr = GetStandbyFlushRecPtr();
call 0 returned 100%
-: 2598:
149: 2599: if (!RecoveryInProgress())
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
-: 2600: {
-: 2601: /*
-: 2602: * We have been promoted. RecoveryInProgress() updated
-: 2603: * ThisTimeLineID to the new current timeline.
-: 2604: */
#####: 2605: am_cascading_walsender = false;
#####: 2606: becameHistoric = true;
-: 2607: }
-: 2608: else
-: 2609: {
-: 2610: /*
-: 2611: * Still a cascading standby. But is the timeline we're sending
-: 2612: * still the one recovery is recovering from? ThisTimeLineID was
-: 2613: * updated by the GetStandbyFlushRecPtr() call above.
-: 2614: */
149: 2615: if (sendTimeLine != ThisTimeLineID)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2616: becameHistoric = true;
-: 2617: }
-: 2618:
149: 2619: if (becameHistoric)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 2620: {
-: 2621: /*
-: 2622: * The timeline we were sending has become historic. Read the
-: 2623: * timeline history file of the new timeline to see where exactly
-: 2624: * we forked off from the timeline we were sending.
-: 2625: */
-: 2626: List *history;
-: 2627:
#####: 2628: history = readTimeLineHistory(ThisTimeLineID);
call 0 never executed
#####: 2629: sendTimeLineValidUpto = tliSwitchPoint(sendTimeLine, history, &sendTimeLineNextTLI);
call 0 never executed
-: 2630:
#####: 2631: Assert(sendTimeLine < sendTimeLineNextTLI);
branch 0 never executed
branch 1 never executed
call 2 never executed
#####: 2632: list_free_deep(history);
call 0 never executed
-: 2633:
#####: 2634: sendTimeLineIsHistoric = true;
-: 2635:
#####: 2636: SendRqstPtr = sendTimeLineValidUpto;
-: 2637: }
-: 2638: }
-: 2639: else
-: 2640: {
-: 2641: /*
-: 2642: * Streaming the current timeline on a master.
-: 2643: *
-: 2644: * Attempt to send all data that's already been written out and
-: 2645: * fsync'd to disk. We cannot go further than what's been written out
-: 2646: * given the current implementation of XLogRead(). And in any case
-: 2647: * it's unsafe to send WAL that is not securely down to disk on the
-: 2648: * master: if the master subsequently crashes and restarts, standbys
-: 2649: * must not have applied any WAL that got lost on the master.
-: 2650: */
841: 2651: SendRqstPtr = GetFlushRecPtr();
call 0 returned 100%
-: 2652: }
-: 2653:
-: 2654: /*
-: 2655: * Record the current system time as an approximation of the time at which
-: 2656: * this WAL location was written for the purposes of lag tracking.
-: 2657: *
-: 2658: * In theory we could make XLogFlush() record a time in shmem whenever WAL
-: 2659: * is flushed and we could get that time as well as the LSN when we call
-: 2660: * GetFlushRecPtr() above (and likewise for the cascading standby
-: 2661: * equivalent), but rather than putting any new code into the hot WAL path
-: 2662: * it seems good enough to capture the time here. We should reach this
-: 2663: * after XLogFlush() runs WalSndWakeupProcessRequests(), and although that
-: 2664: * may take some time, we read the WAL flush pointer and take the time
-: 2665: * very close to together here so that we'll get a later position if it is
-: 2666: * still moving.
-: 2667: *
-: 2668: * Because LagTrackerWrite ignores samples when the LSN hasn't advanced,
-: 2669: * this gives us a cheap approximation for the WAL flush time for this
-: 2670: * LSN.
-: 2671: *
-: 2672: * Note that the LSN is not necessarily the LSN for the data contained in
-: 2673: * the present message; it's the end of the WAL, which might be further
-: 2674: * ahead. All the lag tracking machinery cares about is finding out when
-: 2675: * that arbitrary LSN is eventually reported as written, flushed and
-: 2676: * applied, so that it can measure the elapsed time.
-: 2677: */
1006: 2678: LagTrackerWrite(SendRqstPtr, GetCurrentTimestamp());
call 0 returned 100%
call 1 returned 100%
-: 2679:
-: 2680: /*
-: 2681: * If this is a historic timeline and we've reached the point where we
-: 2682: * forked to the next timeline, stop streaming.
-: 2683: *
-: 2684: * Note: We might already have sent WAL > sendTimeLineValidUpto. The
-: 2685: * startup process will normally replay all WAL that has been received
-: 2686: * from the master, before promoting, but if the WAL streaming is
-: 2687: * terminated at a WAL page boundary, the valid portion of the timeline
-: 2688: * might end in the middle of a WAL record. We might've already sent the
-: 2689: * first half of that partial WAL record to the cascading standby, so that
-: 2690: * sentPtr > sendTimeLineValidUpto. That's OK; the cascading standby can't
-: 2691: * replay the partial WAL record either, so it can still follow our
-: 2692: * timeline switch.
-: 2693: */
1006: 2694: if (sendTimeLineIsHistoric && sendTimeLineValidUpto <= sentPtr)
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
branch 2 taken 31% (fallthrough)
branch 3 taken 69%
-: 2695: {
-: 2696: /* close the current file. */
5: 2697: if (sendSeg->ws_file >= 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
5: 2698: close(sendSeg->ws_file);
call 0 returned 100%
5: 2699: sendSeg->ws_file = -1;
-: 2700:
-: 2701: /* Send CopyDone */
5: 2702: pq_putmessage_noblock('c', NULL, 0);
call 0 returned 100%
5: 2703: streamingDoneSending = true;
-: 2704:
5: 2705: WalSndCaughtUp = true;
-: 2706:
5: 2707: elog(DEBUG1, "walsender reached end of timeline at %X/%X (sent up to %X/%X)",
call 0 returned 100%
call 1 returned 100%
-: 2708: (uint32) (sendTimeLineValidUpto >> 32), (uint32) sendTimeLineValidUpto,
-: 2709: (uint32) (sentPtr >> 32), (uint32) sentPtr);
5: 2710: return;
-: 2711: }
-: 2712:
-: 2713: /* Do we have any work to do? */
1001: 2714: Assert(sentPtr <= SendRqstPtr);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1001: 2715: if (SendRqstPtr <= sentPtr)
branch 0 taken 37% (fallthrough)
branch 1 taken 63%
-: 2716: {
366: 2717: WalSndCaughtUp = true;
366: 2718: return;
-: 2719: }
-: 2720:
-: 2721: /*
-: 2722: * Figure out how much to send in one message. If there's no more than
-: 2723: * MAX_SEND_SIZE bytes to send, send everything. Otherwise send
-: 2724: * MAX_SEND_SIZE bytes, but round back to logfile or page boundary.
-: 2725: *
-: 2726: * The rounding is not only for performance reasons. Walreceiver relies on
-: 2727: * the fact that we never split a WAL record across two messages. Since a
-: 2728: * long WAL record is split at page boundary into continuation records,
-: 2729: * page boundary is always a safe cut-off point. We also assume that
-: 2730: * SendRqstPtr never points to the middle of a WAL record.
-: 2731: */
635: 2732: startptr = sentPtr;
635: 2733: endptr = startptr;
635: 2734: endptr += MAX_SEND_SIZE;
-: 2735:
-: 2736: /* if we went beyond SendRqstPtr, back off */
635: 2737: if (SendRqstPtr <= endptr)
branch 0 taken 29% (fallthrough)
branch 1 taken 71%
-: 2738: {
184: 2739: endptr = SendRqstPtr;
184: 2740: if (sendTimeLineIsHistoric)
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
5: 2741: WalSndCaughtUp = false;
-: 2742: else
179: 2743: WalSndCaughtUp = true;
-: 2744: }
-: 2745: else
-: 2746: {
-: 2747: /* round down to page boundary. */
451: 2748: endptr -= (endptr % XLOG_BLCKSZ);
451: 2749: WalSndCaughtUp = false;
-: 2750: }
-: 2751:
635: 2752: nbytes = endptr - startptr;
635: 2753: Assert(nbytes <= MAX_SEND_SIZE);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2754:
-: 2755: /*
-: 2756: * OK to read and send the slice.
-: 2757: */
635: 2758: resetStringInfo(&output_message);
call 0 returned 100%
635: 2759: pq_sendbyte(&output_message, 'w');
call 0 returned 100%
-: 2760:
635: 2761: pq_sendint64(&output_message, startptr); /* dataStart */
call 0 returned 100%
635: 2762: pq_sendint64(&output_message, SendRqstPtr); /* walEnd */
call 0 returned 100%
635: 2763: pq_sendint64(&output_message, 0); /* sendtime, filled in last */
call 0 returned 100%
-: 2764:
-: 2765: /*
-: 2766: * Read the log directly into the output buffer to avoid extra memcpy
-: 2767: * calls.
-: 2768: */
635: 2769: enlargeStringInfo(&output_message, nbytes);
call 0 returned 100%
635: 2770: XLogRead(sendCxt, &output_message.data[output_message.len], startptr, nbytes);
call 0 returned 100%
635: 2771: output_message.len += nbytes;
635: 2772: output_message.data[output_message.len] = '\0';
-: 2773:
-: 2774: /*
-: 2775: * Fill the send timestamp last, so that it is taken as late as possible.
-: 2776: */
635: 2777: resetStringInfo(&tmpbuf);
call 0 returned 100%
635: 2778: pq_sendint64(&tmpbuf, GetCurrentTimestamp());
call 0 returned 100%
call 1 returned 100%
635: 2779: memcpy(&output_message.data[1 + sizeof(int64) + sizeof(int64)],
635: 2780: tmpbuf.data, sizeof(int64));
-: 2781:
635: 2782: pq_putmessage_noblock('d', output_message.data, output_message.len);
call 0 returned 100%
-: 2783:
635: 2784: sentPtr = endptr;
-: 2785:
-: 2786: /* Update shared memory status */
-: 2787: {
635: 2788: WalSnd *walsnd = MyWalSnd;
-: 2789:
635: 2790: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
635: 2791: walsnd->sentPtr = sentPtr;
635: 2792: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 2793: }
-: 2794:
-: 2795: /* Report progress of XLOG streaming in PS display */
635: 2796: if (update_process_title)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 2797: {
-: 2798: char activitymsg[50];
-: 2799:
1270: 2800: snprintf(activitymsg, sizeof(activitymsg), "streaming %X/%X",
call 0 returned 100%
635: 2801: (uint32) (sentPtr >> 32), (uint32) sentPtr);
635: 2802: set_ps_display(activitymsg, false);
call 0 returned 100%
-: 2803: }
-: 2804:
635: 2805: return;
-: 2806:}
-: 2807:
-: 2808:/*
-: 2809: * Stream out logically decoded data.
-: 2810: */
-: 2811:static void
function XLogSendLogical called 16688 returned 99% blocks executed 80%
16688: 2812:XLogSendLogical(void)
-: 2813:{
-: 2814: XLogRecord *record;
-: 2815: char *errm;
-: 2816: XLogRecPtr flushPtr;
-: 2817:
-: 2818: /*
-: 2819: * Don't know whether we've caught up yet. We'll set WalSndCaughtUp to
-: 2820: * true in WalSndWaitForWal, if we're actually waiting. We also set to
-: 2821: * true if XLogReadRecord() had to stop reading but WalSndWaitForWal
-: 2822: * didn't wait - i.e. when we're shutting down.
-: 2823: */
16688: 2824: WalSndCaughtUp = false;
-: 2825:
16688: 2826: record = XLogReadRecord(logical_decoding_ctx->reader, logical_startptr, &errm);
call 0 returned 99%
16672: 2827: logical_startptr = InvalidXLogRecPtr;
-: 2828:
-: 2829: /* xlog record was invalid */
16672: 2830: if (errm != NULL)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2831: elog(ERROR, "%s", errm);
call 0 never executed
call 1 never executed
call 2 never executed
-: 2832:
-: 2833: /*
-: 2834: * We'll use the current flush point to determine whether we've caught up.
-: 2835: */
16672: 2836: flushPtr = GetFlushRecPtr();
call 0 returned 100%
-: 2837:
16672: 2838: if (record != NULL)
branch 0 taken 57% (fallthrough)
branch 1 taken 43%
-: 2839: {
-: 2840: /*
-: 2841: * Note the lack of any call to LagTrackerWrite() which is handled by
-: 2842: * WalSndUpdateProgress which is called by output plugin through
-: 2843: * logical decoding write api.
-: 2844: */
9508: 2845: LogicalDecodingProcessRecord(logical_decoding_ctx, logical_decoding_ctx->reader);
call 0 returned 100%
-: 2846:
9508: 2847: sentPtr = logical_decoding_ctx->reader->EndRecPtr;
-: 2848: }
-: 2849:
-: 2850: /* Set flag if we're caught up. */
16672: 2851: if (logical_decoding_ctx->reader->EndRecPtr >= flushPtr)
branch 0 taken 44% (fallthrough)
branch 1 taken 56%
7342: 2852: WalSndCaughtUp = true;
-: 2853:
-: 2854: /*
-: 2855: * If we're caught up and have been requested to stop, have WalSndLoop()
-: 2856: * terminate the connection in an orderly manner, after writing out all
-: 2857: * the pending data.
-: 2858: */
16672: 2859: if (WalSndCaughtUp && got_STOPPING)
branch 0 taken 44% (fallthrough)
branch 1 taken 56%
branch 2 taken 97% (fallthrough)
branch 3 taken 3%
7160: 2860: got_SIGUSR2 = true;
-: 2861:
-: 2862: /* Update shared memory status */
-: 2863: {
16672: 2864: WalSnd *walsnd = MyWalSnd;
-: 2865:
16672: 2866: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
16672: 2867: walsnd->sentPtr = sentPtr;
16672: 2868: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 2869: }
16672: 2870:}
-: 2871:
-: 2872:/*
-: 2873: * Shutdown if the sender is caught up.
-: 2874: *
-: 2875: * NB: This should only be called when the shutdown signal has been received
-: 2876: * from postmaster.
-: 2877: *
-: 2878: * Note that if we determine that there's still more data to send, this
-: 2879: * function will return control to the caller.
-: 2880: */
-: 2881:static void
function WalSndDone called 3594 returned 99% blocks executed 93%
3594: 2882:WalSndDone(WalSndSendDataCallback send_data)
-: 2883:{
-: 2884: XLogRecPtr replicatedPtr;
-: 2885:
-: 2886: /* ... let's just be real sure we're caught up ... */
3594: 2887: send_data();
call 0 returned 100%
-: 2888:
-: 2889: /*
-: 2890: * To figure out whether all WAL has successfully been replicated, check
-: 2891: * flush location if valid, write otherwise. Tools like pg_receivewal will
-: 2892: * usually (unless in synchronous mode) return an invalid flush location.
-: 2893: */
7188: 2894: replicatedPtr = XLogRecPtrIsInvalid(MyWalSnd->flush) ?
3594: 2895: MyWalSnd->write : MyWalSnd->flush;
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 2896:
3610: 2897: if (WalSndCaughtUp && sentPtr == replicatedPtr &&
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 1% (fallthrough)
branch 3 taken 99%
branch 4 taken 100% (fallthrough)
branch 5 taken 0%
16: 2898: !pq_is_send_pending())
call 0 returned 100%
-: 2899: {
-: 2900: /* Inform the standby that XLOG streaming is done */
16: 2901: EndCommand("COPY 0", DestRemote);
call 0 returned 100%
16: 2902: pq_flush();
call 0 returned 100%
-: 2903:
16: 2904: proc_exit(0);
call 0 returned 0%
-: 2905: }
3578: 2906: if (!waiting_for_ping_response)
branch 0 taken 20% (fallthrough)
branch 1 taken 80%
-: 2907: {
712: 2908: WalSndKeepalive(true);
call 0 returned 100%
712: 2909: waiting_for_ping_response = true;
-: 2910: }
3578: 2911:}
-: 2912:
-: 2913:/*
-: 2914: * Returns the latest point in WAL that has been safely flushed to disk, and
-: 2915: * can be sent to the standby. This should only be called when in recovery,
-: 2916: * ie. we're streaming to a cascaded standby.
-: 2917: *
-: 2918: * As a side-effect, ThisTimeLineID is updated to the TLI of the last
-: 2919: * replayed WAL record.
-: 2920: */
-: 2921:static XLogRecPtr
function GetStandbyFlushRecPtr called 157 returned 100% blocks executed 100%
157: 2922:GetStandbyFlushRecPtr(void)
-: 2923:{
-: 2924: XLogRecPtr replayPtr;
-: 2925: TimeLineID replayTLI;
-: 2926: XLogRecPtr receivePtr;
-: 2927: TimeLineID receiveTLI;
-: 2928: XLogRecPtr result;
-: 2929:
-: 2930: /*
-: 2931: * We can safely send what's already been replayed. Also, if walreceiver
-: 2932: * is streaming WAL from the same timeline, we can send anything that it
-: 2933: * has streamed, but hasn't been replayed yet.
-: 2934: */
-: 2935:
157: 2936: receivePtr = GetWalRcvWriteRecPtr(NULL, &receiveTLI);
call 0 returned 100%
157: 2937: replayPtr = GetXLogReplayRecPtr(&replayTLI);
call 0 returned 100%
-: 2938:
157: 2939: ThisTimeLineID = replayTLI;
-: 2940:
157: 2941: result = replayPtr;
157: 2942: if (receiveTLI == ThisTimeLineID && receivePtr > replayPtr)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 5% (fallthrough)
branch 3 taken 95%
8: 2943: result = receivePtr;
-: 2944:
157: 2945: return result;
-: 2946:}
-: 2947:
-: 2948:/*
-: 2949: * Request walsenders to reload the currently-open WAL file
-: 2950: */
-: 2951:void
function WalSndRqstFileReload called 5 returned 100% blocks executed 77%
5: 2952:WalSndRqstFileReload(void)
-: 2953:{
-: 2954: int i;
-: 2955:
27: 2956: for (i = 0; i < max_wal_senders; i++)
branch 0 taken 81%
branch 1 taken 19% (fallthrough)
-: 2957: {
22: 2958: WalSnd *walsnd = &WalSndCtl->walsnds[i];
-: 2959:
22: 2960: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
22: 2961: if (walsnd->pid == 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 2962: {
22: 2963: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
22: 2964: continue;
-: 2965: }
#####: 2966: walsnd->needreload = true;
#####: 2967: SpinLockRelease(&walsnd->mutex);
call 0 never executed
-: 2968: }
5: 2969:}
-: 2970:
-: 2971:/*
-: 2972: * Handle PROCSIG_WALSND_INIT_STOPPING signal.
-: 2973: */
-: 2974:void
function HandleWalSndInitStopping called 16 returned 100% blocks executed 67%
16: 2975:HandleWalSndInitStopping(void)
-: 2976:{
16: 2977: Assert(am_walsender);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 2978:
-: 2979: /*
-: 2980: * If replication has not yet started, die like with SIGTERM. If
-: 2981: * replication is active, only set a flag and wake up the main loop. It
-: 2982: * will send any outstanding WAL, wait for it to be replicated to the
-: 2983: * standby, and then exit gracefully.
-: 2984: */
16: 2985: if (!replication_active)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 2986: kill(MyProcPid, SIGTERM);
call 0 never executed
-: 2987: else
16: 2988: got_STOPPING = true;
16: 2989:}
-: 2990:
-: 2991:/*
-: 2992: * SIGUSR2: set flag to do a last cycle and shut down afterwards. The WAL
-: 2993: * sender should already have been switched to WALSNDSTATE_STOPPING at
-: 2994: * this point.
-: 2995: */
-: 2996:static void
function WalSndLastCycleHandler called 14 returned 100% blocks executed 100%
14: 2997:WalSndLastCycleHandler(SIGNAL_ARGS)
-: 2998:{
14: 2999: int save_errno = errno;
call 0 returned 100%
-: 3000:
14: 3001: got_SIGUSR2 = true;
14: 3002: SetLatch(MyLatch);
call 0 returned 100%
-: 3003:
14: 3004: errno = save_errno;
call 0 returned 100%
14: 3005:}
-: 3006:
-: 3007:/* Set up signal handlers */
-: 3008:void
function WalSndSignals called 231 returned 100% blocks executed 100%
231: 3009:WalSndSignals(void)
-: 3010:{
-: 3011: /* Set up signal handlers */
231: 3012: pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config
call 0 returned 100%
-: 3013: * file */
231: 3014: pqsignal(SIGINT, StatementCancelHandler); /* query cancel */
call 0 returned 100%
231: 3015: pqsignal(SIGTERM, die); /* request shutdown */
call 0 returned 100%
231: 3016: pqsignal(SIGQUIT, quickdie); /* hard crash time */
call 0 returned 100%
231: 3017: InitializeTimeouts(); /* establishes SIGALRM handler */
call 0 returned 100%
231: 3018: pqsignal(SIGPIPE, SIG_IGN);
call 0 returned 100%
231: 3019: pqsignal(SIGUSR1, procsignal_sigusr1_handler);
call 0 returned 100%
231: 3020: pqsignal(SIGUSR2, WalSndLastCycleHandler); /* request a last cycle and
call 0 returned 100%
-: 3021: * shutdown */
-: 3022:
-: 3023: /* Reset some signals that are accepted by postmaster but not here */
231: 3024: pqsignal(SIGCHLD, SIG_DFL);
call 0 returned 100%
231: 3025:}
-: 3026:
-: 3027:/* Report shared-memory space needed by WalSndShmemInit */
-: 3028:Size
function WalSndShmemSize called 18629 returned 100% blocks executed 100%
18629: 3029:WalSndShmemSize(void)
-: 3030:{
18629: 3031: Size size = 0;
-: 3032:
18629: 3033: size = offsetof(WalSndCtlData, walsnds);
18629: 3034: size = add_size(size, mul_size(max_wal_senders, sizeof(WalSnd)));
call 0 returned 100%
call 1 returned 100%
-: 3035:
18629: 3036: return size;
-: 3037:}
-: 3038:
-: 3039:/* Allocate and initialize walsender-related shared memory */
-: 3040:void
function WalSndShmemInit called 6209 returned 100% blocks executed 100%
6209: 3041:WalSndShmemInit(void)
-: 3042:{
-: 3043: bool found;
-: 3044: int i;
-: 3045:
6209: 3046: WalSndCtl = (WalSndCtlData *)
6209: 3047: ShmemInitStruct("Wal Sender Ctl", WalSndShmemSize(), &found);
call 0 returned 100%
call 1 returned 100%
-: 3048:
6209: 3049: if (!found)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
-: 3050: {
-: 3051: /* First time through, so initialize */
6209: 3052: MemSet(WalSndCtl, 0, WalSndShmemSize());
call 0 returned 100%
branch 1 taken 100% (fallthrough)
branch 2 taken 0%
branch 3 taken 100% (fallthrough)
branch 4 taken 0%
branch 5 taken 100% (fallthrough)
branch 6 taken 0%
branch 7 taken 44% (fallthrough)
branch 8 taken 56%
branch 9 taken 98%
branch 10 taken 2% (fallthrough)
-: 3053:
24836: 3054: for (i = 0; i < NUM_SYNC_REP_WAIT_MODE; i++)
branch 0 taken 75%
branch 1 taken 25% (fallthrough)
18627: 3055: SHMQueueInit(&(WalSndCtl->SyncRepQueue[i]));
call 0 returned 100%
-: 3056:
50689: 3057: for (i = 0; i < max_wal_senders; i++)
branch 0 taken 88%
branch 1 taken 12% (fallthrough)
-: 3058: {
44480: 3059: WalSnd *walsnd = &WalSndCtl->walsnds[i];
-: 3060:
44480: 3061: SpinLockInit(&walsnd->mutex);
call 0 returned 100%
-: 3062: }
-: 3063: }
6209: 3064:}
-: 3065:
-: 3066:/*
-: 3067: * Wake up all walsenders
-: 3068: *
-: 3069: * This will be called inside critical sections, so throwing an error is not
-: 3070: * advisable.
-: 3071: */
-: 3072:void
function WalSndWakeup called 112264 returned 100% blocks executed 91%
112264: 3073:WalSndWakeup(void)
-: 3074:{
-: 3075: int i;
-: 3076:
1227628: 3077: for (i = 0; i < max_wal_senders; i++)
branch 0 taken 91%
branch 1 taken 9% (fallthrough)
-: 3078: {
-: 3079: Latch *latch;
1115364: 3080: WalSnd *walsnd = &WalSndCtl->walsnds[i];
-: 3081:
-: 3082: /*
-: 3083: * Get latch pointer with spinlock held, for the unlikely case that
-: 3084: * pointer reads aren't atomic (as they're 8 bytes).
-: 3085: */
1115364: 3086: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
1115364: 3087: latch = walsnd->latch;
1115364: 3088: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 3089:
1115364: 3090: if (latch != NULL)
branch 0 taken 1% (fallthrough)
branch 1 taken 99%
636: 3091: SetLatch(latch);
call 0 returned 100%
-: 3092: }
112264: 3093:}
-: 3094:
-: 3095:/*
-: 3096: * Signal all walsenders to move to stopping state.
-: 3097: *
-: 3098: * This will trigger walsenders to move to a state where no further WAL can be
-: 3099: * generated. See this file's header for details.
-: 3100: */
-: 3101:void
function WalSndInitStopping called 465 returned 100% blocks executed 92%
465: 3102:WalSndInitStopping(void)
-: 3103:{
-: 3104: int i;
-: 3105:
4590: 3106: for (i = 0; i < max_wal_senders; i++)
branch 0 taken 90%
branch 1 taken 10% (fallthrough)
-: 3107: {
4125: 3108: WalSnd *walsnd = &WalSndCtl->walsnds[i];
-: 3109: pid_t pid;
-: 3110:
4125: 3111: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
4125: 3112: pid = walsnd->pid;
4125: 3113: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 3114:
4125: 3115: if (pid == 0)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
4109: 3116: continue;
-: 3117:
16: 3118: SendProcSignal(pid, PROCSIG_WALSND_INIT_STOPPING, InvalidBackendId);
call 0 returned 100%
-: 3119: }
465: 3120:}
-: 3121:
-: 3122:/*
-: 3123: * Wait that all the WAL senders have quit or reached the stopping state. This
-: 3124: * is used by the checkpointer to control when the shutdown checkpoint can
-: 3125: * safely be performed.
-: 3126: */
-: 3127:void
function WalSndWaitStopping called 465 returned 100% blocks executed 95%
488: 3128:WalSndWaitStopping(void)
-: 3129:{
-: 3130: for (;;)
-: 3131: {
-: 3132: int i;
488: 3133: bool all_stopped = true;
-: 3134:
4613: 3135: for (i = 0; i < max_wal_senders; i++)
branch 0 taken 90%
branch 1 taken 10% (fallthrough)
-: 3136: {
4148: 3137: WalSnd *walsnd = &WalSndCtl->walsnds[i];
-: 3138:
4148: 3139: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
-: 3140:
4148: 3141: if (walsnd->pid == 0)
branch 0 taken 99% (fallthrough)
branch 1 taken 1%
-: 3142: {
4111: 3143: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
4111: 3144: continue;
-: 3145: }
-: 3146:
37: 3147: if (walsnd->state != WALSNDSTATE_STOPPING)
branch 0 taken 62% (fallthrough)
branch 1 taken 38%
-: 3148: {
23: 3149: all_stopped = false;
23: 3150: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
23: 3151: break;
-: 3152: }
14: 3153: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 3154: }
-: 3155:
-: 3156: /* safe to leave if confirmation is done for all WAL senders */
488: 3157: if (all_stopped)
branch 0 taken 95% (fallthrough)
branch 1 taken 5%
930: 3158: return;
-: 3159:
23: 3160: pg_usleep(10000L); /* wait for 10 msec */
call 0 returned 100%
23: 3161: }
-: 3162:}
-: 3163:
-: 3164:/* Set state for current walsender (only called in walsender) */
-: 3165:void
function WalSndSetState called 419 returned 100% blocks executed 82%
419: 3166:WalSndSetState(WalSndState state)
-: 3167:{
419: 3168: WalSnd *walsnd = MyWalSnd;
-: 3169:
419: 3170: Assert(am_walsender);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3171:
419: 3172: if (walsnd->state == state)
branch 0 taken 17% (fallthrough)
branch 1 taken 83%
489: 3173: return;
-: 3174:
349: 3175: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
349: 3176: walsnd->state = state;
349: 3177: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 3178:}
-: 3179:
-: 3180:/*
-: 3181: * Return a string constant representing the state. This is used
-: 3182: * in system views, and should *not* be translated.
-: 3183: */
-: 3184:static const char *
function WalSndGetStateString called 151 returned 100% blocks executed 50%
151: 3185:WalSndGetStateString(WalSndState state)
-: 3186:{
151: 3187: switch (state)
branch 0 taken 3%
branch 1 taken 0%
branch 2 taken 0%
branch 3 taken 97%
branch 4 taken 0%
branch 5 taken 0%
-: 3188: {
-: 3189: case WALSNDSTATE_STARTUP:
4: 3190: return "startup";
-: 3191: case WALSNDSTATE_BACKUP:
#####: 3192: return "backup";
-: 3193: case WALSNDSTATE_CATCHUP:
#####: 3194: return "catchup";
-: 3195: case WALSNDSTATE_STREAMING:
147: 3196: return "streaming";
-: 3197: case WALSNDSTATE_STOPPING:
#####: 3198: return "stopping";
-: 3199: }
#####: 3200: return "UNKNOWN";
-: 3201:}
-: 3202:
-: 3203:static Interval *
function offset_to_interval called 242 returned 100% blocks executed 100%
242: 3204:offset_to_interval(TimeOffset offset)
-: 3205:{
242: 3206: Interval *result = palloc(sizeof(Interval));
call 0 returned 100%
-: 3207:
242: 3208: result->month = 0;
242: 3209: result->day = 0;
242: 3210: result->time = offset;
-: 3211:
242: 3212: return result;
-: 3213:}
-: 3214:
-: 3215:/*
-: 3216: * Returns activity of walsenders, including pids and xlog locations sent to
-: 3217: * standby servers.
-: 3218: */
-: 3219:Datum
function pg_stat_get_wal_senders called 116 returned 100% blocks executed 71%
116: 3220:pg_stat_get_wal_senders(PG_FUNCTION_ARGS)
-: 3221:{
-: 3222:#define PG_STAT_GET_WAL_SENDERS_COLS 12
116: 3223: ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-: 3224: TupleDesc tupdesc;
-: 3225: Tuplestorestate *tupstore;
-: 3226: MemoryContext per_query_ctx;
-: 3227: MemoryContext oldcontext;
-: 3228: List *sync_standbys;
-: 3229: int i;
-: 3230:
-: 3231: /* check to see if caller supports us returning a tuplestore */
116: 3232: if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 3233: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 3234: (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-: 3235: errmsg("set-valued function called in context that cannot accept a set")));
116: 3236: if (!(rsinfo->allowedModes & SFRM_Materialize))
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3237: ereport(ERROR,
call 0 never executed
branch 1 never executed
branch 2 never executed
call 3 never executed
call 4 never executed
call 5 never executed
call 6 never executed
-: 3238: (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-: 3239: errmsg("materialize mode required, but it is not " \
-: 3240: "allowed in this context")));
-: 3241:
-: 3242: /* Build a tuple descriptor for our result type */
116: 3243: if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
#####: 3244: elog(ERROR, "return type must be a row type");
call 0 never executed
call 1 never executed
call 2 never executed
-: 3245:
116: 3246: per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
116: 3247: oldcontext = MemoryContextSwitchTo(per_query_ctx);
call 0 returned 100%
-: 3248:
116: 3249: tupstore = tuplestore_begin_heap(true, false, work_mem);
call 0 returned 100%
116: 3250: rsinfo->returnMode = SFRM_Materialize;
116: 3251: rsinfo->setResult = tupstore;
116: 3252: rsinfo->setDesc = tupdesc;
-: 3253:
116: 3254: MemoryContextSwitchTo(oldcontext);
call 0 returned 100%
-: 3255:
-: 3256: /*
-: 3257: * Get the currently active synchronous standbys.
-: 3258: */
116: 3259: LWLockAcquire(SyncRepLock, LW_SHARED);
call 0 returned 100%
116: 3260: sync_standbys = SyncRepGetSyncStandbys(NULL);
call 0 returned 100%
116: 3261: LWLockRelease(SyncRepLock);
call 0 returned 100%
-: 3262:
693: 3263: for (i = 0; i < max_wal_senders; i++)
branch 0 taken 83%
branch 1 taken 17% (fallthrough)
-: 3264: {
577: 3265: WalSnd *walsnd = &WalSndCtl->walsnds[i];
-: 3266: XLogRecPtr sentPtr;
-: 3267: XLogRecPtr write;
-: 3268: XLogRecPtr flush;
-: 3269: XLogRecPtr apply;
-: 3270: TimeOffset writeLag;
-: 3271: TimeOffset flushLag;
-: 3272: TimeOffset applyLag;
-: 3273: int priority;
-: 3274: int pid;
-: 3275: WalSndState state;
-: 3276: TimestampTz replyTime;
-: 3277: Datum values[PG_STAT_GET_WAL_SENDERS_COLS];
-: 3278: bool nulls[PG_STAT_GET_WAL_SENDERS_COLS];
-: 3279:
577: 3280: SpinLockAcquire(&walsnd->mutex);
call 0 returned 100%
branch 1 taken 0% (fallthrough)
branch 2 taken 100%
call 3 never executed
577: 3281: if (walsnd->pid == 0)
branch 0 taken 74% (fallthrough)
branch 1 taken 26%
-: 3282: {
426: 3283: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
426: 3284: continue;
-: 3285: }
151: 3286: pid = walsnd->pid;
151: 3287: sentPtr = walsnd->sentPtr;
151: 3288: state = walsnd->state;
151: 3289: write = walsnd->write;
151: 3290: flush = walsnd->flush;
151: 3291: apply = walsnd->apply;
151: 3292: writeLag = walsnd->writeLag;
151: 3293: flushLag = walsnd->flushLag;
151: 3294: applyLag = walsnd->applyLag;
151: 3295: priority = walsnd->sync_standby_priority;
151: 3296: replyTime = walsnd->replyTime;
151: 3297: SpinLockRelease(&walsnd->mutex);
call 0 returned 100%
-: 3298:
151: 3299: memset(nulls, 0, sizeof(nulls));
151: 3300: values[0] = Int32GetDatum(pid);
-: 3301:
151: 3302: if (!is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_STATS))
call 0 returned 100%
call 1 returned 100%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
-: 3303: {
-: 3304: /*
-: 3305: * Only superusers and members of pg_read_all_stats can see
-: 3306: * details. Other users only get the pid value to know it's a
-: 3307: * walsender, but no details.
-: 3308: */
#####: 3309: MemSet(&nulls[1], true, PG_STAT_GET_WAL_SENDERS_COLS - 1);
branch 0 never executed
branch 1 never executed
branch 2 never executed
branch 3 never executed
branch 4 never executed
branch 5 never executed
branch 6 never executed
branch 7 never executed
branch 8 never executed
branch 9 never executed
-: 3310: }
-: 3311: else
-: 3312: {
151: 3313: values[1] = CStringGetTextDatum(WalSndGetStateString(state));
call 0 returned 100%
call 1 returned 100%
-: 3314:
151: 3315: if (XLogRecPtrIsInvalid(sentPtr))
branch 0 taken 3% (fallthrough)
branch 1 taken 97%
4: 3316: nulls[2] = true;
151: 3317: values[2] = LSNGetDatum(sentPtr);
-: 3318:
151: 3319: if (XLogRecPtrIsInvalid(write))
branch 0 taken 8% (fallthrough)
branch 1 taken 92%
12: 3320: nulls[3] = true;
151: 3321: values[3] = LSNGetDatum(write);
-: 3322:
151: 3323: if (XLogRecPtrIsInvalid(flush))
branch 0 taken 8% (fallthrough)
branch 1 taken 92%
12: 3324: nulls[4] = true;
151: 3325: values[4] = LSNGetDatum(flush);
-: 3326:
151: 3327: if (XLogRecPtrIsInvalid(apply))
branch 0 taken 8% (fallthrough)
branch 1 taken 92%
12: 3328: nulls[5] = true;
151: 3329: values[5] = LSNGetDatum(apply);
-: 3330:
-: 3331: /*
-: 3332: * Treat a standby such as a pg_basebackup background process
-: 3333: * which always returns an invalid flush location, as an
-: 3334: * asynchronous standby.
-: 3335: */
151: 3336: priority = XLogRecPtrIsInvalid(flush) ? 0 : priority;
branch 0 taken 92% (fallthrough)
branch 1 taken 8%
-: 3337:
151: 3338: if (writeLag < 0)
branch 0 taken 48% (fallthrough)
branch 1 taken 52%
72: 3339: nulls[6] = true;
-: 3340: else
79: 3341: values[6] = IntervalPGetDatum(offset_to_interval(writeLag));
call 0 returned 100%
-: 3342:
151: 3343: if (flushLag < 0)
branch 0 taken 44% (fallthrough)
branch 1 taken 56%
67: 3344: nulls[7] = true;
-: 3345: else
84: 3346: values[7] = IntervalPGetDatum(offset_to_interval(flushLag));
call 0 returned 100%
-: 3347:
151: 3348: if (applyLag < 0)
branch 0 taken 48% (fallthrough)
branch 1 taken 52%
72: 3349: nulls[8] = true;
-: 3350: else
79: 3351: values[8] = IntervalPGetDatum(offset_to_interval(applyLag));
call 0 returned 100%
-: 3352:
151: 3353: values[9] = Int32GetDatum(priority);
-: 3354:
-: 3355: /*
-: 3356: * More easily understood version of standby state. This is purely
-: 3357: * informational.
-: 3358: *
-: 3359: * In quorum-based sync replication, the role of each standby
-: 3360: * listed in synchronous_standby_names can be changing very
-: 3361: * frequently. Any standbys considered as "sync" at one moment can
-: 3362: * be switched to "potential" ones at the next moment. So, it's
-: 3363: * basically useless to report "sync" or "potential" as their sync
-: 3364: * states. We report just "quorum" for them.
-: 3365: */
151: 3366: if (priority == 0)
branch 0 taken 77% (fallthrough)
branch 1 taken 23%
117: 3367: values[10] = CStringGetTextDatum("async");
call 0 returned 100%
34: 3368: else if (list_member_int(sync_standbys, i))
call 0 returned 100%
branch 1 taken 74% (fallthrough)
branch 2 taken 26%
50: 3369: values[10] = SyncRepConfig->syncrep_method == SYNC_REP_PRIORITY ?
25: 3370: CStringGetTextDatum("sync") : CStringGetTextDatum("quorum");
branch 0 taken 92% (fallthrough)
branch 1 taken 8%
call 2 returned 100%
call 3 returned 100%
-: 3371: else
9: 3372: values[10] = CStringGetTextDatum("potential");
call 0 returned 100%
-: 3373:
151: 3374: if (replyTime == 0)
branch 0 taken 7% (fallthrough)
branch 1 taken 93%
11: 3375: nulls[11] = true;
-: 3376: else
140: 3377: values[11] = TimestampTzGetDatum(replyTime);
-: 3378: }
-: 3379:
151: 3380: tuplestore_putvalues(tupstore, tupdesc, values, nulls);
call 0 returned 100%
-: 3381: }
-: 3382:
-: 3383: /* clean up and return the tuplestore */
-: 3384: tuplestore_donestoring(tupstore);
-: 3385:
116: 3386: return (Datum) 0;
-: 3387:}
-: 3388:
-: 3389:/*
-: 3390: * This function is used to send a keepalive message to standby.
-: 3391: * If requestReply is set, sets a flag in the message requesting the standby
-: 3392: * to send a message back to us, for heartbeat purposes.
-: 3393: */
-: 3394:static void
function WalSndKeepalive called 804 returned 100% blocks executed 100%
804: 3395:WalSndKeepalive(bool requestReply)
-: 3396:{
804: 3397: elog(DEBUG2, "sending replication keepalive");
call 0 returned 100%
call 1 returned 100%
-: 3398:
-: 3399: /* construct the message... */
804: 3400: resetStringInfo(&output_message);
call 0 returned 100%
804: 3401: pq_sendbyte(&output_message, 'k');
call 0 returned 100%
804: 3402: pq_sendint64(&output_message, sentPtr);
call 0 returned 100%
804: 3403: pq_sendint64(&output_message, GetCurrentTimestamp());
call 0 returned 100%
call 1 returned 100%
804: 3404: pq_sendbyte(&output_message, requestReply ? 1 : 0);
call 0 returned 100%
-: 3405:
-: 3406: /* ... and send it wrapped in CopyData */
804: 3407: pq_putmessage_noblock('d', output_message.data, output_message.len);
call 0 returned 100%
804: 3408:}
-: 3409:
-: 3410:/*
-: 3411: * Send keepalive message if too much time has elapsed.
-: 3412: */
-: 3413:static void
function WalSndKeepaliveIfNecessary called 17443 returned 100% blocks executed 55%
17443: 3414:WalSndKeepaliveIfNecessary(void)
-: 3415:{
-: 3416: TimestampTz ping_time;
-: 3417:
-: 3418: /*
-: 3419: * Don't send keepalive messages if timeouts are globally disabled or
-: 3420: * we're doing something not partaking in timeouts.
-: 3421: */
17443: 3422: if (wal_sender_timeout <= 0 || last_reply_timestamp <= 0)
branch 0 taken 100% (fallthrough)
branch 1 taken 0%
branch 2 taken 0% (fallthrough)
branch 3 taken 100%
#####: 3423: return;
-: 3424:
17443: 3425: if (waiting_for_ping_response)
branch 0 taken 57% (fallthrough)
branch 1 taken 43%
9901: 3426: return;
-: 3427:
-: 3428: /*
-: 3429: * If half of wal_sender_timeout has lapsed without receiving any reply
-: 3430: * from the standby, send a keep-alive message to the standby requesting
-: 3431: * an immediate reply.
-: 3432: */
7542: 3433: ping_time = TimestampTzPlusMilliseconds(last_reply_timestamp,
-: 3434: wal_sender_timeout / 2);
7542: 3435: if (last_processing >= ping_time)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 3436: {
#####: 3437: WalSndKeepalive(true);
call 0 never executed
#####: 3438: waiting_for_ping_response = true;
-: 3439:
-: 3440: /* Try to flush pending output to the client */
#####: 3441: if (pq_flush_if_writable() != 0)
call 0 never executed
branch 1 never executed
branch 2 never executed
#####: 3442: WalSndShutdown();
call 0 never executed
-: 3443: }
-: 3444:}
-: 3445:
-: 3446:/*
-: 3447: * Record the end of the WAL and the time it was flushed locally, so that
-: 3448: * LagTrackerRead can compute the elapsed time (lag) when this WAL location is
-: 3449: * eventually reported to have been written, flushed and applied by the
-: 3450: * standby in a reply message.
-: 3451: */
-: 3452:static void
function LagTrackerWrite called 1021 returned 100% blocks executed 67%
1021: 3453:LagTrackerWrite(XLogRecPtr lsn, TimestampTz local_flush_time)
-: 3454:{
-: 3455: bool buffer_full;
-: 3456: int new_write_head;
-: 3457: int i;
-: 3458:
1021: 3459: if (!am_walsender)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3460: return;
-: 3461:
-: 3462: /*
-: 3463: * If the lsn hasn't advanced since last time, then do nothing. This way
-: 3464: * we only record a new sample when new WAL has been written.
-: 3465: */
1021: 3466: if (lag_tracker->last_lsn == lsn)
branch 0 taken 75% (fallthrough)
branch 1 taken 25%
761: 3467: return;
260: 3468: lag_tracker->last_lsn = lsn;
-: 3469:
-: 3470: /*
-: 3471: * If advancing the write head of the circular buffer would crash into any
-: 3472: * of the read heads, then the buffer is full. In other words, the
-: 3473: * slowest reader (presumably apply) is the one that controls the release
-: 3474: * of space.
-: 3475: */
260: 3476: new_write_head = (lag_tracker->write_head + 1) % LAG_TRACKER_BUFFER_SIZE;
260: 3477: buffer_full = false;
1040: 3478: for (i = 0; i < NUM_SYNC_REP_WAIT_MODE; ++i)
branch 0 taken 75%
branch 1 taken 25% (fallthrough)
-: 3479: {
780: 3480: if (new_write_head == lag_tracker->read_heads[i])
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 3481: buffer_full = true;
-: 3482: }
-: 3483:
-: 3484: /*
-: 3485: * If the buffer is full, for now we just rewind by one slot and overwrite
-: 3486: * the last sample, as a simple (if somewhat uneven) way to lower the
-: 3487: * sampling rate. There may be better adaptive compaction algorithms.
-: 3488: */
260: 3489: if (buffer_full)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 3490: {
#####: 3491: new_write_head = lag_tracker->write_head;
#####: 3492: if (lag_tracker->write_head > 0)
branch 0 never executed
branch 1 never executed
#####: 3493: lag_tracker->write_head--;
-: 3494: else
#####: 3495: lag_tracker->write_head = LAG_TRACKER_BUFFER_SIZE - 1;
-: 3496: }
-: 3497:
-: 3498: /* Store a sample at the current write head position. */
260: 3499: lag_tracker->buffer[lag_tracker->write_head].lsn = lsn;
260: 3500: lag_tracker->buffer[lag_tracker->write_head].time = local_flush_time;
260: 3501: lag_tracker->write_head = new_write_head;
-: 3502:}
-: 3503:
-: 3504:/*
-: 3505: * Find out how much time has elapsed between the moment WAL location 'lsn'
-: 3506: * (or the highest known earlier LSN) was flushed locally and the time 'now'.
-: 3507: * We have a separate read head for each of the reported LSN locations we
-: 3508: * receive in replies from standby; 'head' controls which read head is
-: 3509: * used. Whenever a read head crosses an LSN which was written into the
-: 3510: * lag buffer with LagTrackerWrite, we can use the associated timestamp to
-: 3511: * find out the time this LSN (or an earlier one) was flushed locally, and
-: 3512: * therefore compute the lag.
-: 3513: *
-: 3514: * Return -1 if no new sample data is available, and otherwise the elapsed
-: 3515: * time in microseconds.
-: 3516: */
-: 3517:static TimeOffset
function LagTrackerRead called 4011 returned 100% blocks executed 76%
4011: 3518:LagTrackerRead(int head, XLogRecPtr lsn, TimestampTz now)
-: 3519:{
4011: 3520: TimestampTz time = 0;
-: 3521:
-: 3522: /* Read all unread samples up to this LSN or end of buffer. */
10362: 3523: while (lag_tracker->read_heads[head] != lag_tracker->write_head &&
branch 0 taken 40% (fallthrough)
branch 1 taken 60%
branch 2 taken 28%
branch 3 taken 72% (fallthrough)
1826: 3524: lag_tracker->buffer[lag_tracker->read_heads[head]].lsn <= lsn)
-: 3525: {
514: 3526: time = lag_tracker->buffer[lag_tracker->read_heads[head]].time;
1028: 3527: lag_tracker->last_read[head] =
514: 3528: lag_tracker->buffer[lag_tracker->read_heads[head]];
1028: 3529: lag_tracker->read_heads[head] =
514: 3530: (lag_tracker->read_heads[head] + 1) % LAG_TRACKER_BUFFER_SIZE;
-: 3531: }
-: 3532:
-: 3533: /*
-: 3534: * If the lag tracker is empty, that means the standby has processed
-: 3535: * everything we've ever sent so we should now clear 'last_read'. If we
-: 3536: * didn't do that, we'd risk using a stale and irrelevant sample for
-: 3537: * interpolation at the beginning of the next burst of WAL after a period
-: 3538: * of idleness.
-: 3539: */
4011: 3540: if (lag_tracker->read_heads[head] == lag_tracker->write_head)
branch 0 taken 67% (fallthrough)
branch 1 taken 33%
2699: 3541: lag_tracker->last_read[head].time = 0;
-: 3542:
4011: 3543: if (time > now)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 3544: {
-: 3545: /* If the clock somehow went backwards, treat as not found. */
#####: 3546: return -1;
-: 3547: }
4011: 3548: else if (time == 0)
branch 0 taken 88% (fallthrough)
branch 1 taken 12%
-: 3549: {
-: 3550: /*
-: 3551: * We didn't cross a time. If there is a future sample that we
-: 3552: * haven't reached yet, and we've already reached at least one sample,
-: 3553: * let's interpolate the local flushed time. This is mainly useful
-: 3554: * for reporting a completely stuck apply position as having
-: 3555: * increasing lag, since otherwise we'd have to wait for it to
-: 3556: * eventually start moving again and cross one of our samples before
-: 3557: * we can show the lag increasing.
-: 3558: */
3532: 3559: if (lag_tracker->read_heads[head] == lag_tracker->write_head)
branch 0 taken 63% (fallthrough)
branch 1 taken 37%
-: 3560: {
-: 3561: /* There are no future samples, so we can't interpolate. */
2242: 3562: return -1;
-: 3563: }
1290: 3564: else if (lag_tracker->last_read[head].time != 0)
branch 0 taken 2% (fallthrough)
branch 1 taken 98%
-: 3565: {
-: 3566: /* We can interpolate between last_read and the next sample. */
-: 3567: double fraction;
20: 3568: WalTimeSample prev = lag_tracker->last_read[head];
20: 3569: WalTimeSample next = lag_tracker->buffer[lag_tracker->read_heads[head]];
-: 3570:
20: 3571: if (lsn < prev.lsn)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 3572: {
-: 3573: /*
-: 3574: * Reported LSNs shouldn't normally go backwards, but it's
-: 3575: * possible when there is a timeline change. Treat as not
-: 3576: * found.
-: 3577: */
#####: 3578: return -1;
-: 3579: }
-: 3580:
20: 3581: Assert(prev.lsn < next.lsn);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
-: 3582:
20: 3583: if (prev.time > next.time)
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
-: 3584: {
-: 3585: /* If the clock somehow went backwards, treat as not found. */
#####: 3586: return -1;
-: 3587: }
-: 3588:
-: 3589: /* See how far we are between the previous and next samples. */
20: 3590: fraction =
20: 3591: (double) (lsn - prev.lsn) / (double) (next.lsn - prev.lsn);
-: 3592:
-: 3593: /* Scale the local flush time proportionally. */
20: 3594: time = (TimestampTz)
20: 3595: ((double) prev.time + (next.time - prev.time) * fraction);
-: 3596: }
-: 3597: else
-: 3598: {
-: 3599: /*
-: 3600: * We have only a future sample, implying that we were entirely
-: 3601: * caught up but and now there is a new burst of WAL and the
-: 3602: * standby hasn't processed the first sample yet. Until the
-: 3603: * standby reaches the future sample the best we can do is report
-: 3604: * the hypothetical lag if that sample were to be replayed now.
-: 3605: */
1270: 3606: time = lag_tracker->buffer[lag_tracker->read_heads[head]].time;
-: 3607: }
-: 3608: }
-: 3609:
-: 3610: /* Return the elapsed time since local flush time in microseconds. */
1769: 3611: Assert(time != 0);
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
call 2 never executed
1769: 3612: return now - time;
-: 3613:}