LCOV - code coverage report
Current view: top level - src/backend/replication/logical - reorderbuffer.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 93.4 % 1614 1508
Test Date: 2026-07-17 01:15:41 Functions: 100.0 % 94 94
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 75.1 % 859 645

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * reorderbuffer.c
       4                 :             :  *    PostgreSQL logical replay/reorder buffer management
       5                 :             :  *
       6                 :             :  *
       7                 :             :  * Copyright (c) 2012-2026, PostgreSQL Global Development Group
       8                 :             :  *
       9                 :             :  *
      10                 :             :  * IDENTIFICATION
      11                 :             :  *    src/backend/replication/logical/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 - signaled 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 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 lifespans).
      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 transaction. When the total amount of used memory exceeds the
      55                 :             :  *    limit, the transaction consuming the most memory is then serialized to
      56                 :             :  *    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. However, the transaction records are fairly small 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 use a max-heap with transaction size as the key to efficiently find
      71                 :             :  *    the largest transaction. We update the max-heap whenever the memory
      72                 :             :  *    counter is updated; however transactions with size 0 are not stored in
      73                 :             :  *    the heap, because they have no changes to evict.
      74                 :             :  *
      75                 :             :  *    We still rely on max_changes_in_memory when loading serialized changes
      76                 :             :  *    back into memory. At that point we can't use the memory limit directly
      77                 :             :  *    as we load the subxacts independently. One option to deal with this
      78                 :             :  *    would be to count the subxacts, and allow each to allocate 1/N of the
      79                 :             :  *    memory limit. That however does not seem very appealing, because with
      80                 :             :  *    many subtransactions it may easily cause thrashing (short cycles of
      81                 :             :  *    deserializing and applying very few changes). We probably should give
      82                 :             :  *    a bit more memory to the oldest subtransactions, because it's likely
      83                 :             :  *    they are the source for the next sequence of changes.
      84                 :             :  *
      85                 :             :  * -------------------------------------------------------------------------
      86                 :             :  */
      87                 :             : #include "postgres.h"
      88                 :             : 
      89                 :             : #include <unistd.h>
      90                 :             : #include <sys/stat.h>
      91                 :             : 
      92                 :             : #include "access/detoast.h"
      93                 :             : #include "access/heapam.h"
      94                 :             : #include "access/rewriteheap.h"
      95                 :             : #include "access/transam.h"
      96                 :             : #include "access/xact.h"
      97                 :             : #include "access/xlog_internal.h"
      98                 :             : #include "catalog/catalog.h"
      99                 :             : #include "common/int.h"
     100                 :             : #include "lib/binaryheap.h"
     101                 :             : #include "miscadmin.h"
     102                 :             : #include "pgstat.h"
     103                 :             : #include "replication/logical.h"
     104                 :             : #include "replication/reorderbuffer.h"
     105                 :             : #include "replication/slot.h"
     106                 :             : #include "replication/snapbuild.h"    /* just for SnapBuildSnapDecRefcount */
     107                 :             : #include "storage/bufmgr.h"
     108                 :             : #include "storage/fd.h"
     109                 :             : #include "storage/procarray.h"
     110                 :             : #include "storage/sinval.h"
     111                 :             : #include "utils/builtins.h"
     112                 :             : #include "utils/inval.h"
     113                 :             : #include "utils/memutils.h"
     114                 :             : #include "utils/rel.h"
     115                 :             : #include "utils/relfilenumbermap.h"
     116                 :             : #include "utils/wait_event.h"
     117                 :             : 
     118                 :             : /*
     119                 :             :  * Each transaction has an 8MB limit for invalidation messages distributed from
     120                 :             :  * other transactions. This limit is set considering scenarios with many
     121                 :             :  * concurrent logical decoding operations. When the distributed invalidation
     122                 :             :  * messages reach this threshold, the transaction is marked as
     123                 :             :  * RBTXN_DISTR_INVAL_OVERFLOWED to invalidate the complete cache as we have lost
     124                 :             :  * some inval messages and hence don't know what needs to be invalidated.
     125                 :             :  */
     126                 :             : #define MAX_DISTR_INVAL_MSG_PER_TXN \
     127                 :             :     ((8 * 1024 * 1024) / sizeof(SharedInvalidationMessage))
     128                 :             : 
     129                 :             : /* entry for a hash table we use to map from xid to our transaction state */
     130                 :             : typedef struct ReorderBufferTXNByIdEnt
     131                 :             : {
     132                 :             :     TransactionId xid;
     133                 :             :     ReorderBufferTXN *txn;
     134                 :             : } ReorderBufferTXNByIdEnt;
     135                 :             : 
     136                 :             : /* data structures for (relfilelocator, ctid) => (cmin, cmax) mapping */
     137                 :             : typedef struct ReorderBufferTupleCidKey
     138                 :             : {
     139                 :             :     RelFileLocator rlocator;
     140                 :             :     ItemPointerData tid;
     141                 :             : } ReorderBufferTupleCidKey;
     142                 :             : 
     143                 :             : typedef struct ReorderBufferTupleCidEnt
     144                 :             : {
     145                 :             :     ReorderBufferTupleCidKey key;
     146                 :             :     CommandId   cmin;
     147                 :             :     CommandId   cmax;
     148                 :             :     CommandId   combocid;       /* just for debugging */
     149                 :             : } ReorderBufferTupleCidEnt;
     150                 :             : 
     151                 :             : /* Virtual file descriptor with file offset tracking */
     152                 :             : typedef struct TXNEntryFile
     153                 :             : {
     154                 :             :     File        vfd;            /* -1 when the file is closed */
     155                 :             :     off_t       curOffset;      /* offset for next write or read. Reset to 0
     156                 :             :                                  * when vfd is opened. */
     157                 :             : } TXNEntryFile;
     158                 :             : 
     159                 :             : /* k-way in-order change iteration support structures */
     160                 :             : typedef struct ReorderBufferIterTXNEntry
     161                 :             : {
     162                 :             :     XLogRecPtr  lsn;
     163                 :             :     ReorderBufferChange *change;
     164                 :             :     ReorderBufferTXN *txn;
     165                 :             :     TXNEntryFile file;
     166                 :             :     XLogSegNo   segno;
     167                 :             : } ReorderBufferIterTXNEntry;
     168                 :             : 
     169                 :             : typedef struct ReorderBufferIterTXNState
     170                 :             : {
     171                 :             :     binaryheap *heap;
     172                 :             :     Size        nr_txns;
     173                 :             :     dlist_head  old_change;
     174                 :             :     ReorderBufferIterTXNEntry entries[FLEXIBLE_ARRAY_MEMBER];
     175                 :             : } ReorderBufferIterTXNState;
     176                 :             : 
     177                 :             : /* toast datastructures */
     178                 :             : typedef struct ReorderBufferToastEnt
     179                 :             : {
     180                 :             :     Oid         chunk_id;       /* toast_table.chunk_id */
     181                 :             :     int32       last_chunk_seq; /* toast_table.chunk_seq of the last chunk we
     182                 :             :                                  * have seen */
     183                 :             :     Size        num_chunks;     /* number of chunks we've already seen */
     184                 :             :     Size        size;           /* combined size of chunks seen */
     185                 :             :     dlist_head  chunks;         /* linked list of chunks */
     186                 :             :     varlena    *reconstructed;  /* reconstructed varlena now pointed to in
     187                 :             :                                  * main tup */
     188                 :             : } ReorderBufferToastEnt;
     189                 :             : 
     190                 :             : /* Disk serialization support datastructures */
     191                 :             : typedef struct ReorderBufferDiskChange
     192                 :             : {
     193                 :             :     Size        size;
     194                 :             :     ReorderBufferChange change;
     195                 :             :     /* data follows */
     196                 :             : } ReorderBufferDiskChange;
     197                 :             : 
     198                 :             : #define IsSpecInsert(action) \
     199                 :             : ( \
     200                 :             :     ((action) == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT) \
     201                 :             : )
     202                 :             : #define IsSpecConfirmOrAbort(action) \
     203                 :             : ( \
     204                 :             :     (((action) == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM) || \
     205                 :             :     ((action) == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT)) \
     206                 :             : )
     207                 :             : #define IsInsertOrUpdate(action) \
     208                 :             : ( \
     209                 :             :     (((action) == REORDER_BUFFER_CHANGE_INSERT) || \
     210                 :             :     ((action) == REORDER_BUFFER_CHANGE_UPDATE) || \
     211                 :             :     ((action) == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT)) \
     212                 :             : )
     213                 :             : 
     214                 :             : /*
     215                 :             :  * Maximum number of changes kept in memory, per transaction. After that,
     216                 :             :  * changes are spooled to disk.
     217                 :             :  *
     218                 :             :  * The current value should be sufficient to decode the entire transaction
     219                 :             :  * without hitting disk in OLTP workloads, while starting to spool to disk in
     220                 :             :  * other workloads reasonably fast.
     221                 :             :  *
     222                 :             :  * At some point in the future it probably makes sense to have a more elaborate
     223                 :             :  * resource management here, but it's not entirely clear what that would look
     224                 :             :  * like.
     225                 :             :  */
     226                 :             : int         logical_decoding_work_mem;
     227                 :             : static const Size max_changes_in_memory = 4096; /* XXX for restore only */
     228                 :             : 
     229                 :             : /* GUC variable */
     230                 :             : int         debug_logical_replication_streaming = DEBUG_LOGICAL_REP_STREAMING_BUFFERED;
     231                 :             : 
     232                 :             : /* ---------------------------------------
     233                 :             :  * primary reorderbuffer support routines
     234                 :             :  * ---------------------------------------
     235                 :             :  */
     236                 :             : static ReorderBufferTXN *ReorderBufferAllocTXN(ReorderBuffer *rb);
     237                 :             : static void ReorderBufferFreeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
     238                 :             : static ReorderBufferTXN *ReorderBufferTXNByXid(ReorderBuffer *rb,
     239                 :             :                                                TransactionId xid, bool create, bool *is_new,
     240                 :             :                                                XLogRecPtr lsn, bool create_as_top);
     241                 :             : static void ReorderBufferTransferSnapToParent(ReorderBufferTXN *txn,
     242                 :             :                                               ReorderBufferTXN *subtxn);
     243                 :             : 
     244                 :             : static void AssertTXNLsnOrder(ReorderBuffer *rb);
     245                 :             : 
     246                 :             : /* ---------------------------------------
     247                 :             :  * support functions for lsn-order iterating over the ->changes of a
     248                 :             :  * transaction and its subtransactions
     249                 :             :  *
     250                 :             :  * used for iteration over the k-way heap merge of a transaction and its
     251                 :             :  * subtransactions
     252                 :             :  * ---------------------------------------
     253                 :             :  */
     254                 :             : static void ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn,
     255                 :             :                                      ReorderBufferIterTXNState *volatile *iter_state);
     256                 :             : static ReorderBufferChange *ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state);
     257                 :             : static void ReorderBufferIterTXNFinish(ReorderBuffer *rb,
     258                 :             :                                        ReorderBufferIterTXNState *state);
     259                 :             : static void ReorderBufferExecuteInvalidations(uint32 nmsgs, SharedInvalidationMessage *msgs);
     260                 :             : 
     261                 :             : /*
     262                 :             :  * ---------------------------------------
     263                 :             :  * Disk serialization support functions
     264                 :             :  * ---------------------------------------
     265                 :             :  */
     266                 :             : static void ReorderBufferCheckMemoryLimit(ReorderBuffer *rb);
     267                 :             : static void ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
     268                 :             : static void ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
     269                 :             :                                          int fd, ReorderBufferChange *change);
     270                 :             : static Size ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn,
     271                 :             :                                         TXNEntryFile *file, XLogSegNo *segno);
     272                 :             : static void ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
     273                 :             :                                        char *data);
     274                 :             : static void ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn);
     275                 :             : static void ReorderBufferTruncateTXN(ReorderBuffer *rb, ReorderBufferTXN *txn,
     276                 :             :                                      bool txn_prepared);
     277                 :             : static void ReorderBufferMaybeMarkTXNStreamed(ReorderBuffer *rb, ReorderBufferTXN *txn);
     278                 :             : static bool ReorderBufferCheckAndTruncateAbortedTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
     279                 :             : static void ReorderBufferCleanupSerializedTXNs(const char *slotname);
     280                 :             : static void ReorderBufferSerializedPath(char *path, ReplicationSlot *slot,
     281                 :             :                                         TransactionId xid, XLogSegNo segno);
     282                 :             : static int  ReorderBufferTXNSizeCompare(const pairingheap_node *a, const pairingheap_node *b, void *arg);
     283                 :             : 
     284                 :             : static void ReorderBufferFreeSnap(ReorderBuffer *rb, Snapshot snap);
     285                 :             : static Snapshot ReorderBufferCopySnap(ReorderBuffer *rb, Snapshot orig_snap,
     286                 :             :                                       ReorderBufferTXN *txn, CommandId cid);
     287                 :             : 
     288                 :             : /*
     289                 :             :  * ---------------------------------------
     290                 :             :  * Streaming support functions
     291                 :             :  * ---------------------------------------
     292                 :             :  */
     293                 :             : static inline bool ReorderBufferCanStream(ReorderBuffer *rb);
     294                 :             : static inline bool ReorderBufferCanStartStreaming(ReorderBuffer *rb);
     295                 :             : static void ReorderBufferStreamTXN(ReorderBuffer *rb, ReorderBufferTXN *txn);
     296                 :             : static void ReorderBufferStreamCommit(ReorderBuffer *rb, ReorderBufferTXN *txn);
     297                 :             : 
     298                 :             : /* ---------------------------------------
     299                 :             :  * toast reassembly support
     300                 :             :  * ---------------------------------------
     301                 :             :  */
     302                 :             : static void ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn);
     303                 :             : static void ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn);
     304                 :             : static void ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
     305                 :             :                                       Relation relation, ReorderBufferChange *change);
     306                 :             : static void ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn,
     307                 :             :                                           Relation relation, ReorderBufferChange *change);
     308                 :             : 
     309                 :             : /*
     310                 :             :  * ---------------------------------------
     311                 :             :  * memory accounting
     312                 :             :  * ---------------------------------------
     313                 :             :  */
     314                 :             : static Size ReorderBufferChangeSize(ReorderBufferChange *change);
     315                 :             : static void ReorderBufferChangeMemoryUpdate(ReorderBuffer *rb,
     316                 :             :                                             ReorderBufferChange *change,
     317                 :             :                                             ReorderBufferTXN *txn,
     318                 :             :                                             bool addition, Size sz);
     319                 :             : 
     320                 :             : /*
     321                 :             :  * Allocate a new ReorderBuffer and clean out any old serialized state from
     322                 :             :  * prior ReorderBuffer instances for the same slot.
     323                 :             :  */
     324                 :             : ReorderBuffer *
     325                 :        1230 : ReorderBufferAllocate(void)
     326                 :             : {
     327                 :             :     ReorderBuffer *buffer;
     328                 :             :     HASHCTL     hash_ctl;
     329                 :             :     MemoryContext new_ctx;
     330                 :             : 
     331                 :             :     Assert(MyReplicationSlot != NULL);
     332                 :             : 
     333                 :             :     /* allocate memory in own context, to have better accountability */
     334                 :        1230 :     new_ctx = AllocSetContextCreate(CurrentMemoryContext,
     335                 :             :                                     "ReorderBuffer",
     336                 :             :                                     ALLOCSET_DEFAULT_SIZES);
     337                 :             : 
     338                 :             :     buffer =
     339                 :        1230 :         (ReorderBuffer *) MemoryContextAlloc(new_ctx, sizeof(ReorderBuffer));
     340                 :             : 
     341                 :        1230 :     memset(&hash_ctl, 0, sizeof(hash_ctl));
     342                 :             : 
     343                 :        1230 :     buffer->context = new_ctx;
     344                 :             : 
     345                 :        1230 :     buffer->change_context = SlabContextCreate(new_ctx,
     346                 :             :                                                "Change",
     347                 :             :                                                SLAB_DEFAULT_BLOCK_SIZE,
     348                 :             :                                                sizeof(ReorderBufferChange));
     349                 :             : 
     350                 :        1230 :     buffer->txn_context = SlabContextCreate(new_ctx,
     351                 :             :                                             "TXN",
     352                 :             :                                             SLAB_DEFAULT_BLOCK_SIZE,
     353                 :             :                                             sizeof(ReorderBufferTXN));
     354                 :             : 
     355                 :             :     /*
     356                 :             :      * To minimize memory fragmentation caused by long-running transactions
     357                 :             :      * with changes spanning multiple memory blocks, we use a single
     358                 :             :      * fixed-size memory block for decoded tuple storage. The performance
     359                 :             :      * testing showed that the default memory block size maintains logical
     360                 :             :      * decoding performance without causing fragmentation due to concurrent
     361                 :             :      * transactions. One might think that we can use the max size as
     362                 :             :      * SLAB_LARGE_BLOCK_SIZE but the test also showed it doesn't help resolve
     363                 :             :      * the memory fragmentation.
     364                 :             :      */
     365                 :        1230 :     buffer->tup_context = GenerationContextCreate(new_ctx,
     366                 :             :                                                   "Tuples",
     367                 :             :                                                   SLAB_DEFAULT_BLOCK_SIZE,
     368                 :             :                                                   SLAB_DEFAULT_BLOCK_SIZE,
     369                 :             :                                                   SLAB_DEFAULT_BLOCK_SIZE);
     370                 :             : 
     371                 :        1230 :     hash_ctl.keysize = sizeof(TransactionId);
     372                 :        1230 :     hash_ctl.entrysize = sizeof(ReorderBufferTXNByIdEnt);
     373                 :        1230 :     hash_ctl.hcxt = buffer->context;
     374                 :             : 
     375                 :        1230 :     buffer->by_txn = hash_create("ReorderBufferByXid", 1000, &hash_ctl,
     376                 :             :                                  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
     377                 :             : 
     378                 :        1230 :     buffer->by_txn_last_xid = InvalidTransactionId;
     379                 :        1230 :     buffer->by_txn_last_txn = NULL;
     380                 :             : 
     381                 :        1230 :     buffer->outbuf = NULL;
     382                 :        1230 :     buffer->outbufsize = 0;
     383                 :        1230 :     buffer->size = 0;
     384                 :             : 
     385                 :             :     /* txn_heap is ordered by transaction size */
     386                 :        1230 :     buffer->txn_heap = pairingheap_allocate(ReorderBufferTXNSizeCompare, NULL);
     387                 :             : 
     388                 :        1230 :     buffer->spillTxns = 0;
     389                 :        1230 :     buffer->spillCount = 0;
     390                 :        1230 :     buffer->spillBytes = 0;
     391                 :        1230 :     buffer->streamTxns = 0;
     392                 :        1230 :     buffer->streamCount = 0;
     393                 :        1230 :     buffer->streamBytes = 0;
     394                 :        1230 :     buffer->memExceededCount = 0;
     395                 :        1230 :     buffer->totalTxns = 0;
     396                 :        1230 :     buffer->totalBytes = 0;
     397                 :             : 
     398                 :        1230 :     buffer->current_restart_decoding_lsn = InvalidXLogRecPtr;
     399                 :             : 
     400                 :        1230 :     dlist_init(&buffer->toplevel_by_lsn);
     401                 :        1230 :     dlist_init(&buffer->txns_by_base_snapshot_lsn);
     402                 :        1230 :     dclist_init(&buffer->catchange_txns);
     403                 :             : 
     404                 :             :     /*
     405                 :             :      * Ensure there's no stale data from prior uses of this slot, in case some
     406                 :             :      * prior exit avoided calling ReorderBufferFree. Failure to do this can
     407                 :             :      * produce duplicated txns, and it's very cheap if there's nothing there.
     408                 :             :      */
     409                 :        1230 :     ReorderBufferCleanupSerializedTXNs(NameStr(MyReplicationSlot->data.name));
     410                 :             : 
     411                 :        1230 :     return buffer;
     412                 :             : }
     413                 :             : 
     414                 :             : /*
     415                 :             :  * Free a ReorderBuffer
     416                 :             :  */
     417                 :             : void
     418                 :         956 : ReorderBufferFree(ReorderBuffer *rb)
     419                 :             : {
     420                 :         956 :     MemoryContext context = rb->context;
     421                 :             : 
     422                 :             :     /*
     423                 :             :      * We free separately allocated data by entirely scrapping reorderbuffer's
     424                 :             :      * memory context.
     425                 :             :      */
     426                 :         956 :     MemoryContextDelete(context);
     427                 :             : 
     428                 :             :     /* Free disk space used by unconsumed reorder buffers */
     429                 :         956 :     ReorderBufferCleanupSerializedTXNs(NameStr(MyReplicationSlot->data.name));
     430                 :         956 : }
     431                 :             : 
     432                 :             : /*
     433                 :             :  * Allocate a new ReorderBufferTXN.
     434                 :             :  */
     435                 :             : static ReorderBufferTXN *
     436                 :        4878 : ReorderBufferAllocTXN(ReorderBuffer *rb)
     437                 :             : {
     438                 :             :     ReorderBufferTXN *txn;
     439                 :             : 
     440                 :             :     txn = (ReorderBufferTXN *)
     441                 :        4878 :         MemoryContextAlloc(rb->txn_context, sizeof(ReorderBufferTXN));
     442                 :             : 
     443                 :        4878 :     memset(txn, 0, sizeof(ReorderBufferTXN));
     444                 :             : 
     445                 :        4878 :     dlist_init(&txn->changes);
     446                 :        4878 :     dlist_init(&txn->tuplecids);
     447                 :        4878 :     dlist_init(&txn->subtxns);
     448                 :             : 
     449                 :             :     /* InvalidCommandId is not zero, so set it explicitly */
     450                 :        4878 :     txn->command_id = InvalidCommandId;
     451                 :        4878 :     txn->output_plugin_private = NULL;
     452                 :             : 
     453                 :        4878 :     return txn;
     454                 :             : }
     455                 :             : 
     456                 :             : /*
     457                 :             :  * Free a ReorderBufferTXN.
     458                 :             :  */
     459                 :             : static void
     460                 :        4802 : ReorderBufferFreeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
     461                 :             : {
     462                 :             :     /* clean the lookup cache if we were cached (quite likely) */
     463         [ +  + ]:        4802 :     if (rb->by_txn_last_xid == txn->xid)
     464                 :             :     {
     465                 :        4614 :         rb->by_txn_last_xid = InvalidTransactionId;
     466                 :        4614 :         rb->by_txn_last_txn = NULL;
     467                 :             :     }
     468                 :             : 
     469                 :             :     /* free data that's contained */
     470                 :             : 
     471         [ +  + ]:        4802 :     if (txn->gid != NULL)
     472                 :             :     {
     473                 :          43 :         pfree(txn->gid);
     474                 :          43 :         txn->gid = NULL;
     475                 :             :     }
     476                 :             : 
     477         [ +  + ]:        4802 :     if (txn->tuplecid_hash != NULL)
     478                 :             :     {
     479                 :         949 :         hash_destroy(txn->tuplecid_hash);
     480                 :         949 :         txn->tuplecid_hash = NULL;
     481                 :             :     }
     482                 :             : 
     483         [ +  + ]:        4802 :     if (txn->invalidations)
     484                 :             :     {
     485                 :        1628 :         pfree(txn->invalidations);
     486                 :        1628 :         txn->invalidations = NULL;
     487                 :             :     }
     488                 :             : 
     489         [ +  + ]:        4802 :     if (txn->invalidations_distributed)
     490                 :             :     {
     491                 :          21 :         pfree(txn->invalidations_distributed);
     492                 :          21 :         txn->invalidations_distributed = NULL;
     493                 :             :     }
     494                 :             : 
     495                 :             :     /* Reset the toast hash */
     496                 :        4802 :     ReorderBufferToastReset(rb, txn);
     497                 :             : 
     498                 :             :     /* All changes must be deallocated */
     499                 :             :     Assert(txn->size == 0);
     500                 :             : 
     501                 :        4802 :     pfree(txn);
     502                 :        4802 : }
     503                 :             : 
     504                 :             : /*
     505                 :             :  * Allocate a ReorderBufferChange.
     506                 :             :  */
     507                 :             : ReorderBufferChange *
     508                 :     1819872 : ReorderBufferAllocChange(ReorderBuffer *rb)
     509                 :             : {
     510                 :             :     ReorderBufferChange *change;
     511                 :             : 
     512                 :             :     change = (ReorderBufferChange *)
     513                 :     1819872 :         MemoryContextAlloc(rb->change_context, sizeof(ReorderBufferChange));
     514                 :             : 
     515                 :     1819872 :     memset(change, 0, sizeof(ReorderBufferChange));
     516                 :     1819872 :     return change;
     517                 :             : }
     518                 :             : 
     519                 :             : /*
     520                 :             :  * Free a ReorderBufferChange and update memory accounting, if requested.
     521                 :             :  */
     522                 :             : void
     523                 :     1799105 : ReorderBufferFreeChange(ReorderBuffer *rb, ReorderBufferChange *change,
     524                 :             :                         bool upd_mem)
     525                 :             : {
     526                 :             :     /* update memory accounting info */
     527         [ +  + ]:     1799105 :     if (upd_mem)
     528                 :      206269 :         ReorderBufferChangeMemoryUpdate(rb, change, NULL, false,
     529                 :             :                                         ReorderBufferChangeSize(change));
     530                 :             : 
     531                 :             :     /* free contained data */
     532   [ +  +  +  +  :     1799105 :     switch (change->action)
                +  +  - ]
     533                 :             :     {
     534                 :     1715863 :         case REORDER_BUFFER_CHANGE_INSERT:
     535                 :             :         case REORDER_BUFFER_CHANGE_UPDATE:
     536                 :             :         case REORDER_BUFFER_CHANGE_DELETE:
     537                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
     538         [ +  + ]:     1715863 :             if (change->data.tp.newtuple)
     539                 :             :             {
     540                 :     1435761 :                 ReorderBufferFreeTupleBuf(change->data.tp.newtuple);
     541                 :     1435761 :                 change->data.tp.newtuple = NULL;
     542                 :             :             }
     543                 :             : 
     544         [ +  + ]:     1715863 :             if (change->data.tp.oldtuple)
     545                 :             :             {
     546                 :      211293 :                 ReorderBufferFreeTupleBuf(change->data.tp.oldtuple);
     547                 :      211293 :                 change->data.tp.oldtuple = NULL;
     548                 :             :             }
     549                 :     1715863 :             break;
     550                 :          40 :         case REORDER_BUFFER_CHANGE_MESSAGE:
     551         [ +  - ]:          40 :             if (change->data.msg.prefix != NULL)
     552                 :          40 :                 pfree(change->data.msg.prefix);
     553                 :          40 :             change->data.msg.prefix = NULL;
     554         [ +  - ]:          40 :             if (change->data.msg.message != NULL)
     555                 :          40 :                 pfree(change->data.msg.message);
     556                 :          40 :             change->data.msg.message = NULL;
     557                 :          40 :             break;
     558                 :        6001 :         case REORDER_BUFFER_CHANGE_INVALIDATION:
     559         [ +  - ]:        6001 :             if (change->data.inval.invalidations)
     560                 :        6001 :                 pfree(change->data.inval.invalidations);
     561                 :        6001 :             change->data.inval.invalidations = NULL;
     562                 :        6001 :             break;
     563                 :        1669 :         case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
     564         [ +  - ]:        1669 :             if (change->data.snapshot)
     565                 :             :             {
     566                 :        1669 :                 ReorderBufferFreeSnap(rb, change->data.snapshot);
     567                 :        1669 :                 change->data.snapshot = NULL;
     568                 :             :             }
     569                 :        1669 :             break;
     570                 :             :             /* no data in addition to the struct itself */
     571                 :          57 :         case REORDER_BUFFER_CHANGE_TRUNCATE:
     572         [ +  - ]:          57 :             if (change->data.truncate.relids != NULL)
     573                 :             :             {
     574                 :          57 :                 ReorderBufferFreeRelids(rb, change->data.truncate.relids);
     575                 :          57 :                 change->data.truncate.relids = NULL;
     576                 :             :             }
     577                 :          57 :             break;
     578                 :       75475 :         case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
     579                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT:
     580                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
     581                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
     582                 :       75475 :             break;
     583                 :             :     }
     584                 :             : 
     585                 :     1799105 :     pfree(change);
     586                 :     1799105 : }
     587                 :             : 
     588                 :             : /*
     589                 :             :  * Allocate a HeapTuple fitting a tuple of size tuple_len (excluding header
     590                 :             :  * overhead).
     591                 :             :  */
     592                 :             : HeapTuple
     593                 :     1667108 : ReorderBufferAllocTupleBuf(ReorderBuffer *rb, Size tuple_len)
     594                 :             : {
     595                 :             :     HeapTuple   tuple;
     596                 :             :     Size        alloc_len;
     597                 :             : 
     598                 :     1667108 :     alloc_len = tuple_len + SizeofHeapTupleHeader;
     599                 :             : 
     600                 :     1667108 :     tuple = (HeapTuple) MemoryContextAlloc(rb->tup_context,
     601                 :             :                                            HEAPTUPLESIZE + alloc_len);
     602                 :     1667108 :     tuple->t_data = (HeapTupleHeader) ((char *) tuple + HEAPTUPLESIZE);
     603                 :             : 
     604                 :     1667108 :     return tuple;
     605                 :             : }
     606                 :             : 
     607                 :             : /*
     608                 :             :  * Free a HeapTuple returned by ReorderBufferAllocTupleBuf().
     609                 :             :  */
     610                 :             : void
     611                 :     1647054 : ReorderBufferFreeTupleBuf(HeapTuple tuple)
     612                 :             : {
     613                 :     1647054 :     pfree(tuple);
     614                 :     1647054 : }
     615                 :             : 
     616                 :             : /*
     617                 :             :  * Allocate an array for relids of truncated relations.
     618                 :             :  *
     619                 :             :  * We use the global memory context (for the whole reorder buffer), because
     620                 :             :  * none of the existing ones seems like a good match (some are SLAB, so we
     621                 :             :  * can't use those, and tup_context is meant for tuple data, not relids). We
     622                 :             :  * could add yet another context, but it seems like an overkill - TRUNCATE is
     623                 :             :  * not particularly common operation, so it does not seem worth it.
     624                 :             :  */
     625                 :             : Oid *
     626                 :          62 : ReorderBufferAllocRelids(ReorderBuffer *rb, int nrelids)
     627                 :             : {
     628                 :             :     Oid        *relids;
     629                 :             :     Size        alloc_len;
     630                 :             : 
     631                 :          62 :     alloc_len = sizeof(Oid) * nrelids;
     632                 :             : 
     633                 :          62 :     relids = (Oid *) MemoryContextAlloc(rb->context, alloc_len);
     634                 :             : 
     635                 :          62 :     return relids;
     636                 :             : }
     637                 :             : 
     638                 :             : /*
     639                 :             :  * Free an array of relids.
     640                 :             :  */
     641                 :             : void
     642                 :          57 : ReorderBufferFreeRelids(ReorderBuffer *rb, Oid *relids)
     643                 :             : {
     644                 :          57 :     pfree(relids);
     645                 :          57 : }
     646                 :             : 
     647                 :             : /*
     648                 :             :  * Return the ReorderBufferTXN from the given buffer, specified by Xid.
     649                 :             :  * If create is true, and a transaction doesn't already exist, create it
     650                 :             :  * (with the given LSN, and as top transaction if that's specified);
     651                 :             :  * when this happens, is_new is set to true.
     652                 :             :  */
     653                 :             : static ReorderBufferTXN *
     654                 :     6095927 : ReorderBufferTXNByXid(ReorderBuffer *rb, TransactionId xid, bool create,
     655                 :             :                       bool *is_new, XLogRecPtr lsn, bool create_as_top)
     656                 :             : {
     657                 :             :     ReorderBufferTXN *txn;
     658                 :             :     ReorderBufferTXNByIdEnt *ent;
     659                 :             :     bool        found;
     660                 :             : 
     661                 :             :     Assert(TransactionIdIsValid(xid));
     662                 :             : 
     663                 :             :     /*
     664                 :             :      * Check the one-entry lookup cache first
     665                 :             :      */
     666         [ +  + ]:     6095927 :     if (TransactionIdIsValid(rb->by_txn_last_xid) &&
     667         [ +  + ]:     6091254 :         rb->by_txn_last_xid == xid)
     668                 :             :     {
     669                 :     5321424 :         txn = rb->by_txn_last_txn;
     670                 :             : 
     671         [ +  + ]:     5321424 :         if (txn != NULL)
     672                 :             :         {
     673                 :             :             /* found it, and it's valid */
     674         [ +  + ]:     5321361 :             if (is_new)
     675                 :        4148 :                 *is_new = false;
     676                 :     5321361 :             return txn;
     677                 :             :         }
     678                 :             : 
     679                 :             :         /*
     680                 :             :          * cached as non-existent, and asked not to create? Then nothing else
     681                 :             :          * to do.
     682                 :             :          */
     683         [ +  + ]:          63 :         if (!create)
     684                 :          60 :             return NULL;
     685                 :             :         /* otherwise fall through to create it */
     686                 :             :     }
     687                 :             : 
     688                 :             :     /*
     689                 :             :      * If the cache wasn't hit or it yielded a "does-not-exist" and we want to
     690                 :             :      * create an entry.
     691                 :             :      */
     692                 :             : 
     693                 :             :     /* search the lookup table */
     694                 :             :     ent = (ReorderBufferTXNByIdEnt *)
     695                 :      774506 :         hash_search(rb->by_txn,
     696                 :             :                     &xid,
     697                 :             :                     create ? HASH_ENTER : HASH_FIND,
     698                 :             :                     &found);
     699         [ +  + ]:      774506 :     if (found)
     700                 :      768294 :         txn = ent->txn;
     701         [ +  + ]:        6212 :     else if (create)
     702                 :             :     {
     703                 :             :         /* initialize the new entry, if creation was requested */
     704                 :             :         Assert(ent != NULL);
     705                 :             :         Assert(XLogRecPtrIsValid(lsn));
     706                 :             : 
     707                 :        4878 :         ent->txn = ReorderBufferAllocTXN(rb);
     708                 :        4878 :         ent->txn->xid = xid;
     709                 :        4878 :         txn = ent->txn;
     710                 :        4878 :         txn->first_lsn = lsn;
     711                 :        4878 :         txn->restart_decoding_lsn = rb->current_restart_decoding_lsn;
     712                 :             : 
     713         [ +  + ]:        4878 :         if (create_as_top)
     714                 :             :         {
     715                 :        4219 :             dlist_push_tail(&rb->toplevel_by_lsn, &txn->node);
     716                 :        4219 :             AssertTXNLsnOrder(rb);
     717                 :             :         }
     718                 :             :     }
     719                 :             :     else
     720                 :        1334 :         txn = NULL;             /* not found and not asked to create */
     721                 :             : 
     722                 :             :     /* update cache */
     723                 :      774506 :     rb->by_txn_last_xid = xid;
     724                 :      774506 :     rb->by_txn_last_txn = txn;
     725                 :             : 
     726         [ +  + ]:      774506 :     if (is_new)
     727                 :        1741 :         *is_new = !found;
     728                 :             : 
     729                 :             :     Assert(!create || txn != NULL);
     730                 :      774506 :     return txn;
     731                 :             : }
     732                 :             : 
     733                 :             : /*
     734                 :             :  * Record the partial change for the streaming of in-progress transactions.  We
     735                 :             :  * can stream only complete changes so if we have a partial change like toast
     736                 :             :  * table insert or speculative insert then we mark such a 'txn' so that it
     737                 :             :  * can't be streamed.  We also ensure that if the changes in such a 'txn' can
     738                 :             :  * be streamed and are above logical_decoding_work_mem threshold then we stream
     739                 :             :  * them as soon as we have a complete change.
     740                 :             :  */
     741                 :             : static void
     742                 :     1603872 : ReorderBufferProcessPartialChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
     743                 :             :                                   ReorderBufferChange *change,
     744                 :             :                                   bool toast_insert)
     745                 :             : {
     746                 :             :     ReorderBufferTXN *toptxn;
     747                 :             : 
     748                 :             :     /*
     749                 :             :      * The partial changes need to be processed only while streaming
     750                 :             :      * in-progress transactions.
     751                 :             :      */
     752         [ +  + ]:     1603872 :     if (!ReorderBufferCanStream(rb))
     753                 :     1052874 :         return;
     754                 :             : 
     755                 :             :     /* Get the top transaction. */
     756         [ +  + ]:      550998 :     toptxn = rbtxn_get_toptxn(txn);
     757                 :             : 
     758                 :             :     /*
     759                 :             :      * Indicate a partial change for toast inserts.  The change will be
     760                 :             :      * considered as complete once we get the insert or update on the main
     761                 :             :      * table and we are sure that the pending toast chunks are not required
     762                 :             :      * anymore.
     763                 :             :      *
     764                 :             :      * If we allow streaming when there are pending toast chunks then such
     765                 :             :      * chunks won't be released till the insert (multi_insert) is complete and
     766                 :             :      * we expect the txn to have streamed all changes after streaming.  This
     767                 :             :      * restriction is mainly to ensure the correctness of streamed
     768                 :             :      * transactions and it doesn't seem worth uplifting such a restriction
     769                 :             :      * just to allow this case because anyway we will stream the transaction
     770                 :             :      * once such an insert is complete.
     771                 :             :      */
     772         [ +  + ]:      550998 :     if (toast_insert)
     773                 :        1649 :         toptxn->txn_flags |= RBTXN_HAS_PARTIAL_CHANGE;
     774         [ +  + ]:      549349 :     else if (rbtxn_has_partial_change(toptxn) &&
     775   [ -  +  -  -  :          57 :              IsInsertOrUpdate(change->action) &&
                   -  - ]
     776         [ +  + ]:          57 :              change->data.tp.clear_toast_afterwards)
     777                 :          37 :         toptxn->txn_flags &= ~RBTXN_HAS_PARTIAL_CHANGE;
     778                 :             : 
     779                 :             :     /*
     780                 :             :      * Indicate a partial change for speculative inserts.  The change will be
     781                 :             :      * considered as complete once we get the speculative confirm or abort
     782                 :             :      * token.
     783                 :             :      */
     784         [ -  + ]:      550998 :     if (IsSpecInsert(change->action))
     785                 :           0 :         toptxn->txn_flags |= RBTXN_HAS_PARTIAL_CHANGE;
     786         [ +  + ]:      550998 :     else if (rbtxn_has_partial_change(toptxn) &&
     787   [ +  -  -  + ]:        1669 :              IsSpecConfirmOrAbort(change->action))
     788                 :           0 :         toptxn->txn_flags &= ~RBTXN_HAS_PARTIAL_CHANGE;
     789                 :             : 
     790                 :             :     /*
     791                 :             :      * Stream the transaction if it is serialized before and the changes are
     792                 :             :      * now complete in the top-level transaction.
     793                 :             :      *
     794                 :             :      * The reason for doing the streaming of such a transaction as soon as we
     795                 :             :      * get the complete change for it is that previously it would have reached
     796                 :             :      * the memory threshold and wouldn't get streamed because of incomplete
     797                 :             :      * changes.  Delaying such transactions would increase apply lag for them.
     798                 :             :      */
     799         [ +  + ]:      550998 :     if (ReorderBufferCanStartStreaming(rb) &&
     800         [ +  + ]:      206073 :         !(rbtxn_has_partial_change(toptxn)) &&
     801         [ +  + ]:      204542 :         rbtxn_is_serialized(txn) &&
     802         [ +  + ]:          39 :         rbtxn_has_streamable_change(toptxn))
     803                 :           9 :         ReorderBufferStreamTXN(rb, toptxn);
     804                 :             : }
     805                 :             : 
     806                 :             : /*
     807                 :             :  * Queue a change into a transaction so it can be replayed upon commit or will be
     808                 :             :  * streamed when we reach logical_decoding_work_mem threshold.
     809                 :             :  */
     810                 :             : void
     811                 :     1613281 : ReorderBufferQueueChange(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn,
     812                 :             :                          ReorderBufferChange *change, bool toast_insert)
     813                 :             : {
     814                 :             :     ReorderBufferTXN *txn;
     815                 :             : 
     816                 :     1613281 :     txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
     817                 :             : 
     818                 :             :     /*
     819                 :             :      * If we have detected that the transaction is aborted while streaming the
     820                 :             :      * previous changes or by checking its CLOG, there is no point in
     821                 :             :      * collecting further changes for it.
     822                 :             :      */
     823         [ +  + ]:     1613281 :     if (rbtxn_is_aborted(txn))
     824                 :             :     {
     825                 :             :         /*
     826                 :             :          * We don't need to update memory accounting for this change as we
     827                 :             :          * have not added it to the queue yet.
     828                 :             :          */
     829                 :        9409 :         ReorderBufferFreeChange(rb, change, false);
     830                 :        9409 :         return;
     831                 :             :     }
     832                 :             : 
     833                 :             :     /*
     834                 :             :      * The changes that are sent downstream are considered streamable.  We
     835                 :             :      * remember such transactions so that only those will later be considered
     836                 :             :      * for streaming.
     837                 :             :      */
     838         [ +  + ]:     1603872 :     if (change->action == REORDER_BUFFER_CHANGE_INSERT ||
     839         [ +  + ]:      547290 :         change->action == REORDER_BUFFER_CHANGE_UPDATE ||
     840         [ +  + ]:      339807 :         change->action == REORDER_BUFFER_CHANGE_DELETE ||
     841         [ +  + ]:       71748 :         change->action == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT ||
     842         [ +  + ]:       53783 :         change->action == REORDER_BUFFER_CHANGE_TRUNCATE ||
     843         [ +  + ]:       53731 :         change->action == REORDER_BUFFER_CHANGE_MESSAGE)
     844                 :             :     {
     845         [ +  + ]:     1550180 :         ReorderBufferTXN *toptxn = rbtxn_get_toptxn(txn);
     846                 :             : 
     847                 :     1550180 :         toptxn->txn_flags |= RBTXN_HAS_STREAMABLE_CHANGE;
     848                 :             :     }
     849                 :             : 
     850                 :     1603872 :     change->lsn = lsn;
     851                 :     1603872 :     change->txn = txn;
     852                 :             : 
     853                 :             :     Assert(XLogRecPtrIsValid(lsn));
     854                 :     1603872 :     dlist_push_tail(&txn->changes, &change->node);
     855                 :     1603872 :     txn->nentries++;
     856                 :     1603872 :     txn->nentries_mem++;
     857                 :             : 
     858                 :             :     /* update memory accounting information */
     859                 :     1603872 :     ReorderBufferChangeMemoryUpdate(rb, change, NULL, true,
     860                 :             :                                     ReorderBufferChangeSize(change));
     861                 :             : 
     862                 :             :     /* process partial change */
     863                 :     1603872 :     ReorderBufferProcessPartialChange(rb, txn, change, toast_insert);
     864                 :             : 
     865                 :             :     /* check the memory limits and evict something if needed */
     866                 :     1603872 :     ReorderBufferCheckMemoryLimit(rb);
     867                 :             : }
     868                 :             : 
     869                 :             : /*
     870                 :             :  * A transactional message is queued to be processed upon commit and a
     871                 :             :  * non-transactional message gets processed immediately.
     872                 :             :  */
     873                 :             : void
     874                 :          48 : ReorderBufferQueueMessage(ReorderBuffer *rb, TransactionId xid,
     875                 :             :                           Snapshot snap, XLogRecPtr lsn,
     876                 :             :                           bool transactional, const char *prefix,
     877                 :             :                           Size message_size, const char *message)
     878                 :             : {
     879         [ +  + ]:          48 :     if (transactional)
     880                 :             :     {
     881                 :             :         MemoryContext oldcontext;
     882                 :             :         ReorderBufferChange *change;
     883                 :             : 
     884                 :             :         Assert(xid != InvalidTransactionId);
     885                 :             : 
     886                 :             :         /*
     887                 :             :          * We don't expect snapshots for transactional changes - we'll use the
     888                 :             :          * snapshot derived later during apply (unless the change gets
     889                 :             :          * skipped).
     890                 :             :          */
     891                 :             :         Assert(!snap);
     892                 :             : 
     893                 :          39 :         oldcontext = MemoryContextSwitchTo(rb->context);
     894                 :             : 
     895                 :          39 :         change = ReorderBufferAllocChange(rb);
     896                 :          39 :         change->action = REORDER_BUFFER_CHANGE_MESSAGE;
     897                 :          39 :         change->data.msg.prefix = pstrdup(prefix);
     898                 :          39 :         change->data.msg.message_size = message_size;
     899                 :          39 :         change->data.msg.message = palloc(message_size);
     900                 :          39 :         memcpy(change->data.msg.message, message, message_size);
     901                 :             : 
     902                 :          39 :         ReorderBufferQueueChange(rb, xid, lsn, change, false);
     903                 :             : 
     904                 :          39 :         MemoryContextSwitchTo(oldcontext);
     905                 :             :     }
     906                 :             :     else
     907                 :             :     {
     908                 :           9 :         ReorderBufferTXN *txn = NULL;
     909                 :           9 :         volatile Snapshot snapshot_now = snap;
     910                 :             : 
     911                 :             :         /* Non-transactional changes require a valid snapshot. */
     912                 :             :         Assert(snapshot_now);
     913                 :             : 
     914         [ +  + ]:           9 :         if (xid != InvalidTransactionId)
     915                 :           3 :             txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
     916                 :             : 
     917                 :             :         /* setup snapshot to allow catalog access */
     918                 :           9 :         SetupHistoricSnapshot(snapshot_now, NULL);
     919         [ +  - ]:           9 :         PG_TRY();
     920                 :             :         {
     921                 :           9 :             rb->message(rb, txn, lsn, false, prefix, message_size, message);
     922                 :             : 
     923                 :           9 :             TeardownHistoricSnapshot(false);
     924                 :             :         }
     925                 :           0 :         PG_CATCH();
     926                 :             :         {
     927                 :           0 :             TeardownHistoricSnapshot(true);
     928                 :           0 :             PG_RE_THROW();
     929                 :             :         }
     930         [ -  + ]:           9 :         PG_END_TRY();
     931                 :             :     }
     932                 :          48 : }
     933                 :             : 
     934                 :             : /*
     935                 :             :  * AssertTXNLsnOrder
     936                 :             :  *      Verify LSN ordering of transaction lists in the reorderbuffer
     937                 :             :  *
     938                 :             :  * Other LSN-related invariants are checked too.
     939                 :             :  *
     940                 :             :  * No-op if assertions are not in use.
     941                 :             :  */
     942                 :             : static void
     943                 :       10127 : AssertTXNLsnOrder(ReorderBuffer *rb)
     944                 :             : {
     945                 :             : #ifdef USE_ASSERT_CHECKING
     946                 :             :     LogicalDecodingContext *ctx = rb->private_data;
     947                 :             :     dlist_iter  iter;
     948                 :             :     XLogRecPtr  prev_first_lsn = InvalidXLogRecPtr;
     949                 :             :     XLogRecPtr  prev_base_snap_lsn = InvalidXLogRecPtr;
     950                 :             : 
     951                 :             :     /*
     952                 :             :      * Skip the verification if we don't reach the LSN at which we start
     953                 :             :      * decoding the contents of transactions yet because until we reach the
     954                 :             :      * LSN, we could have transactions that don't have the association between
     955                 :             :      * the top-level transaction and subtransaction yet and consequently have
     956                 :             :      * the same LSN.  We don't guarantee this association until we try to
     957                 :             :      * decode the actual contents of transaction. The ordering of the records
     958                 :             :      * prior to the start_decoding_at LSN should have been checked before the
     959                 :             :      * restart.
     960                 :             :      */
     961                 :             :     if (SnapBuildXactNeedsSkip(ctx->snapshot_builder, ctx->reader->EndRecPtr))
     962                 :             :         return;
     963                 :             : 
     964                 :             :     dlist_foreach(iter, &rb->toplevel_by_lsn)
     965                 :             :     {
     966                 :             :         ReorderBufferTXN *cur_txn = dlist_container(ReorderBufferTXN, node,
     967                 :             :                                                     iter.cur);
     968                 :             : 
     969                 :             :         /* start LSN must be set */
     970                 :             :         Assert(XLogRecPtrIsValid(cur_txn->first_lsn));
     971                 :             : 
     972                 :             :         /* If there is an end LSN, it must be higher than start LSN */
     973                 :             :         if (XLogRecPtrIsValid(cur_txn->end_lsn))
     974                 :             :             Assert(cur_txn->first_lsn <= cur_txn->end_lsn);
     975                 :             : 
     976                 :             :         /* Current initial LSN must be strictly higher than previous */
     977                 :             :         if (XLogRecPtrIsValid(prev_first_lsn))
     978                 :             :             Assert(prev_first_lsn < cur_txn->first_lsn);
     979                 :             : 
     980                 :             :         /* known-as-subtxn txns must not be listed */
     981                 :             :         Assert(!rbtxn_is_known_subxact(cur_txn));
     982                 :             : 
     983                 :             :         prev_first_lsn = cur_txn->first_lsn;
     984                 :             :     }
     985                 :             : 
     986                 :             :     dlist_foreach(iter, &rb->txns_by_base_snapshot_lsn)
     987                 :             :     {
     988                 :             :         ReorderBufferTXN *cur_txn = dlist_container(ReorderBufferTXN,
     989                 :             :                                                     base_snapshot_node,
     990                 :             :                                                     iter.cur);
     991                 :             : 
     992                 :             :         /* base snapshot (and its LSN) must be set */
     993                 :             :         Assert(cur_txn->base_snapshot != NULL);
     994                 :             :         Assert(XLogRecPtrIsValid(cur_txn->base_snapshot_lsn));
     995                 :             : 
     996                 :             :         /* current LSN must be strictly higher than previous */
     997                 :             :         if (XLogRecPtrIsValid(prev_base_snap_lsn))
     998                 :             :             Assert(prev_base_snap_lsn < cur_txn->base_snapshot_lsn);
     999                 :             : 
    1000                 :             :         /* known-as-subtxn txns must not be listed */
    1001                 :             :         Assert(!rbtxn_is_known_subxact(cur_txn));
    1002                 :             : 
    1003                 :             :         prev_base_snap_lsn = cur_txn->base_snapshot_lsn;
    1004                 :             :     }
    1005                 :             : #endif
    1006                 :       10127 : }
    1007                 :             : 
    1008                 :             : /*
    1009                 :             :  * AssertChangeLsnOrder
    1010                 :             :  *
    1011                 :             :  * Check ordering of changes in the (sub)transaction.
    1012                 :             :  */
    1013                 :             : static void
    1014                 :        3046 : AssertChangeLsnOrder(ReorderBufferTXN *txn)
    1015                 :             : {
    1016                 :             : #ifdef USE_ASSERT_CHECKING
    1017                 :             :     dlist_iter  iter;
    1018                 :             :     XLogRecPtr  prev_lsn = txn->first_lsn;
    1019                 :             : 
    1020                 :             :     dlist_foreach(iter, &txn->changes)
    1021                 :             :     {
    1022                 :             :         ReorderBufferChange *cur_change;
    1023                 :             : 
    1024                 :             :         cur_change = dlist_container(ReorderBufferChange, node, iter.cur);
    1025                 :             : 
    1026                 :             :         Assert(XLogRecPtrIsValid(txn->first_lsn));
    1027                 :             :         Assert(XLogRecPtrIsValid(cur_change->lsn));
    1028                 :             :         Assert(txn->first_lsn <= cur_change->lsn);
    1029                 :             : 
    1030                 :             :         if (XLogRecPtrIsValid(txn->end_lsn))
    1031                 :             :             Assert(cur_change->lsn <= txn->end_lsn);
    1032                 :             : 
    1033                 :             :         Assert(prev_lsn <= cur_change->lsn);
    1034                 :             : 
    1035                 :             :         prev_lsn = cur_change->lsn;
    1036                 :             :     }
    1037                 :             : #endif
    1038                 :        3046 : }
    1039                 :             : 
    1040                 :             : /*
    1041                 :             :  * ReorderBufferGetOldestTXN
    1042                 :             :  *      Return oldest transaction in reorderbuffer
    1043                 :             :  */
    1044                 :             : ReorderBufferTXN *
    1045                 :         519 : ReorderBufferGetOldestTXN(ReorderBuffer *rb)
    1046                 :             : {
    1047                 :             :     ReorderBufferTXN *txn;
    1048                 :             : 
    1049                 :         519 :     AssertTXNLsnOrder(rb);
    1050                 :             : 
    1051         [ +  + ]:         519 :     if (dlist_is_empty(&rb->toplevel_by_lsn))
    1052                 :         461 :         return NULL;
    1053                 :             : 
    1054                 :          58 :     txn = dlist_head_element(ReorderBufferTXN, node, &rb->toplevel_by_lsn);
    1055                 :             : 
    1056                 :             :     Assert(!rbtxn_is_known_subxact(txn));
    1057                 :             :     Assert(XLogRecPtrIsValid(txn->first_lsn));
    1058                 :          58 :     return txn;
    1059                 :             : }
    1060                 :             : 
    1061                 :             : /*
    1062                 :             :  * ReorderBufferGetOldestXmin
    1063                 :             :  *      Return oldest Xmin in reorderbuffer
    1064                 :             :  *
    1065                 :             :  * Returns oldest possibly running Xid from the point of view of snapshots
    1066                 :             :  * used in the transactions kept by reorderbuffer, or InvalidTransactionId if
    1067                 :             :  * there are none.
    1068                 :             :  *
    1069                 :             :  * Since snapshots are assigned monotonically, this equals the Xmin of the
    1070                 :             :  * base snapshot with minimal base_snapshot_lsn.
    1071                 :             :  */
    1072                 :             : TransactionId
    1073                 :         537 : ReorderBufferGetOldestXmin(ReorderBuffer *rb)
    1074                 :             : {
    1075                 :             :     ReorderBufferTXN *txn;
    1076                 :             : 
    1077                 :         537 :     AssertTXNLsnOrder(rb);
    1078                 :             : 
    1079         [ +  + ]:         537 :     if (dlist_is_empty(&rb->txns_by_base_snapshot_lsn))
    1080                 :         488 :         return InvalidTransactionId;
    1081                 :             : 
    1082                 :          49 :     txn = dlist_head_element(ReorderBufferTXN, base_snapshot_node,
    1083                 :             :                              &rb->txns_by_base_snapshot_lsn);
    1084                 :          49 :     return txn->base_snapshot->xmin;
    1085                 :             : }
    1086                 :             : 
    1087                 :             : void
    1088                 :         613 : ReorderBufferSetRestartPoint(ReorderBuffer *rb, XLogRecPtr ptr)
    1089                 :             : {
    1090                 :         613 :     rb->current_restart_decoding_lsn = ptr;
    1091                 :         613 : }
    1092                 :             : 
    1093                 :             : /*
    1094                 :             :  * ReorderBufferAssignChild
    1095                 :             :  *
    1096                 :             :  * Make note that we know that subxid is a subtransaction of xid, seen as of
    1097                 :             :  * the given lsn.
    1098                 :             :  */
    1099                 :             : void
    1100                 :         848 : ReorderBufferAssignChild(ReorderBuffer *rb, TransactionId xid,
    1101                 :             :                          TransactionId subxid, XLogRecPtr lsn)
    1102                 :             : {
    1103                 :             :     ReorderBufferTXN *txn;
    1104                 :             :     ReorderBufferTXN *subtxn;
    1105                 :             :     bool        new_top;
    1106                 :             :     bool        new_sub;
    1107                 :             : 
    1108                 :         848 :     txn = ReorderBufferTXNByXid(rb, xid, true, &new_top, lsn, true);
    1109                 :         848 :     subtxn = ReorderBufferTXNByXid(rb, subxid, true, &new_sub, lsn, false);
    1110                 :             : 
    1111         [ +  + ]:         848 :     if (!new_sub)
    1112                 :             :     {
    1113         [ +  - ]:         189 :         if (rbtxn_is_known_subxact(subtxn))
    1114                 :             :         {
    1115                 :             :             /* already associated, nothing to do */
    1116                 :         189 :             return;
    1117                 :             :         }
    1118                 :             :         else
    1119                 :             :         {
    1120                 :             :             /*
    1121                 :             :              * We already saw this transaction, but initially added it to the
    1122                 :             :              * list of top-level txns.  Now that we know it's not top-level,
    1123                 :             :              * remove it from there.
    1124                 :             :              */
    1125                 :           0 :             dlist_delete(&subtxn->node);
    1126                 :             :         }
    1127                 :             :     }
    1128                 :             : 
    1129                 :         659 :     subtxn->txn_flags |= RBTXN_IS_SUBXACT;
    1130                 :         659 :     subtxn->toplevel_xid = xid;
    1131                 :             :     Assert(subtxn->nsubtxns == 0);
    1132                 :             : 
    1133                 :             :     /* set the reference to top-level transaction */
    1134                 :         659 :     subtxn->toptxn = txn;
    1135                 :             : 
    1136                 :             :     /* add to subtransaction list */
    1137                 :         659 :     dlist_push_tail(&txn->subtxns, &subtxn->node);
    1138                 :         659 :     txn->nsubtxns++;
    1139                 :             : 
    1140                 :             :     /* Possibly transfer the subtxn's snapshot to its top-level txn. */
    1141                 :         659 :     ReorderBufferTransferSnapToParent(txn, subtxn);
    1142                 :             : 
    1143                 :             :     /* Verify LSN-ordering invariant */
    1144                 :         659 :     AssertTXNLsnOrder(rb);
    1145                 :             : }
    1146                 :             : 
    1147                 :             : /*
    1148                 :             :  * ReorderBufferTransferSnapToParent
    1149                 :             :  *      Transfer base snapshot from subtxn to top-level txn, if needed
    1150                 :             :  *
    1151                 :             :  * This is done if the top-level txn doesn't have a base snapshot, or if the
    1152                 :             :  * subtxn's base snapshot has an earlier LSN than the top-level txn's base
    1153                 :             :  * snapshot's LSN.  This can happen if there are no changes in the toplevel
    1154                 :             :  * txn but there are some in the subtxn, or the first change in subtxn has
    1155                 :             :  * earlier LSN than first change in the top-level txn and we learned about
    1156                 :             :  * their kinship only now.
    1157                 :             :  *
    1158                 :             :  * The subtransaction's snapshot is cleared regardless of the transfer
    1159                 :             :  * happening, since it's not needed anymore in either case.
    1160                 :             :  *
    1161                 :             :  * We do this as soon as we become aware of their kinship, to avoid queueing
    1162                 :             :  * extra snapshots to txns known-as-subtxns -- only top-level txns will
    1163                 :             :  * receive further snapshots.
    1164                 :             :  */
    1165                 :             : static void
    1166                 :         663 : ReorderBufferTransferSnapToParent(ReorderBufferTXN *txn,
    1167                 :             :                                   ReorderBufferTXN *subtxn)
    1168                 :             : {
    1169                 :             :     Assert(subtxn->toplevel_xid == txn->xid);
    1170                 :             : 
    1171         [ -  + ]:         663 :     if (subtxn->base_snapshot != NULL)
    1172                 :             :     {
    1173         [ #  # ]:           0 :         if (txn->base_snapshot == NULL ||
    1174         [ #  # ]:           0 :             subtxn->base_snapshot_lsn < txn->base_snapshot_lsn)
    1175                 :             :         {
    1176                 :             :             /*
    1177                 :             :              * If the toplevel transaction already has a base snapshot but
    1178                 :             :              * it's newer than the subxact's, purge it.
    1179                 :             :              */
    1180         [ #  # ]:           0 :             if (txn->base_snapshot != NULL)
    1181                 :             :             {
    1182                 :           0 :                 SnapBuildSnapDecRefcount(txn->base_snapshot);
    1183                 :           0 :                 dlist_delete(&txn->base_snapshot_node);
    1184                 :             :             }
    1185                 :             : 
    1186                 :             :             /*
    1187                 :             :              * The snapshot is now the top transaction's; transfer it, and
    1188                 :             :              * adjust the list position of the top transaction in the list by
    1189                 :             :              * moving it to where the subtransaction is.
    1190                 :             :              */
    1191                 :           0 :             txn->base_snapshot = subtxn->base_snapshot;
    1192                 :           0 :             txn->base_snapshot_lsn = subtxn->base_snapshot_lsn;
    1193                 :           0 :             dlist_insert_before(&subtxn->base_snapshot_node,
    1194                 :             :                                 &txn->base_snapshot_node);
    1195                 :             : 
    1196                 :             :             /*
    1197                 :             :              * The subtransaction doesn't have a snapshot anymore (so it
    1198                 :             :              * mustn't be in the list.)
    1199                 :             :              */
    1200                 :           0 :             subtxn->base_snapshot = NULL;
    1201                 :           0 :             subtxn->base_snapshot_lsn = InvalidXLogRecPtr;
    1202                 :           0 :             dlist_delete(&subtxn->base_snapshot_node);
    1203                 :             :         }
    1204                 :             :         else
    1205                 :             :         {
    1206                 :             :             /* Base snap of toplevel is fine, so subxact's is not needed */
    1207                 :           0 :             SnapBuildSnapDecRefcount(subtxn->base_snapshot);
    1208                 :           0 :             dlist_delete(&subtxn->base_snapshot_node);
    1209                 :           0 :             subtxn->base_snapshot = NULL;
    1210                 :           0 :             subtxn->base_snapshot_lsn = InvalidXLogRecPtr;
    1211                 :             :         }
    1212                 :             :     }
    1213                 :         663 : }
    1214                 :             : 
    1215                 :             : /*
    1216                 :             :  * Associate a subtransaction with its toplevel transaction at commit
    1217                 :             :  * time. There may be no further changes added after this.
    1218                 :             :  */
    1219                 :             : void
    1220                 :         270 : ReorderBufferCommitChild(ReorderBuffer *rb, TransactionId xid,
    1221                 :             :                          TransactionId subxid, XLogRecPtr commit_lsn,
    1222                 :             :                          XLogRecPtr end_lsn)
    1223                 :             : {
    1224                 :             :     ReorderBufferTXN *subtxn;
    1225                 :             : 
    1226                 :         270 :     subtxn = ReorderBufferTXNByXid(rb, subxid, false, NULL,
    1227                 :             :                                    InvalidXLogRecPtr, false);
    1228                 :             : 
    1229                 :             :     /*
    1230                 :             :      * No need to do anything if that subtxn didn't contain any changes
    1231                 :             :      */
    1232         [ +  + ]:         270 :     if (!subtxn)
    1233                 :          81 :         return;
    1234                 :             : 
    1235                 :         189 :     subtxn->final_lsn = commit_lsn;
    1236                 :         189 :     subtxn->end_lsn = end_lsn;
    1237                 :             : 
    1238                 :             :     /*
    1239                 :             :      * Assign this subxact as a child of the toplevel xact (no-op if already
    1240                 :             :      * done.)
    1241                 :             :      */
    1242                 :         189 :     ReorderBufferAssignChild(rb, xid, subxid, InvalidXLogRecPtr);
    1243                 :             : }
    1244                 :             : 
    1245                 :             : 
    1246                 :             : /*
    1247                 :             :  * Support for efficiently iterating over a transaction's and its
    1248                 :             :  * subtransactions' changes.
    1249                 :             :  *
    1250                 :             :  * We do by doing a k-way merge between transactions/subtransactions. For that
    1251                 :             :  * we model the current heads of the different transactions as a binary heap
    1252                 :             :  * so we easily know which (sub-)transaction has the change with the smallest
    1253                 :             :  * lsn next.
    1254                 :             :  *
    1255                 :             :  * We assume the changes in individual transactions are already sorted by LSN.
    1256                 :             :  */
    1257                 :             : 
    1258                 :             : /*
    1259                 :             :  * Binary heap comparison function.
    1260                 :             :  */
    1261                 :             : static int
    1262                 :       51571 : ReorderBufferIterCompare(Datum a, Datum b, void *arg)
    1263                 :             : {
    1264                 :       51571 :     ReorderBufferIterTXNState *state = (ReorderBufferIterTXNState *) arg;
    1265                 :       51571 :     XLogRecPtr  pos_a = state->entries[DatumGetInt32(a)].lsn;
    1266                 :       51571 :     XLogRecPtr  pos_b = state->entries[DatumGetInt32(b)].lsn;
    1267                 :             : 
    1268         [ +  + ]:       51571 :     if (pos_a < pos_b)
    1269                 :       50714 :         return 1;
    1270         [ -  + ]:         857 :     else if (pos_a == pos_b)
    1271                 :           0 :         return 0;
    1272                 :         857 :     return -1;
    1273                 :             : }
    1274                 :             : 
    1275                 :             : /*
    1276                 :             :  * Allocate & initialize an iterator which iterates in lsn order over a
    1277                 :             :  * transaction and all its subtransactions.
    1278                 :             :  *
    1279                 :             :  * Note: The iterator state is returned through iter_state parameter rather
    1280                 :             :  * than the function's return value.  This is because the state gets cleaned up
    1281                 :             :  * in a PG_CATCH block in the caller, so we want to make sure the caller gets
    1282                 :             :  * back the state even if this function throws an exception.
    1283                 :             :  */
    1284                 :             : static void
    1285                 :        2580 : ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn,
    1286                 :             :                          ReorderBufferIterTXNState *volatile *iter_state)
    1287                 :             : {
    1288                 :        2580 :     Size        nr_txns = 0;
    1289                 :             :     ReorderBufferIterTXNState *state;
    1290                 :             :     dlist_iter  cur_txn_i;
    1291                 :             :     Size        off;
    1292                 :             : 
    1293                 :        2580 :     *iter_state = NULL;
    1294                 :             : 
    1295                 :             :     /* Check ordering of changes in the toplevel transaction. */
    1296                 :        2580 :     AssertChangeLsnOrder(txn);
    1297                 :             : 
    1298                 :             :     /*
    1299                 :             :      * Calculate the size of our heap: one element for every transaction that
    1300                 :             :      * contains changes.  (Besides the transactions already in the reorder
    1301                 :             :      * buffer, we count the one we were directly passed.)
    1302                 :             :      */
    1303         [ +  + ]:        2580 :     if (txn->nentries > 0)
    1304                 :        2394 :         nr_txns++;
    1305                 :             : 
    1306   [ +  -  +  + ]:        3046 :     dlist_foreach(cur_txn_i, &txn->subtxns)
    1307                 :             :     {
    1308                 :             :         ReorderBufferTXN *cur_txn;
    1309                 :             : 
    1310                 :         466 :         cur_txn = dlist_container(ReorderBufferTXN, node, cur_txn_i.cur);
    1311                 :             : 
    1312                 :             :         /* Check ordering of changes in this subtransaction. */
    1313                 :         466 :         AssertChangeLsnOrder(cur_txn);
    1314                 :             : 
    1315         [ +  + ]:         466 :         if (cur_txn->nentries > 0)
    1316                 :         304 :             nr_txns++;
    1317                 :             :     }
    1318                 :             : 
    1319                 :             :     /* allocate iteration state */
    1320                 :             :     state = (ReorderBufferIterTXNState *)
    1321                 :        2580 :         MemoryContextAllocZero(rb->context,
    1322                 :             :                                sizeof(ReorderBufferIterTXNState) +
    1323                 :        2580 :                                sizeof(ReorderBufferIterTXNEntry) * nr_txns);
    1324                 :             : 
    1325                 :        2580 :     state->nr_txns = nr_txns;
    1326                 :        2580 :     dlist_init(&state->old_change);
    1327                 :             : 
    1328         [ +  + ]:        5278 :     for (off = 0; off < state->nr_txns; off++)
    1329                 :             :     {
    1330                 :        2698 :         state->entries[off].file.vfd = -1;
    1331                 :        2698 :         state->entries[off].segno = 0;
    1332                 :             :     }
    1333                 :             : 
    1334                 :             :     /* allocate heap */
    1335                 :        2580 :     state->heap = binaryheap_allocate(state->nr_txns,
    1336                 :             :                                       ReorderBufferIterCompare,
    1337                 :             :                                       state);
    1338                 :             : 
    1339                 :             :     /* Now that the state fields are initialized, it is safe to return it. */
    1340                 :        2580 :     *iter_state = state;
    1341                 :             : 
    1342                 :             :     /*
    1343                 :             :      * Now insert items into the binary heap, in an unordered fashion.  (We
    1344                 :             :      * will run a heap assembly step at the end; this is more efficient.)
    1345                 :             :      */
    1346                 :             : 
    1347                 :        2580 :     off = 0;
    1348                 :             : 
    1349                 :             :     /* add toplevel transaction if it contains changes */
    1350         [ +  + ]:        2580 :     if (txn->nentries > 0)
    1351                 :             :     {
    1352                 :             :         ReorderBufferChange *cur_change;
    1353                 :             : 
    1354         [ +  + ]:        2394 :         if (rbtxn_is_serialized(txn))
    1355                 :             :         {
    1356                 :             :             /* serialize remaining changes */
    1357                 :          23 :             ReorderBufferSerializeTXN(rb, txn);
    1358                 :          23 :             ReorderBufferRestoreChanges(rb, txn, &state->entries[off].file,
    1359                 :             :                                         &state->entries[off].segno);
    1360                 :             :         }
    1361                 :             : 
    1362                 :        2394 :         cur_change = dlist_head_element(ReorderBufferChange, node,
    1363                 :             :                                         &txn->changes);
    1364                 :             : 
    1365                 :        2394 :         state->entries[off].lsn = cur_change->lsn;
    1366                 :        2394 :         state->entries[off].change = cur_change;
    1367                 :        2394 :         state->entries[off].txn = txn;
    1368                 :             : 
    1369                 :        2394 :         binaryheap_add_unordered(state->heap, Int32GetDatum(off++));
    1370                 :             :     }
    1371                 :             : 
    1372                 :             :     /* add subtransactions if they contain changes */
    1373   [ +  -  +  + ]:        3046 :     dlist_foreach(cur_txn_i, &txn->subtxns)
    1374                 :             :     {
    1375                 :             :         ReorderBufferTXN *cur_txn;
    1376                 :             : 
    1377                 :         466 :         cur_txn = dlist_container(ReorderBufferTXN, node, cur_txn_i.cur);
    1378                 :             : 
    1379         [ +  + ]:         466 :         if (cur_txn->nentries > 0)
    1380                 :             :         {
    1381                 :             :             ReorderBufferChange *cur_change;
    1382                 :             : 
    1383         [ +  + ]:         304 :             if (rbtxn_is_serialized(cur_txn))
    1384                 :             :             {
    1385                 :             :                 /* serialize remaining changes */
    1386                 :          17 :                 ReorderBufferSerializeTXN(rb, cur_txn);
    1387                 :          17 :                 ReorderBufferRestoreChanges(rb, cur_txn,
    1388                 :             :                                             &state->entries[off].file,
    1389                 :             :                                             &state->entries[off].segno);
    1390                 :             :             }
    1391                 :         304 :             cur_change = dlist_head_element(ReorderBufferChange, node,
    1392                 :             :                                             &cur_txn->changes);
    1393                 :             : 
    1394                 :         304 :             state->entries[off].lsn = cur_change->lsn;
    1395                 :         304 :             state->entries[off].change = cur_change;
    1396                 :         304 :             state->entries[off].txn = cur_txn;
    1397                 :             : 
    1398                 :         304 :             binaryheap_add_unordered(state->heap, Int32GetDatum(off++));
    1399                 :             :         }
    1400                 :             :     }
    1401                 :             : 
    1402                 :             :     /* assemble a valid binary heap */
    1403                 :        2580 :     binaryheap_build(state->heap);
    1404                 :        2580 : }
    1405                 :             : 
    1406                 :             : /*
    1407                 :             :  * Return the next change when iterating over a transaction and its
    1408                 :             :  * subtransactions.
    1409                 :             :  *
    1410                 :             :  * Returns NULL when no further changes exist.
    1411                 :             :  */
    1412                 :             : static ReorderBufferChange *
    1413                 :      386024 : ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state)
    1414                 :             : {
    1415                 :             :     ReorderBufferChange *change;
    1416                 :             :     ReorderBufferIterTXNEntry *entry;
    1417                 :             :     int32       off;
    1418                 :             : 
    1419                 :             :     /* nothing there anymore */
    1420         [ +  + ]:      386024 :     if (binaryheap_empty(state->heap))
    1421                 :        2565 :         return NULL;
    1422                 :             : 
    1423                 :      383459 :     off = DatumGetInt32(binaryheap_first(state->heap));
    1424                 :      383459 :     entry = &state->entries[off];
    1425                 :             : 
    1426                 :             :     /* free memory we might have "leaked" in the previous *Next call */
    1427         [ +  + ]:      383459 :     if (!dlist_is_empty(&state->old_change))
    1428                 :             :     {
    1429                 :          45 :         change = dlist_container(ReorderBufferChange, node,
    1430                 :             :                                  dlist_pop_head_node(&state->old_change));
    1431                 :          45 :         ReorderBufferFreeChange(rb, change, true);
    1432                 :             :         Assert(dlist_is_empty(&state->old_change));
    1433                 :             :     }
    1434                 :             : 
    1435                 :      383459 :     change = entry->change;
    1436                 :             : 
    1437                 :             :     /*
    1438                 :             :      * update heap with information about which transaction has the next
    1439                 :             :      * relevant change in LSN order
    1440                 :             :      */
    1441                 :             : 
    1442                 :             :     /* there are in-memory changes */
    1443         [ +  + ]:      383459 :     if (dlist_has_next(&entry->txn->changes, &entry->change->node))
    1444                 :             :     {
    1445                 :      380729 :         dlist_node *next = dlist_next_node(&entry->txn->changes, &change->node);
    1446                 :      380729 :         ReorderBufferChange *next_change =
    1447                 :             :             dlist_container(ReorderBufferChange, node, next);
    1448                 :             : 
    1449                 :             :         /* txn stays the same */
    1450                 :      380729 :         state->entries[off].lsn = next_change->lsn;
    1451                 :      380729 :         state->entries[off].change = next_change;
    1452                 :             : 
    1453                 :      380729 :         binaryheap_replace_first(state->heap, Int32GetDatum(off));
    1454                 :      380729 :         return change;
    1455                 :             :     }
    1456                 :             : 
    1457                 :             :     /* try to load changes from disk */
    1458         [ +  + ]:        2730 :     if (entry->txn->nentries != entry->txn->nentries_mem)
    1459                 :             :     {
    1460                 :             :         /*
    1461                 :             :          * Ugly: restoring changes will reuse *Change records, thus delete the
    1462                 :             :          * current one from the per-tx list and only free in the next call.
    1463                 :             :          */
    1464                 :          65 :         dlist_delete(&change->node);
    1465                 :          65 :         dlist_push_tail(&state->old_change, &change->node);
    1466                 :             : 
    1467                 :             :         /*
    1468                 :             :          * Update the total bytes processed by the txn for which we are
    1469                 :             :          * releasing the current set of changes and restoring the new set of
    1470                 :             :          * changes.
    1471                 :             :          */
    1472                 :          65 :         rb->totalBytes += entry->txn->size;
    1473         [ +  + ]:          65 :         if (ReorderBufferRestoreChanges(rb, entry->txn, &entry->file,
    1474                 :             :                                         &state->entries[off].segno))
    1475                 :             :         {
    1476                 :             :             /* successfully restored changes from disk */
    1477                 :             :             ReorderBufferChange *next_change =
    1478                 :          36 :                 dlist_head_element(ReorderBufferChange, node,
    1479                 :             :                                    &entry->txn->changes);
    1480                 :             : 
    1481         [ -  + ]:          36 :             elog(DEBUG2, "restored %u/%u changes from disk",
    1482                 :             :                  (uint32) entry->txn->nentries_mem,
    1483                 :             :                  (uint32) entry->txn->nentries);
    1484                 :             : 
    1485                 :             :             Assert(entry->txn->nentries_mem);
    1486                 :             :             /* txn stays the same */
    1487                 :          36 :             state->entries[off].lsn = next_change->lsn;
    1488                 :          36 :             state->entries[off].change = next_change;
    1489                 :          36 :             binaryheap_replace_first(state->heap, Int32GetDatum(off));
    1490                 :             : 
    1491                 :          36 :             return change;
    1492                 :             :         }
    1493                 :             :     }
    1494                 :             : 
    1495                 :             :     /* ok, no changes there anymore, remove */
    1496                 :        2694 :     binaryheap_remove_first(state->heap);
    1497                 :             : 
    1498                 :        2694 :     return change;
    1499                 :             : }
    1500                 :             : 
    1501                 :             : /*
    1502                 :             :  * Deallocate the iterator
    1503                 :             :  */
    1504                 :             : static void
    1505                 :        2576 : ReorderBufferIterTXNFinish(ReorderBuffer *rb,
    1506                 :             :                            ReorderBufferIterTXNState *state)
    1507                 :             : {
    1508                 :             :     Size        off;
    1509                 :             : 
    1510         [ +  + ]:        5270 :     for (off = 0; off < state->nr_txns; off++)
    1511                 :             :     {
    1512         [ -  + ]:        2694 :         if (state->entries[off].file.vfd != -1)
    1513                 :           0 :             FileClose(state->entries[off].file.vfd);
    1514                 :             :     }
    1515                 :             : 
    1516                 :             :     /* free memory we might have "leaked" in the last *Next call */
    1517         [ +  + ]:        2576 :     if (!dlist_is_empty(&state->old_change))
    1518                 :             :     {
    1519                 :             :         ReorderBufferChange *change;
    1520                 :             : 
    1521                 :          19 :         change = dlist_container(ReorderBufferChange, node,
    1522                 :             :                                  dlist_pop_head_node(&state->old_change));
    1523                 :          19 :         ReorderBufferFreeChange(rb, change, true);
    1524                 :             :         Assert(dlist_is_empty(&state->old_change));
    1525                 :             :     }
    1526                 :             : 
    1527                 :        2576 :     binaryheap_free(state->heap);
    1528                 :        2576 :     pfree(state);
    1529                 :        2576 : }
    1530                 :             : 
    1531                 :             : /*
    1532                 :             :  * Cleanup the contents of a transaction, usually after the transaction
    1533                 :             :  * committed or aborted.
    1534                 :             :  */
    1535                 :             : static void
    1536                 :        4802 : ReorderBufferCleanupTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
    1537                 :             : {
    1538                 :             :     bool        found;
    1539                 :             :     dlist_mutable_iter iter;
    1540                 :        4802 :     Size        mem_freed = 0;
    1541                 :             : 
    1542                 :             :     /* cleanup subtransactions & their changes */
    1543   [ +  -  +  + ]:        4990 :     dlist_foreach_modify(iter, &txn->subtxns)
    1544                 :             :     {
    1545                 :             :         ReorderBufferTXN *subtxn;
    1546                 :             : 
    1547                 :         188 :         subtxn = dlist_container(ReorderBufferTXN, node, iter.cur);
    1548                 :             : 
    1549                 :             :         /*
    1550                 :             :          * Subtransactions are always associated to the toplevel TXN, even if
    1551                 :             :          * they originally were happening inside another subtxn, so we won't
    1552                 :             :          * ever recurse more than one level deep here.
    1553                 :             :          */
    1554                 :             :         Assert(rbtxn_is_known_subxact(subtxn));
    1555                 :             :         Assert(subtxn->nsubtxns == 0);
    1556                 :             : 
    1557                 :         188 :         ReorderBufferCleanupTXN(rb, subtxn);
    1558                 :             :     }
    1559                 :             : 
    1560                 :             :     /* cleanup changes in the txn */
    1561   [ +  -  +  + ]:      105490 :     dlist_foreach_modify(iter, &txn->changes)
    1562                 :             :     {
    1563                 :             :         ReorderBufferChange *change;
    1564                 :             : 
    1565                 :      100688 :         change = dlist_container(ReorderBufferChange, node, iter.cur);
    1566                 :             : 
    1567                 :             :         /* Check we're not mixing changes from different transactions. */
    1568                 :             :         Assert(change->txn == txn);
    1569                 :             : 
    1570                 :             :         /*
    1571                 :             :          * Instead of updating the memory counter for individual changes, we
    1572                 :             :          * sum up the size of memory to free so we can update the memory
    1573                 :             :          * counter all together below. This saves costs of maintaining the
    1574                 :             :          * max-heap.
    1575                 :             :          */
    1576                 :      100688 :         mem_freed += ReorderBufferChangeSize(change);
    1577                 :             : 
    1578                 :      100688 :         ReorderBufferFreeChange(rb, change, false);
    1579                 :             :     }
    1580                 :             : 
    1581                 :             :     /* Update the memory counter */
    1582                 :        4802 :     ReorderBufferChangeMemoryUpdate(rb, NULL, txn, false, mem_freed);
    1583                 :             : 
    1584                 :             :     /*
    1585                 :             :      * Cleanup the tuplecids we stored for decoding catalog snapshot access.
    1586                 :             :      * They are always stored in the toplevel transaction.
    1587                 :             :      */
    1588   [ +  -  +  + ]:       32443 :     dlist_foreach_modify(iter, &txn->tuplecids)
    1589                 :             :     {
    1590                 :             :         ReorderBufferChange *change;
    1591                 :             : 
    1592                 :       27641 :         change = dlist_container(ReorderBufferChange, node, iter.cur);
    1593                 :             : 
    1594                 :             :         /* Check we're not mixing changes from different transactions. */
    1595                 :             :         Assert(change->txn == txn);
    1596                 :             :         Assert(change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
    1597                 :             : 
    1598                 :       27641 :         ReorderBufferFreeChange(rb, change, true);
    1599                 :             :     }
    1600                 :             : 
    1601                 :             :     /*
    1602                 :             :      * Cleanup the base snapshot, if set.
    1603                 :             :      */
    1604         [ +  + ]:        4802 :     if (txn->base_snapshot != NULL)
    1605                 :             :     {
    1606                 :        4127 :         SnapBuildSnapDecRefcount(txn->base_snapshot);
    1607                 :        4127 :         dlist_delete(&txn->base_snapshot_node);
    1608                 :             :     }
    1609                 :             : 
    1610                 :             :     /*
    1611                 :             :      * Cleanup the snapshot for the last streamed run.
    1612                 :             :      */
    1613         [ +  + ]:        4802 :     if (txn->snapshot_now != NULL)
    1614                 :             :     {
    1615                 :             :         Assert(rbtxn_is_streamed(txn));
    1616                 :          65 :         ReorderBufferFreeSnap(rb, txn->snapshot_now);
    1617                 :             :     }
    1618                 :             : 
    1619                 :             :     /*
    1620                 :             :      * Remove TXN from its containing lists.
    1621                 :             :      *
    1622                 :             :      * Note: if txn is known as subxact, we are deleting the TXN from its
    1623                 :             :      * parent's list of known subxacts; this leaves the parent's nsubxacts
    1624                 :             :      * count too high, but we don't care.  Otherwise, we are deleting the TXN
    1625                 :             :      * from the LSN-ordered list of toplevel TXNs. We remove the TXN from the
    1626                 :             :      * list of catalog modifying transactions as well.
    1627                 :             :      */
    1628                 :        4802 :     dlist_delete(&txn->node);
    1629         [ +  + ]:        4802 :     if (rbtxn_has_catalog_changes(txn))
    1630                 :        1699 :         dclist_delete_from(&rb->catchange_txns, &txn->catchange_node);
    1631                 :             : 
    1632                 :             :     /* now remove reference from buffer */
    1633                 :        4802 :     hash_search(rb->by_txn, &txn->xid, HASH_REMOVE, &found);
    1634                 :             :     Assert(found);
    1635                 :             : 
    1636                 :             :     /* remove entries spilled to disk */
    1637         [ +  + ]:        4802 :     if (rbtxn_is_serialized(txn))
    1638                 :         300 :         ReorderBufferRestoreCleanup(rb, txn);
    1639                 :             : 
    1640                 :             :     /* deallocate */
    1641                 :        4802 :     ReorderBufferFreeTXN(rb, txn);
    1642                 :        4802 : }
    1643                 :             : 
    1644                 :             : /*
    1645                 :             :  * Discard changes from a transaction (and subtransactions), either after
    1646                 :             :  * streaming, decoding them at PREPARE, or detecting the transaction abort.
    1647                 :             :  * Keep the remaining info - transactions, tuplecids, invalidations and
    1648                 :             :  * snapshots.
    1649                 :             :  *
    1650                 :             :  * We additionally remove tuplecids after decoding the transaction at prepare
    1651                 :             :  * time as we only need to perform invalidation at rollback or commit prepared.
    1652                 :             :  *
    1653                 :             :  * 'txn_prepared' indicates that we have decoded the transaction at prepare
    1654                 :             :  * time.
    1655                 :             :  */
    1656                 :             : static void
    1657                 :        1047 : ReorderBufferTruncateTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, bool txn_prepared)
    1658                 :             : {
    1659                 :             :     dlist_mutable_iter iter;
    1660                 :        1047 :     Size        mem_freed = 0;
    1661                 :             : 
    1662                 :             :     /* cleanup subtransactions & their changes */
    1663   [ +  -  +  + ]:        1344 :     dlist_foreach_modify(iter, &txn->subtxns)
    1664                 :             :     {
    1665                 :             :         ReorderBufferTXN *subtxn;
    1666                 :             : 
    1667                 :         297 :         subtxn = dlist_container(ReorderBufferTXN, node, iter.cur);
    1668                 :             : 
    1669                 :             :         /*
    1670                 :             :          * Subtransactions are always associated to the toplevel TXN, even if
    1671                 :             :          * they originally were happening inside another subtxn, so we won't
    1672                 :             :          * ever recurse more than one level deep here.
    1673                 :             :          */
    1674                 :             :         Assert(rbtxn_is_known_subxact(subtxn));
    1675                 :             :         Assert(subtxn->nsubtxns == 0);
    1676                 :             : 
    1677                 :         297 :         ReorderBufferMaybeMarkTXNStreamed(rb, subtxn);
    1678                 :         297 :         ReorderBufferTruncateTXN(rb, subtxn, txn_prepared);
    1679                 :             :     }
    1680                 :             : 
    1681                 :             :     /* cleanup changes in the txn */
    1682   [ +  -  +  + ]:      158667 :     dlist_foreach_modify(iter, &txn->changes)
    1683                 :             :     {
    1684                 :             :         ReorderBufferChange *change;
    1685                 :             : 
    1686                 :      157620 :         change = dlist_container(ReorderBufferChange, node, iter.cur);
    1687                 :             : 
    1688                 :             :         /* Check we're not mixing changes from different transactions. */
    1689                 :             :         Assert(change->txn == txn);
    1690                 :             : 
    1691                 :             :         /* remove the change from its containing list */
    1692                 :      157620 :         dlist_delete(&change->node);
    1693                 :             : 
    1694                 :             :         /*
    1695                 :             :          * Instead of updating the memory counter for individual changes, we
    1696                 :             :          * sum up the size of memory to free so we can update the memory
    1697                 :             :          * counter all together below. This saves costs of maintaining the
    1698                 :             :          * max-heap.
    1699                 :             :          */
    1700                 :      157620 :         mem_freed += ReorderBufferChangeSize(change);
    1701                 :             : 
    1702                 :      157620 :         ReorderBufferFreeChange(rb, change, false);
    1703                 :             :     }
    1704                 :             : 
    1705                 :             :     /* Update the memory counter */
    1706                 :        1047 :     ReorderBufferChangeMemoryUpdate(rb, NULL, txn, false, mem_freed);
    1707                 :             : 
    1708         [ +  + ]:        1047 :     if (txn_prepared)
    1709                 :             :     {
    1710                 :             :         /*
    1711                 :             :          * If this is a prepared txn, cleanup the tuplecids we stored for
    1712                 :             :          * decoding catalog snapshot access. They are always stored in the
    1713                 :             :          * toplevel transaction.
    1714                 :             :          */
    1715   [ +  -  +  + ]:         184 :         dlist_foreach_modify(iter, &txn->tuplecids)
    1716                 :             :         {
    1717                 :             :             ReorderBufferChange *change;
    1718                 :             : 
    1719                 :         123 :             change = dlist_container(ReorderBufferChange, node, iter.cur);
    1720                 :             : 
    1721                 :             :             /* Check we're not mixing changes from different transactions. */
    1722                 :             :             Assert(change->txn == txn);
    1723                 :             :             Assert(change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
    1724                 :             : 
    1725                 :             :             /* Remove the change from its containing list. */
    1726                 :         123 :             dlist_delete(&change->node);
    1727                 :             : 
    1728                 :         123 :             ReorderBufferFreeChange(rb, change, true);
    1729                 :             :         }
    1730                 :             :     }
    1731                 :             : 
    1732                 :             :     /*
    1733                 :             :      * Destroy the (relfilelocator, ctid) hashtable, so that we don't leak any
    1734                 :             :      * memory. We could also keep the hash table and update it with new ctid
    1735                 :             :      * values, but this seems simpler and good enough for now.
    1736                 :             :      */
    1737         [ +  + ]:        1047 :     if (txn->tuplecid_hash != NULL)
    1738                 :             :     {
    1739                 :          51 :         hash_destroy(txn->tuplecid_hash);
    1740                 :          51 :         txn->tuplecid_hash = NULL;
    1741                 :             :     }
    1742                 :             : 
    1743                 :             :     /* If this txn is serialized then clean the disk space. */
    1744         [ +  + ]:        1047 :     if (rbtxn_is_serialized(txn))
    1745                 :             :     {
    1746                 :           9 :         ReorderBufferRestoreCleanup(rb, txn);
    1747                 :           9 :         txn->txn_flags &= ~RBTXN_IS_SERIALIZED;
    1748                 :             : 
    1749                 :             :         /*
    1750                 :             :          * We set this flag to indicate if the transaction is ever serialized.
    1751                 :             :          * We need this to accurately update the stats as otherwise the same
    1752                 :             :          * transaction can be counted as serialized multiple times.
    1753                 :             :          */
    1754                 :           9 :         txn->txn_flags |= RBTXN_IS_SERIALIZED_CLEAR;
    1755                 :             :     }
    1756                 :             : 
    1757                 :             :     /* also reset the number of entries in the transaction */
    1758                 :        1047 :     txn->nentries_mem = 0;
    1759                 :        1047 :     txn->nentries = 0;
    1760                 :        1047 : }
    1761                 :             : 
    1762                 :             : /*
    1763                 :             :  * Check the transaction status by CLOG lookup and discard all changes if
    1764                 :             :  * the transaction is aborted. The transaction status is cached in
    1765                 :             :  * txn->txn_flags so we can skip future changes and avoid CLOG lookups on the
    1766                 :             :  * next call.
    1767                 :             :  *
    1768                 :             :  * Return true if the transaction is aborted, otherwise return false.
    1769                 :             :  *
    1770                 :             :  * When the 'debug_logical_replication_streaming' is set to "immediate", we
    1771                 :             :  * don't check the transaction status, meaning the caller will always process
    1772                 :             :  * this transaction.
    1773                 :             :  */
    1774                 :             : static bool
    1775                 :        4543 : ReorderBufferCheckAndTruncateAbortedTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
    1776                 :             : {
    1777                 :             :     /* Quick return for regression tests */
    1778         [ +  + ]:        4543 :     if (unlikely(debug_logical_replication_streaming == DEBUG_LOGICAL_REP_STREAMING_IMMEDIATE))
    1779                 :        1066 :         return false;
    1780                 :             : 
    1781                 :             :     /*
    1782                 :             :      * Quick return if the transaction status is already known.
    1783                 :             :      */
    1784                 :             : 
    1785         [ +  + ]:        3477 :     if (rbtxn_is_committed(txn))
    1786                 :        3037 :         return false;
    1787         [ -  + ]:         440 :     if (rbtxn_is_aborted(txn))
    1788                 :             :     {
    1789                 :             :         /* Already-aborted transactions should not have any changes */
    1790                 :             :         Assert(txn->size == 0);
    1791                 :             : 
    1792                 :           0 :         return true;
    1793                 :             :     }
    1794                 :             : 
    1795                 :             :     /* Otherwise, check the transaction status using CLOG lookup */
    1796                 :             : 
    1797         [ +  + ]:         440 :     if (TransactionIdIsInProgress(txn->xid))
    1798                 :         211 :         return false;
    1799                 :             : 
    1800         [ +  + ]:         229 :     if (TransactionIdDidCommit(txn->xid))
    1801                 :             :     {
    1802                 :             :         /*
    1803                 :             :          * Remember the transaction is committed so that we can skip CLOG
    1804                 :             :          * check next time, avoiding the pressure on CLOG lookup.
    1805                 :             :          */
    1806                 :             :         Assert(!rbtxn_is_aborted(txn));
    1807                 :         220 :         txn->txn_flags |= RBTXN_IS_COMMITTED;
    1808                 :         220 :         return false;
    1809                 :             :     }
    1810                 :             : 
    1811                 :             :     /*
    1812                 :             :      * The transaction aborted. We discard both the changes collected so far
    1813                 :             :      * and the toast reconstruction data. The full cleanup will happen as part
    1814                 :             :      * of decoding ABORT record of this transaction.
    1815                 :             :      */
    1816                 :           9 :     ReorderBufferTruncateTXN(rb, txn, rbtxn_is_prepared(txn));
    1817                 :           9 :     ReorderBufferToastReset(rb, txn);
    1818                 :             : 
    1819                 :             :     /* All changes should be discarded */
    1820                 :             :     Assert(txn->size == 0);
    1821                 :             : 
    1822                 :             :     /*
    1823                 :             :      * Mark the transaction as aborted so we can ignore future changes of this
    1824                 :             :      * transaction.
    1825                 :             :      */
    1826                 :             :     Assert(!rbtxn_is_committed(txn));
    1827                 :           9 :     txn->txn_flags |= RBTXN_IS_ABORTED;
    1828                 :             : 
    1829                 :           9 :     return true;
    1830                 :             : }
    1831                 :             : 
    1832                 :             : /*
    1833                 :             :  * Build a hash with a (relfilelocator, ctid) -> (cmin, cmax) mapping for use by
    1834                 :             :  * HeapTupleSatisfiesHistoricMVCC.
    1835                 :             :  */
    1836                 :             : static void
    1837                 :        2580 : ReorderBufferBuildTupleCidHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
    1838                 :             : {
    1839                 :             :     dlist_iter  iter;
    1840                 :             :     HASHCTL     hash_ctl;
    1841                 :             : 
    1842   [ +  +  +  + ]:        2580 :     if (!rbtxn_has_catalog_changes(txn) || dlist_is_empty(&txn->tuplecids))
    1843                 :        1580 :         return;
    1844                 :             : 
    1845                 :        1000 :     hash_ctl.keysize = sizeof(ReorderBufferTupleCidKey);
    1846                 :        1000 :     hash_ctl.entrysize = sizeof(ReorderBufferTupleCidEnt);
    1847                 :        1000 :     hash_ctl.hcxt = rb->context;
    1848                 :             : 
    1849                 :             :     /*
    1850                 :             :      * create the hash with the exact number of to-be-stored tuplecids from
    1851                 :             :      * the start
    1852                 :             :      */
    1853                 :        1000 :     txn->tuplecid_hash =
    1854                 :        1000 :         hash_create("ReorderBufferTupleCid", txn->ntuplecids, &hash_ctl,
    1855                 :             :                     HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
    1856                 :             : 
    1857   [ +  -  +  + ]:       14574 :     dlist_foreach(iter, &txn->tuplecids)
    1858                 :             :     {
    1859                 :             :         ReorderBufferTupleCidKey key;
    1860                 :             :         ReorderBufferTupleCidEnt *ent;
    1861                 :             :         bool        found;
    1862                 :             :         ReorderBufferChange *change;
    1863                 :             : 
    1864                 :       13574 :         change = dlist_container(ReorderBufferChange, node, iter.cur);
    1865                 :             : 
    1866                 :             :         Assert(change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
    1867                 :             : 
    1868                 :             :         /* be careful about padding */
    1869                 :       13574 :         memset(&key, 0, sizeof(ReorderBufferTupleCidKey));
    1870                 :             : 
    1871                 :       13574 :         key.rlocator = change->data.tuplecid.locator;
    1872                 :             : 
    1873                 :       13574 :         ItemPointerCopy(&change->data.tuplecid.tid,
    1874                 :             :                         &key.tid);
    1875                 :             : 
    1876                 :             :         ent = (ReorderBufferTupleCidEnt *)
    1877                 :       13574 :             hash_search(txn->tuplecid_hash, &key, HASH_ENTER, &found);
    1878         [ +  + ]:       13574 :         if (!found)
    1879                 :             :         {
    1880                 :       11931 :             ent->cmin = change->data.tuplecid.cmin;
    1881                 :       11931 :             ent->cmax = change->data.tuplecid.cmax;
    1882                 :       11931 :             ent->combocid = change->data.tuplecid.combocid;
    1883                 :             :         }
    1884                 :             :         else
    1885                 :             :         {
    1886                 :             :             /*
    1887                 :             :              * Maybe we already saw this tuple before in this transaction, but
    1888                 :             :              * if so it must have the same cmin.
    1889                 :             :              */
    1890                 :             :             Assert(ent->cmin == change->data.tuplecid.cmin);
    1891                 :             : 
    1892                 :             :             /*
    1893                 :             :              * cmax may be initially invalid, but once set it can only grow,
    1894                 :             :              * and never become invalid again.
    1895                 :             :              */
    1896                 :             :             Assert((ent->cmax == InvalidCommandId) ||
    1897                 :             :                    ((change->data.tuplecid.cmax != InvalidCommandId) &&
    1898                 :             :                     (change->data.tuplecid.cmax > ent->cmax)));
    1899                 :        1643 :             ent->cmax = change->data.tuplecid.cmax;
    1900                 :             :         }
    1901                 :             :     }
    1902                 :             : }
    1903                 :             : 
    1904                 :             : /*
    1905                 :             :  * Copy a provided snapshot so we can modify it privately. This is needed so
    1906                 :             :  * that catalog modifying transactions can look into intermediate catalog
    1907                 :             :  * states.
    1908                 :             :  */
    1909                 :             : static Snapshot
    1910                 :        2603 : ReorderBufferCopySnap(ReorderBuffer *rb, Snapshot orig_snap,
    1911                 :             :                       ReorderBufferTXN *txn, CommandId cid)
    1912                 :             : {
    1913                 :             :     Snapshot    snap;
    1914                 :             :     dlist_iter  iter;
    1915                 :        2603 :     int         i = 0;
    1916                 :             :     Size        size;
    1917                 :             : 
    1918                 :        2603 :     size = sizeof(SnapshotData) +
    1919                 :        2603 :         sizeof(TransactionId) * orig_snap->xcnt +
    1920                 :        2603 :         sizeof(TransactionId) * (txn->nsubtxns + 1);
    1921                 :             : 
    1922                 :        2603 :     snap = MemoryContextAllocZero(rb->context, size);
    1923                 :        2603 :     memcpy(snap, orig_snap, sizeof(SnapshotData));
    1924                 :             : 
    1925                 :        2603 :     snap->copied = true;
    1926                 :        2603 :     snap->active_count = 1;      /* mark as active so nobody frees it */
    1927                 :        2603 :     snap->regd_count = 0;
    1928                 :        2603 :     snap->xip = (TransactionId *) (snap + 1);
    1929                 :             : 
    1930                 :        2603 :     memcpy(snap->xip, orig_snap->xip, sizeof(TransactionId) * snap->xcnt);
    1931                 :             : 
    1932                 :             :     /*
    1933                 :             :      * snap->subxip contains all txids that belong to our transaction which we
    1934                 :             :      * need to check via cmin/cmax. That's why we store the toplevel
    1935                 :             :      * transaction in there as well.
    1936                 :             :      */
    1937                 :        2603 :     snap->subxip = snap->xip + snap->xcnt;
    1938                 :        2603 :     snap->subxip[i++] = txn->xid;
    1939                 :             : 
    1940                 :             :     /*
    1941                 :             :      * txn->nsubtxns isn't decreased when subtransactions abort, so count
    1942                 :             :      * manually. Since it's an upper boundary it is safe to use it for the
    1943                 :             :      * allocation above.
    1944                 :             :      */
    1945                 :        2603 :     snap->subxcnt = 1;
    1946                 :             : 
    1947   [ +  -  +  + ]:        2912 :     dlist_foreach(iter, &txn->subtxns)
    1948                 :             :     {
    1949                 :             :         ReorderBufferTXN *sub_txn;
    1950                 :             : 
    1951                 :         309 :         sub_txn = dlist_container(ReorderBufferTXN, node, iter.cur);
    1952                 :         309 :         snap->subxip[i++] = sub_txn->xid;
    1953                 :         309 :         snap->subxcnt++;
    1954                 :             :     }
    1955                 :             : 
    1956                 :             :     /* sort so we can bsearch() later */
    1957                 :        2603 :     qsort(snap->subxip, snap->subxcnt, sizeof(TransactionId), xidComparator);
    1958                 :             : 
    1959                 :             :     /* store the specified current CommandId */
    1960                 :        2603 :     snap->curcid = cid;
    1961                 :             : 
    1962                 :        2603 :     return snap;
    1963                 :             : }
    1964                 :             : 
    1965                 :             : /*
    1966                 :             :  * Free a previously ReorderBufferCopySnap'ed snapshot
    1967                 :             :  */
    1968                 :             : static void
    1969                 :        4267 : ReorderBufferFreeSnap(ReorderBuffer *rb, Snapshot snap)
    1970                 :             : {
    1971         [ +  + ]:        4267 :     if (snap->copied)
    1972                 :        2600 :         pfree(snap);
    1973                 :             :     else
    1974                 :        1667 :         SnapBuildSnapDecRefcount(snap);
    1975                 :        4267 : }
    1976                 :             : 
    1977                 :             : /*
    1978                 :             :  * If the transaction was (partially) streamed, we need to prepare or commit
    1979                 :             :  * it in a 'streamed' way.  That is, we first stream the remaining part of the
    1980                 :             :  * transaction, and then invoke stream_prepare or stream_commit message as per
    1981                 :             :  * the case.
    1982                 :             :  */
    1983                 :             : static void
    1984                 :          64 : ReorderBufferStreamCommit(ReorderBuffer *rb, ReorderBufferTXN *txn)
    1985                 :             : {
    1986                 :             :     /* we should only call this for previously streamed transactions */
    1987                 :             :     Assert(rbtxn_is_streamed(txn));
    1988                 :             : 
    1989                 :          64 :     ReorderBufferStreamTXN(rb, txn);
    1990                 :             : 
    1991         [ +  + ]:          64 :     if (rbtxn_is_prepared(txn))
    1992                 :             :     {
    1993                 :             :         /*
    1994                 :             :          * Note, we send stream prepare even if a concurrent abort is
    1995                 :             :          * detected. See DecodePrepare for more information.
    1996                 :             :          */
    1997                 :             :         Assert(!rbtxn_sent_prepare(txn));
    1998                 :          14 :         rb->stream_prepare(rb, txn, txn->final_lsn);
    1999                 :          14 :         txn->txn_flags |= RBTXN_SENT_PREPARE;
    2000                 :             : 
    2001                 :             :         /*
    2002                 :             :          * This is a PREPARED transaction, part of a two-phase commit. The
    2003                 :             :          * full cleanup will happen as part of the COMMIT PREPAREDs, so now
    2004                 :             :          * just truncate txn by removing changes and tuplecids.
    2005                 :             :          */
    2006                 :          14 :         ReorderBufferTruncateTXN(rb, txn, true);
    2007                 :             :         /* Reset the CheckXidAlive */
    2008                 :          14 :         CheckXidAlive = InvalidTransactionId;
    2009                 :             :     }
    2010                 :             :     else
    2011                 :             :     {
    2012                 :          50 :         rb->stream_commit(rb, txn, txn->final_lsn);
    2013                 :          50 :         ReorderBufferCleanupTXN(rb, txn);
    2014                 :             :     }
    2015                 :          64 : }
    2016                 :             : 
    2017                 :             : /*
    2018                 :             :  * Set xid to detect concurrent aborts.
    2019                 :             :  *
    2020                 :             :  * While streaming an in-progress transaction or decoding a prepared
    2021                 :             :  * transaction there is a possibility that the (sub)transaction might get
    2022                 :             :  * aborted concurrently.  In such case if the (sub)transaction has catalog
    2023                 :             :  * update then we might decode the tuple using wrong catalog version.  For
    2024                 :             :  * example, suppose there is one catalog tuple with (xmin: 500, xmax: 0).  Now,
    2025                 :             :  * the transaction 501 updates the catalog tuple and after that we will have
    2026                 :             :  * two tuples (xmin: 500, xmax: 501) and (xmin: 501, xmax: 0).  Now, if 501 is
    2027                 :             :  * aborted and some other transaction say 502 updates the same catalog tuple
    2028                 :             :  * then the first tuple will be changed to (xmin: 500, xmax: 502).  So, the
    2029                 :             :  * problem is that when we try to decode the tuple inserted/updated in 501
    2030                 :             :  * after the catalog update, we will see the catalog tuple with (xmin: 500,
    2031                 :             :  * xmax: 502) as visible because it will consider that the tuple is deleted by
    2032                 :             :  * xid 502 which is not visible to our snapshot.  And when we will try to
    2033                 :             :  * decode with that catalog tuple, it can lead to a wrong result or a crash.
    2034                 :             :  * So, it is necessary to detect concurrent aborts to allow streaming of
    2035                 :             :  * in-progress transactions or decoding of prepared transactions.
    2036                 :             :  *
    2037                 :             :  * For detecting the concurrent abort we set CheckXidAlive to the current
    2038                 :             :  * (sub)transaction's xid for which this change belongs to.  And, during
    2039                 :             :  * catalog scan we can check the status of the xid and if it is aborted we will
    2040                 :             :  * report a specific error so that we can stop streaming current transaction
    2041                 :             :  * and discard the already streamed changes on such an error.  We might have
    2042                 :             :  * already streamed some of the changes for the aborted (sub)transaction, but
    2043                 :             :  * that is fine because when we decode the abort we will stream abort message
    2044                 :             :  * to truncate the changes in the subscriber. Similarly, for prepared
    2045                 :             :  * transactions, we stop decoding if concurrent abort is detected and then
    2046                 :             :  * rollback the changes when rollback prepared is encountered. See
    2047                 :             :  * DecodePrepare.
    2048                 :             :  */
    2049                 :             : static inline void
    2050                 :      177857 : SetupCheckXidLive(TransactionId xid)
    2051                 :             : {
    2052                 :             :     /*
    2053                 :             :      * If the input transaction id is already set as a CheckXidAlive then
    2054                 :             :      * nothing to do.
    2055                 :             :      */
    2056         [ +  + ]:      177857 :     if (TransactionIdEquals(CheckXidAlive, xid))
    2057                 :       86293 :         return;
    2058                 :             : 
    2059                 :             :     /*
    2060                 :             :      * setup CheckXidAlive if it's not committed yet.  We don't check if the
    2061                 :             :      * xid is aborted.  That will happen during catalog access.
    2062                 :             :      */
    2063         [ +  + ]:       91564 :     if (!TransactionIdDidCommit(xid))
    2064                 :         338 :         CheckXidAlive = xid;
    2065                 :             :     else
    2066                 :       91226 :         CheckXidAlive = InvalidTransactionId;
    2067                 :             : }
    2068                 :             : 
    2069                 :             : /*
    2070                 :             :  * Helper function for ReorderBufferProcessTXN for applying change.
    2071                 :             :  */
    2072                 :             : static inline void
    2073                 :      358290 : ReorderBufferApplyChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
    2074                 :             :                          Relation relation, ReorderBufferChange *change,
    2075                 :             :                          bool streaming)
    2076                 :             : {
    2077         [ +  + ]:      358290 :     if (streaming)
    2078                 :      175988 :         rb->stream_change(rb, txn, relation, change);
    2079                 :             :     else
    2080                 :      182302 :         rb->apply_change(rb, txn, relation, change);
    2081                 :      358283 : }
    2082                 :             : 
    2083                 :             : /*
    2084                 :             :  * Helper function for ReorderBufferProcessTXN for applying the truncate.
    2085                 :             :  */
    2086                 :             : static inline void
    2087                 :          37 : ReorderBufferApplyTruncate(ReorderBuffer *rb, ReorderBufferTXN *txn,
    2088                 :             :                            int nrelations, Relation *relations,
    2089                 :             :                            ReorderBufferChange *change, bool streaming)
    2090                 :             : {
    2091         [ -  + ]:          37 :     if (streaming)
    2092                 :           0 :         rb->stream_truncate(rb, txn, nrelations, relations, change);
    2093                 :             :     else
    2094                 :          37 :         rb->apply_truncate(rb, txn, nrelations, relations, change);
    2095                 :          37 : }
    2096                 :             : 
    2097                 :             : /*
    2098                 :             :  * Helper function for ReorderBufferProcessTXN for applying the message.
    2099                 :             :  */
    2100                 :             : static inline void
    2101                 :          11 : ReorderBufferApplyMessage(ReorderBuffer *rb, ReorderBufferTXN *txn,
    2102                 :             :                           ReorderBufferChange *change, bool streaming)
    2103                 :             : {
    2104         [ +  + ]:          11 :     if (streaming)
    2105                 :           3 :         rb->stream_message(rb, txn, change->lsn, true,
    2106                 :           3 :                            change->data.msg.prefix,
    2107                 :             :                            change->data.msg.message_size,
    2108                 :           3 :                            change->data.msg.message);
    2109                 :             :     else
    2110                 :           8 :         rb->message(rb, txn, change->lsn, true,
    2111                 :           8 :                     change->data.msg.prefix,
    2112                 :             :                     change->data.msg.message_size,
    2113                 :           8 :                     change->data.msg.message);
    2114                 :          11 : }
    2115                 :             : 
    2116                 :             : /*
    2117                 :             :  * Function to store the command id and snapshot at the end of the current
    2118                 :             :  * stream so that we can reuse the same while sending the next stream.
    2119                 :             :  */
    2120                 :             : static inline void
    2121                 :         696 : ReorderBufferSaveTXNSnapshot(ReorderBuffer *rb, ReorderBufferTXN *txn,
    2122                 :             :                              Snapshot snapshot_now, CommandId command_id)
    2123                 :             : {
    2124                 :         696 :     txn->command_id = command_id;
    2125                 :             : 
    2126                 :             :     /* Avoid copying if it's already copied. */
    2127         [ +  - ]:         696 :     if (snapshot_now->copied)
    2128                 :         696 :         txn->snapshot_now = snapshot_now;
    2129                 :             :     else
    2130                 :           0 :         txn->snapshot_now = ReorderBufferCopySnap(rb, snapshot_now,
    2131                 :             :                                                   txn, command_id);
    2132                 :         696 : }
    2133                 :             : 
    2134                 :             : /*
    2135                 :             :  * Mark the given transaction as streamed if it's a top-level transaction
    2136                 :             :  * or has changes.
    2137                 :             :  */
    2138                 :             : static void
    2139                 :         993 : ReorderBufferMaybeMarkTXNStreamed(ReorderBuffer *rb, ReorderBufferTXN *txn)
    2140                 :             : {
    2141                 :             :     /*
    2142                 :             :      * The top-level transaction, is marked as streamed always, even if it
    2143                 :             :      * does not contain any changes (that is, when all the changes are in
    2144                 :             :      * subtransactions).
    2145                 :             :      *
    2146                 :             :      * For subtransactions, we only mark them as streamed when there are
    2147                 :             :      * changes in them.
    2148                 :             :      *
    2149                 :             :      * We do it this way because of aborts - we don't want to send aborts for
    2150                 :             :      * XIDs the downstream is not aware of. And of course, it always knows
    2151                 :             :      * about the top-level xact (we send the XID in all messages), but we
    2152                 :             :      * never stream XIDs of empty subxacts.
    2153                 :             :      */
    2154   [ +  +  +  + ]:         993 :     if (rbtxn_is_toptxn(txn) || (txn->nentries_mem != 0))
    2155                 :         831 :         txn->txn_flags |= RBTXN_IS_STREAMED;
    2156                 :         993 : }
    2157                 :             : 
    2158                 :             : /*
    2159                 :             :  * Helper function for ReorderBufferProcessTXN to handle the concurrent
    2160                 :             :  * abort of the streaming transaction.  This resets the TXN such that it
    2161                 :             :  * can be used to stream the remaining data of transaction being processed.
    2162                 :             :  * This can happen when the subtransaction is aborted and we still want to
    2163                 :             :  * continue processing the main or other subtransactions data.
    2164                 :             :  */
    2165                 :             : static void
    2166                 :           8 : ReorderBufferResetTXN(ReorderBuffer *rb, ReorderBufferTXN *txn,
    2167                 :             :                       Snapshot snapshot_now,
    2168                 :             :                       CommandId command_id,
    2169                 :             :                       XLogRecPtr last_lsn)
    2170                 :             : {
    2171                 :             :     /* Discard the changes that we just streamed */
    2172                 :           8 :     ReorderBufferTruncateTXN(rb, txn, rbtxn_is_prepared(txn));
    2173                 :             : 
    2174                 :             :     /* Free all resources allocated for toast reconstruction */
    2175                 :           8 :     ReorderBufferToastReset(rb, txn);
    2176                 :             : 
    2177                 :             :     /*
    2178                 :             :      * For the streaming case, stop the stream and remember the command ID and
    2179                 :             :      * snapshot for the streaming run.
    2180                 :             :      */
    2181         [ +  - ]:           8 :     if (rbtxn_is_streamed(txn))
    2182                 :             :     {
    2183                 :           8 :         rb->stream_stop(rb, txn, last_lsn);
    2184                 :           8 :         ReorderBufferSaveTXNSnapshot(rb, txn, snapshot_now, command_id);
    2185                 :             :     }
    2186                 :             : 
    2187                 :             :     /* All changes must be deallocated */
    2188                 :             :     Assert(txn->size == 0);
    2189                 :           8 : }
    2190                 :             : 
    2191                 :             : /*
    2192                 :             :  * Helper function for ReorderBufferReplay and ReorderBufferStreamTXN.
    2193                 :             :  *
    2194                 :             :  * Send data of a transaction (and its subtransactions) to the
    2195                 :             :  * output plugin. We iterate over the top and subtransactions (using a k-way
    2196                 :             :  * merge) and replay the changes in lsn order.
    2197                 :             :  *
    2198                 :             :  * If streaming is true then data will be sent using stream API.
    2199                 :             :  *
    2200                 :             :  * Note: "volatile" markers on some parameters are to avoid trouble with
    2201                 :             :  * PG_TRY inside the function.
    2202                 :             :  */
    2203                 :             : static void
    2204                 :        2580 : ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn,
    2205                 :             :                         XLogRecPtr commit_lsn,
    2206                 :             :                         volatile Snapshot snapshot_now,
    2207                 :             :                         volatile CommandId command_id,
    2208                 :             :                         bool streaming)
    2209                 :             : {
    2210                 :             :     bool        using_subtxn;
    2211                 :        2580 :     MemoryContext ccxt = CurrentMemoryContext;
    2212                 :        2580 :     ResourceOwner cowner = CurrentResourceOwner;
    2213                 :        2580 :     ReorderBufferIterTXNState *volatile iterstate = NULL;
    2214                 :        2580 :     volatile XLogRecPtr prev_lsn = InvalidXLogRecPtr;
    2215                 :        2580 :     ReorderBufferChange *volatile specinsert = NULL;
    2216                 :        2580 :     volatile bool stream_started = false;
    2217                 :        2580 :     ReorderBufferTXN *volatile curtxn = NULL;
    2218                 :             : 
    2219                 :             :     /* build data to be able to lookup the CommandIds of catalog tuples */
    2220                 :        2580 :     ReorderBufferBuildTupleCidHash(rb, txn);
    2221                 :             : 
    2222                 :             :     /* setup the initial snapshot */
    2223                 :        2580 :     SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash);
    2224                 :             : 
    2225                 :             :     /*
    2226                 :             :      * Decoding needs access to syscaches et al., which in turn use
    2227                 :             :      * heavyweight locks and such. Thus we need to have enough state around to
    2228                 :             :      * keep track of those.  The easiest way is to simply use a transaction
    2229                 :             :      * internally.  That also allows us to easily enforce that nothing writes
    2230                 :             :      * to the database by checking for xid assignments.
    2231                 :             :      *
    2232                 :             :      * When we're called via the SQL SRF there's already a transaction
    2233                 :             :      * started, so start an explicit subtransaction there.
    2234                 :             :      */
    2235                 :        2580 :     using_subtxn = IsTransactionOrTransactionBlock();
    2236                 :             : 
    2237         [ +  + ]:        2580 :     PG_TRY();
    2238                 :             :     {
    2239                 :             :         ReorderBufferChange *change;
    2240                 :        2580 :         int         changes_count = 0;  /* used to accumulate the number of
    2241                 :             :                                          * changes */
    2242                 :             : 
    2243         [ +  + ]:        2580 :         if (using_subtxn)
    2244         [ +  + ]:         514 :             BeginInternalSubTransaction(streaming ? "stream" : "replay");
    2245                 :             :         else
    2246                 :        2066 :             StartTransactionCommand();
    2247                 :             : 
    2248                 :             :         /*
    2249                 :             :          * We only need to send begin/begin-prepare for non-streamed
    2250                 :             :          * transactions.
    2251                 :             :          */
    2252         [ +  + ]:        2580 :         if (!streaming)
    2253                 :             :         {
    2254         [ +  + ]:        1884 :             if (rbtxn_is_prepared(txn))
    2255                 :          31 :                 rb->begin_prepare(rb, txn);
    2256                 :             :             else
    2257                 :        1853 :                 rb->begin(rb, txn);
    2258                 :             :         }
    2259                 :             : 
    2260                 :        2580 :         ReorderBufferIterTXNInit(rb, txn, &iterstate);
    2261         [ +  + ]:      388604 :         while ((change = ReorderBufferIterTXNNext(rb, iterstate)) != NULL)
    2262                 :             :         {
    2263                 :      383459 :             Relation    relation = NULL;
    2264                 :             :             Oid         reloid;
    2265                 :             : 
    2266         [ -  + ]:      383459 :             CHECK_FOR_INTERRUPTS();
    2267                 :             : 
    2268                 :             :             /*
    2269                 :             :              * We can't call start stream callback before processing first
    2270                 :             :              * change.
    2271                 :             :              */
    2272         [ +  + ]:      383459 :             if (!XLogRecPtrIsValid(prev_lsn))
    2273                 :             :             {
    2274         [ +  + ]:        2538 :                 if (streaming)
    2275                 :             :                 {
    2276                 :         659 :                     txn->origin_id = change->origin_id;
    2277                 :         659 :                     rb->stream_start(rb, txn, change->lsn);
    2278                 :         659 :                     stream_started = true;
    2279                 :             :                 }
    2280                 :             :             }
    2281                 :             : 
    2282                 :             :             /*
    2283                 :             :              * Enforce correct ordering of changes, merged from multiple
    2284                 :             :              * subtransactions. The changes may have the same LSN due to
    2285                 :             :              * MULTI_INSERT xlog records.
    2286                 :             :              */
    2287                 :             :             Assert(!XLogRecPtrIsValid(prev_lsn) || prev_lsn <= change->lsn);
    2288                 :             : 
    2289                 :      383459 :             prev_lsn = change->lsn;
    2290                 :             : 
    2291                 :             :             /*
    2292                 :             :              * Set the current xid to detect concurrent aborts. This is
    2293                 :             :              * required for the cases when we decode the changes before the
    2294                 :             :              * COMMIT record is processed.
    2295                 :             :              */
    2296   [ +  +  +  + ]:      383459 :             if (streaming || rbtxn_is_prepared(change->txn))
    2297                 :             :             {
    2298                 :      177857 :                 curtxn = change->txn;
    2299                 :      177857 :                 SetupCheckXidLive(curtxn->xid);
    2300                 :             :             }
    2301                 :             : 
    2302   [ +  +  +  -  :      383459 :             switch (change->action)
          +  +  +  +  +  
                   -  - ]
    2303                 :             :             {
    2304                 :        1783 :                 case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
    2305                 :             : 
    2306                 :             :                     /*
    2307                 :             :                      * Confirmation for speculative insertion arrived. Simply
    2308                 :             :                      * use as a normal record. It'll be cleaned up at the end
    2309                 :             :                      * of INSERT processing.
    2310                 :             :                      */
    2311         [ -  + ]:        1783 :                     if (specinsert == NULL)
    2312         [ #  # ]:           0 :                         elog(ERROR, "invalid ordering of speculative insertion changes");
    2313                 :             :                     Assert(specinsert->data.tp.oldtuple == NULL);
    2314                 :        1783 :                     change = specinsert;
    2315                 :        1783 :                     change->action = REORDER_BUFFER_CHANGE_INSERT;
    2316                 :             : 
    2317                 :             :                     /* intentionally fall through */
    2318                 :             :                     pg_fallthrough;
    2319                 :      365098 :                 case REORDER_BUFFER_CHANGE_INSERT:
    2320                 :             :                 case REORDER_BUFFER_CHANGE_UPDATE:
    2321                 :             :                 case REORDER_BUFFER_CHANGE_DELETE:
    2322                 :             :                     Assert(snapshot_now);
    2323                 :             : 
    2324                 :      365098 :                     reloid = RelidByRelfilenumber(change->data.tp.rlocator.spcOid,
    2325                 :             :                                                   change->data.tp.rlocator.relNumber);
    2326                 :             : 
    2327                 :             :                     /*
    2328                 :             :                      * Mapped catalog tuple without data, emitted while
    2329                 :             :                      * catalog table was in the process of being rewritten. We
    2330                 :             :                      * can fail to look up the relfilenumber, because the
    2331                 :             :                      * relmapper has no "historic" view, in contrast to the
    2332                 :             :                      * normal catalog during decoding. Thus repeated rewrites
    2333                 :             :                      * can cause a lookup failure. That's OK because we do not
    2334                 :             :                      * decode catalog changes anyway. Normally such tuples
    2335                 :             :                      * would be skipped over below, but we can't identify
    2336                 :             :                      * whether the table should be logically logged without
    2337                 :             :                      * mapping the relfilenumber to the oid.
    2338                 :             :                      */
    2339         [ +  + ]:      365090 :                     if (reloid == InvalidOid &&
    2340         [ +  - ]:          83 :                         change->data.tp.newtuple == NULL &&
    2341         [ +  - ]:          83 :                         change->data.tp.oldtuple == NULL)
    2342                 :          83 :                         goto change_done;
    2343         [ -  + ]:      365007 :                     else if (reloid == InvalidOid)
    2344         [ #  # ]:           0 :                         elog(ERROR, "could not map filenumber \"%s\" to relation OID",
    2345                 :             :                              relpathperm(change->data.tp.rlocator,
    2346                 :             :                                          MAIN_FORKNUM).str);
    2347                 :             : 
    2348                 :      365007 :                     relation = RelationIdGetRelation(reloid);
    2349                 :             : 
    2350         [ -  + ]:      365007 :                     if (!RelationIsValid(relation))
    2351         [ #  # ]:           0 :                         elog(ERROR, "could not open relation with OID %u (for filenumber \"%s\")",
    2352                 :             :                              reloid,
    2353                 :             :                              relpathperm(change->data.tp.rlocator,
    2354                 :             :                                          MAIN_FORKNUM).str);
    2355                 :             : 
    2356   [ +  +  +  -  :      365007 :                     if (!RelationIsLogicallyLogged(relation))
          +  -  -  +  -  
          -  -  -  +  -  
                   +  + ]
    2357                 :        4594 :                         goto change_done;
    2358                 :             : 
    2359                 :             :                     /*
    2360                 :             :                      * Ignore temporary heaps created during DDL unless the
    2361                 :             :                      * plugin has asked for them.
    2362                 :             :                      */
    2363   [ +  +  +  + ]:      360413 :                     if (relation->rd_rel->relrewrite && !rb->output_rewrites)
    2364                 :          26 :                         goto change_done;
    2365                 :             : 
    2366                 :             :                     /*
    2367                 :             :                      * For now ignore sequence changes entirely. Most of the
    2368                 :             :                      * time they don't log changes using records we
    2369                 :             :                      * understand, so it doesn't make sense to handle the few
    2370                 :             :                      * cases we do.
    2371                 :             :                      */
    2372         [ -  + ]:      360387 :                     if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
    2373                 :           0 :                         goto change_done;
    2374                 :             : 
    2375                 :             :                     /* user-triggered change */
    2376         [ +  + ]:      360387 :                     if (!IsToastRelation(relation))
    2377                 :             :                     {
    2378                 :      358290 :                         ReorderBufferToastReplace(rb, txn, relation, change);
    2379                 :      358290 :                         ReorderBufferApplyChange(rb, txn, relation, change,
    2380                 :             :                                                  streaming);
    2381                 :             : 
    2382                 :             :                         /*
    2383                 :             :                          * Only clear reassembled toast chunks if we're sure
    2384                 :             :                          * they're not required anymore. The creator of the
    2385                 :             :                          * tuple tells us.
    2386                 :             :                          */
    2387         [ +  + ]:      358283 :                         if (change->data.tp.clear_toast_afterwards)
    2388                 :      358062 :                             ReorderBufferToastReset(rb, txn);
    2389                 :             :                     }
    2390                 :             :                     /* we're not interested in toast deletions */
    2391         [ +  + ]:        2097 :                     else if (change->action == REORDER_BUFFER_CHANGE_INSERT)
    2392                 :             :                     {
    2393                 :             :                         /*
    2394                 :             :                          * Need to reassemble the full toasted Datum in
    2395                 :             :                          * memory, to ensure the chunks don't get reused till
    2396                 :             :                          * we're done remove it from the list of this
    2397                 :             :                          * transaction's changes. Otherwise it will get
    2398                 :             :                          * freed/reused while restoring spooled data from
    2399                 :             :                          * disk.
    2400                 :             :                          */
    2401                 :             :                         Assert(change->data.tp.newtuple != NULL);
    2402                 :             : 
    2403                 :        1847 :                         dlist_delete(&change->node);
    2404                 :        1847 :                         ReorderBufferToastAppendChunk(rb, txn, relation,
    2405                 :             :                                                       change);
    2406                 :             :                     }
    2407                 :             : 
    2408                 :         250 :             change_done:
    2409                 :             : 
    2410                 :             :                     /*
    2411                 :             :                      * If speculative insertion was confirmed, the record
    2412                 :             :                      * isn't needed anymore.
    2413                 :             :                      */
    2414         [ +  + ]:      365083 :                     if (specinsert != NULL)
    2415                 :             :                     {
    2416                 :        1782 :                         ReorderBufferFreeChange(rb, specinsert, true);
    2417                 :        1782 :                         specinsert = NULL;
    2418                 :             :                     }
    2419                 :             : 
    2420         [ +  + ]:      365083 :                     if (RelationIsValid(relation))
    2421                 :             :                     {
    2422                 :      365000 :                         RelationClose(relation);
    2423                 :      365000 :                         relation = NULL;
    2424                 :             :                     }
    2425                 :      365083 :                     break;
    2426                 :             : 
    2427                 :        1783 :                 case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
    2428                 :             : 
    2429                 :             :                     /*
    2430                 :             :                      * Speculative insertions are dealt with by delaying the
    2431                 :             :                      * processing of the insert until the confirmation record
    2432                 :             :                      * arrives. For that we simply unlink the record from the
    2433                 :             :                      * chain, so it does not get freed/reused while restoring
    2434                 :             :                      * spooled data from disk.
    2435                 :             :                      *
    2436                 :             :                      * This is safe in the face of concurrent catalog changes
    2437                 :             :                      * because the relevant relation can't be changed between
    2438                 :             :                      * speculative insertion and confirmation due to
    2439                 :             :                      * CheckTableNotInUse() and locking.
    2440                 :             :                      */
    2441                 :             : 
    2442                 :             :                     /* Previous speculative insertion must be aborted */
    2443                 :             :                     Assert(specinsert == NULL);
    2444                 :             : 
    2445                 :             :                     /* and memorize the pending insertion */
    2446                 :        1783 :                     dlist_delete(&change->node);
    2447                 :        1783 :                     specinsert = change;
    2448                 :        1783 :                     break;
    2449                 :             : 
    2450                 :           0 :                 case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT:
    2451                 :             : 
    2452                 :             :                     /*
    2453                 :             :                      * Abort for speculative insertion arrived. So cleanup the
    2454                 :             :                      * specinsert tuple and toast hash.
    2455                 :             :                      *
    2456                 :             :                      * Note that we get the spec abort change for each toast
    2457                 :             :                      * entry but we need to perform the cleanup only the first
    2458                 :             :                      * time we get it for the main table.
    2459                 :             :                      */
    2460         [ #  # ]:           0 :                     if (specinsert != NULL)
    2461                 :             :                     {
    2462                 :             :                         /*
    2463                 :             :                          * We must clean the toast hash before processing a
    2464                 :             :                          * completely new tuple to avoid confusion about the
    2465                 :             :                          * previous tuple's toast chunks.
    2466                 :             :                          */
    2467                 :             :                         Assert(change->data.tp.clear_toast_afterwards);
    2468                 :           0 :                         ReorderBufferToastReset(rb, txn);
    2469                 :             : 
    2470                 :             :                         /* We don't need this record anymore. */
    2471                 :           0 :                         ReorderBufferFreeChange(rb, specinsert, true);
    2472                 :           0 :                         specinsert = NULL;
    2473                 :             :                     }
    2474                 :           0 :                     break;
    2475                 :             : 
    2476                 :          37 :                 case REORDER_BUFFER_CHANGE_TRUNCATE:
    2477                 :             :                     {
    2478                 :             :                         int         i;
    2479                 :          37 :                         int         nrelids = change->data.truncate.nrelids;
    2480                 :          37 :                         int         nrelations = 0;
    2481                 :             :                         Relation   *relations;
    2482                 :             : 
    2483                 :          37 :                         relations = palloc0_array(Relation, nrelids);
    2484         [ +  + ]:          94 :                         for (i = 0; i < nrelids; i++)
    2485                 :             :                         {
    2486                 :          57 :                             Oid         relid = change->data.truncate.relids[i];
    2487                 :             :                             Relation    rel;
    2488                 :             : 
    2489                 :          57 :                             rel = RelationIdGetRelation(relid);
    2490                 :             : 
    2491         [ -  + ]:          57 :                             if (!RelationIsValid(rel))
    2492         [ #  # ]:           0 :                                 elog(ERROR, "could not open relation with OID %u", relid);
    2493                 :             : 
    2494   [ -  +  -  -  :          57 :                             if (!RelationIsLogicallyLogged(rel))
          +  -  -  +  -  
          -  -  -  +  -  
                   -  + ]
    2495                 :           0 :                                 continue;
    2496                 :             : 
    2497                 :          57 :                             relations[nrelations++] = rel;
    2498                 :             :                         }
    2499                 :             : 
    2500                 :             :                         /* Apply the truncate. */
    2501                 :          37 :                         ReorderBufferApplyTruncate(rb, txn, nrelations,
    2502                 :             :                                                    relations, change,
    2503                 :             :                                                    streaming);
    2504                 :             : 
    2505         [ +  + ]:          94 :                         for (i = 0; i < nrelations; i++)
    2506                 :          57 :                             RelationClose(relations[i]);
    2507                 :             : 
    2508                 :          37 :                         break;
    2509                 :             :                     }
    2510                 :             : 
    2511                 :          11 :                 case REORDER_BUFFER_CHANGE_MESSAGE:
    2512                 :          11 :                     ReorderBufferApplyMessage(rb, txn, change, streaming);
    2513                 :          11 :                     break;
    2514                 :             : 
    2515                 :        2880 :                 case REORDER_BUFFER_CHANGE_INVALIDATION:
    2516                 :             :                     /* Execute the invalidation messages locally */
    2517                 :        2880 :                     ReorderBufferExecuteInvalidations(change->data.inval.ninvalidations,
    2518                 :             :                                                       change->data.inval.invalidations);
    2519                 :        2880 :                     break;
    2520                 :             : 
    2521                 :         981 :                 case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
    2522                 :             :                     /* get rid of the old */
    2523                 :         981 :                     TeardownHistoricSnapshot(false);
    2524                 :             : 
    2525         [ +  + ]:         981 :                     if (snapshot_now->copied)
    2526                 :             :                     {
    2527                 :         956 :                         ReorderBufferFreeSnap(rb, snapshot_now);
    2528                 :         956 :                         snapshot_now =
    2529                 :         956 :                             ReorderBufferCopySnap(rb, change->data.snapshot,
    2530                 :             :                                                   txn, command_id);
    2531                 :             :                     }
    2532                 :             : 
    2533                 :             :                     /*
    2534                 :             :                      * Restored from disk, need to be careful not to double
    2535                 :             :                      * free. We could introduce refcounting for that, but for
    2536                 :             :                      * now this seems infrequent enough not to care.
    2537                 :             :                      */
    2538         [ -  + ]:          25 :                     else if (change->data.snapshot->copied)
    2539                 :             :                     {
    2540                 :           0 :                         snapshot_now =
    2541                 :           0 :                             ReorderBufferCopySnap(rb, change->data.snapshot,
    2542                 :             :                                                   txn, command_id);
    2543                 :             :                     }
    2544                 :             :                     else
    2545                 :             :                     {
    2546                 :          25 :                         snapshot_now = change->data.snapshot;
    2547                 :             :                     }
    2548                 :             : 
    2549                 :             :                     /* and continue with the new one */
    2550                 :         981 :                     SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash);
    2551                 :         981 :                     break;
    2552                 :             : 
    2553                 :       12669 :                 case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
    2554                 :             :                     Assert(change->data.command_id != InvalidCommandId);
    2555                 :             : 
    2556         [ +  + ]:       12669 :                     if (command_id < change->data.command_id)
    2557                 :             :                     {
    2558                 :        2530 :                         command_id = change->data.command_id;
    2559                 :             : 
    2560         [ +  + ]:        2530 :                         if (!snapshot_now->copied)
    2561                 :             :                         {
    2562                 :             :                             /* we don't use the global one anymore */
    2563                 :         951 :                             snapshot_now = ReorderBufferCopySnap(rb, snapshot_now,
    2564                 :             :                                                                  txn, command_id);
    2565                 :             :                         }
    2566                 :             : 
    2567                 :        2530 :                         snapshot_now->curcid = command_id;
    2568                 :             : 
    2569                 :        2530 :                         TeardownHistoricSnapshot(false);
    2570                 :        2530 :                         SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash);
    2571                 :             :                     }
    2572                 :             : 
    2573                 :       12669 :                     break;
    2574                 :             : 
    2575                 :           0 :                 case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
    2576         [ #  # ]:           0 :                     elog(ERROR, "tuplecid value in changequeue");
    2577                 :             :                     break;
    2578                 :             :             }
    2579                 :             : 
    2580                 :             :             /*
    2581                 :             :              * It is possible that the data is not sent to downstream for a
    2582                 :             :              * long time either because the output plugin filtered it or there
    2583                 :             :              * is a DDL that generates a lot of data that is not processed by
    2584                 :             :              * the plugin. So, in such cases, the downstream can timeout. To
    2585                 :             :              * avoid that we try to send a keepalive message if required.
    2586                 :             :              * Trying to send a keepalive message after every change has some
    2587                 :             :              * overhead, but testing showed there is no noticeable overhead if
    2588                 :             :              * we do it after every ~100 changes.
    2589                 :             :              */
    2590                 :             : #define CHANGES_THRESHOLD 100
    2591                 :             : 
    2592         [ +  + ]:      383444 :             if (++changes_count >= CHANGES_THRESHOLD)
    2593                 :             :             {
    2594                 :        3344 :                 rb->update_progress_txn(rb, txn, prev_lsn);
    2595                 :        3344 :                 changes_count = 0;
    2596                 :             :             }
    2597                 :             :         }
    2598                 :             : 
    2599                 :             :         /* speculative insertion record must be freed by now */
    2600                 :             :         Assert(!specinsert);
    2601                 :             : 
    2602                 :             :         /* clean up the iterator */
    2603                 :        2565 :         ReorderBufferIterTXNFinish(rb, iterstate);
    2604                 :        2565 :         iterstate = NULL;
    2605                 :             : 
    2606                 :             :         /*
    2607                 :             :          * Update total transaction count and total bytes processed by the
    2608                 :             :          * transaction and its subtransactions. Ensure to not count the
    2609                 :             :          * streamed transaction multiple times.
    2610                 :             :          *
    2611                 :             :          * Note that the statistics computation has to be done after
    2612                 :             :          * ReorderBufferIterTXNFinish as it releases the serialized change
    2613                 :             :          * which we have already accounted in ReorderBufferIterTXNNext.
    2614                 :             :          */
    2615         [ +  + ]:        2565 :         if (!rbtxn_is_streamed(txn))
    2616                 :        1943 :             rb->totalTxns++;
    2617                 :             : 
    2618                 :        2565 :         rb->totalBytes += txn->total_size;
    2619                 :             : 
    2620                 :             :         /*
    2621                 :             :          * Done with current changes, send the last message for this set of
    2622                 :             :          * changes depending upon streaming mode.
    2623                 :             :          */
    2624         [ +  + ]:        2565 :         if (streaming)
    2625                 :             :         {
    2626         [ +  + ]:         688 :             if (stream_started)
    2627                 :             :             {
    2628                 :         651 :                 rb->stream_stop(rb, txn, prev_lsn);
    2629                 :         651 :                 stream_started = false;
    2630                 :             :             }
    2631                 :             :         }
    2632                 :             :         else
    2633                 :             :         {
    2634                 :             :             /*
    2635                 :             :              * Call either PREPARE (for two-phase transactions) or COMMIT (for
    2636                 :             :              * regular ones).
    2637                 :             :              */
    2638         [ +  + ]:        1877 :             if (rbtxn_is_prepared(txn))
    2639                 :             :             {
    2640                 :             :                 Assert(!rbtxn_sent_prepare(txn));
    2641                 :          31 :                 rb->prepare(rb, txn, commit_lsn);
    2642                 :          31 :                 txn->txn_flags |= RBTXN_SENT_PREPARE;
    2643                 :             :             }
    2644                 :             :             else
    2645                 :        1846 :                 rb->commit(rb, txn, commit_lsn);
    2646                 :             :         }
    2647                 :             : 
    2648                 :             :         /* this is just a sanity check against bad output plugin behaviour */
    2649         [ -  + ]:        2556 :         if (GetCurrentTransactionIdIfAny() != InvalidTransactionId)
    2650         [ #  # ]:           0 :             elog(ERROR, "output plugin used XID %u",
    2651                 :             :                  GetCurrentTransactionId());
    2652                 :             : 
    2653                 :             :         /*
    2654                 :             :          * Remember the command ID and snapshot for the next set of changes in
    2655                 :             :          * streaming mode.
    2656                 :             :          */
    2657         [ +  + ]:        2556 :         if (streaming)
    2658                 :         688 :             ReorderBufferSaveTXNSnapshot(rb, txn, snapshot_now, command_id);
    2659         [ +  + ]:        1868 :         else if (snapshot_now->copied)
    2660                 :         951 :             ReorderBufferFreeSnap(rb, snapshot_now);
    2661                 :             : 
    2662                 :             :         /* cleanup */
    2663                 :        2556 :         TeardownHistoricSnapshot(false);
    2664                 :             : 
    2665                 :             :         /*
    2666                 :             :          * Aborting the current (sub-)transaction as a whole has the right
    2667                 :             :          * semantics. We want all locks acquired in here to be released, not
    2668                 :             :          * reassigned to the parent and we do not want any database access
    2669                 :             :          * have persistent effects.
    2670                 :             :          */
    2671                 :        2556 :         AbortCurrentTransaction();
    2672                 :             : 
    2673                 :             :         /* make sure there's no cache pollution */
    2674         [ -  + ]:        2556 :         if (rbtxn_distr_inval_overflowed(txn))
    2675                 :             :         {
    2676                 :             :             Assert(txn->ninvalidations_distributed == 0);
    2677                 :           0 :             InvalidateSystemCaches();
    2678                 :             :         }
    2679                 :             :         else
    2680                 :             :         {
    2681                 :        2556 :             ReorderBufferExecuteInvalidations(txn->ninvalidations, txn->invalidations);
    2682                 :        2556 :             ReorderBufferExecuteInvalidations(txn->ninvalidations_distributed,
    2683                 :             :                                               txn->invalidations_distributed);
    2684                 :             :         }
    2685                 :             : 
    2686         [ +  + ]:        2556 :         if (using_subtxn)
    2687                 :             :         {
    2688                 :         509 :             RollbackAndReleaseCurrentSubTransaction();
    2689                 :         509 :             MemoryContextSwitchTo(ccxt);
    2690                 :         509 :             CurrentResourceOwner = cowner;
    2691                 :             :         }
    2692                 :             : 
    2693                 :             :         /*
    2694                 :             :          * We are here due to one of the four reasons: 1. Decoding an
    2695                 :             :          * in-progress txn. 2. Decoding a prepared txn. 3. Decoding of a
    2696                 :             :          * prepared txn that was (partially) streamed. 4. Decoding a committed
    2697                 :             :          * txn.
    2698                 :             :          *
    2699                 :             :          * For 1, we allow truncation of txn data by removing the changes
    2700                 :             :          * already streamed but still keeping other things like invalidations,
    2701                 :             :          * snapshot, and tuplecids. For 2 and 3, we indicate
    2702                 :             :          * ReorderBufferTruncateTXN to do more elaborate truncation of txn
    2703                 :             :          * data as the entire transaction has been decoded except for commit.
    2704                 :             :          * For 4, as the entire txn has been decoded, we can fully clean up
    2705                 :             :          * the TXN reorder buffer.
    2706                 :             :          */
    2707   [ +  +  +  + ]:        2556 :         if (streaming || rbtxn_is_prepared(txn))
    2708                 :             :         {
    2709         [ +  + ]:         719 :             if (streaming)
    2710                 :         688 :                 ReorderBufferMaybeMarkTXNStreamed(rb, txn);
    2711                 :             : 
    2712                 :         719 :             ReorderBufferTruncateTXN(rb, txn, rbtxn_is_prepared(txn));
    2713                 :             :             /* Reset the CheckXidAlive */
    2714                 :         719 :             CheckXidAlive = InvalidTransactionId;
    2715                 :             :         }
    2716                 :             :         else
    2717                 :        1837 :             ReorderBufferCleanupTXN(rb, txn);
    2718                 :             :     }
    2719                 :          11 :     PG_CATCH();
    2720                 :             :     {
    2721                 :          11 :         MemoryContext ecxt = MemoryContextSwitchTo(ccxt);
    2722                 :          11 :         ErrorData  *errdata = CopyErrorData();
    2723                 :             : 
    2724                 :             :         /* TODO: Encapsulate cleanup from the PG_TRY and PG_CATCH blocks */
    2725         [ +  - ]:          11 :         if (iterstate)
    2726                 :          11 :             ReorderBufferIterTXNFinish(rb, iterstate);
    2727                 :             : 
    2728                 :          11 :         TeardownHistoricSnapshot(true);
    2729                 :             : 
    2730                 :             :         /*
    2731                 :             :          * Force cache invalidation to happen outside of a valid transaction
    2732                 :             :          * to prevent catalog access as we just caught an error.
    2733                 :             :          */
    2734                 :          11 :         AbortCurrentTransaction();
    2735                 :             : 
    2736                 :             :         /* make sure there's no cache pollution */
    2737         [ -  + ]:          11 :         if (rbtxn_distr_inval_overflowed(txn))
    2738                 :             :         {
    2739                 :             :             Assert(txn->ninvalidations_distributed == 0);
    2740                 :           0 :             InvalidateSystemCaches();
    2741                 :             :         }
    2742                 :             :         else
    2743                 :             :         {
    2744                 :          11 :             ReorderBufferExecuteInvalidations(txn->ninvalidations, txn->invalidations);
    2745                 :          11 :             ReorderBufferExecuteInvalidations(txn->ninvalidations_distributed,
    2746                 :             :                                               txn->invalidations_distributed);
    2747                 :             :         }
    2748                 :             : 
    2749         [ +  + ]:          11 :         if (using_subtxn)
    2750                 :             :         {
    2751                 :           5 :             RollbackAndReleaseCurrentSubTransaction();
    2752                 :           5 :             MemoryContextSwitchTo(ccxt);
    2753                 :           5 :             CurrentResourceOwner = cowner;
    2754                 :             :         }
    2755                 :             : 
    2756                 :             :         /* Free the specinsert change before freeing the ReorderBufferTXN */
    2757         [ +  + ]:          11 :         if (specinsert != NULL)
    2758                 :             :         {
    2759                 :           1 :             ReorderBufferFreeChange(rb, specinsert, true);
    2760                 :           1 :             specinsert = NULL;
    2761                 :             :         }
    2762                 :             : 
    2763                 :             :         /*
    2764                 :             :          * The error code ERRCODE_TRANSACTION_ROLLBACK indicates a concurrent
    2765                 :             :          * abort of the (sub)transaction we are streaming or preparing. We
    2766                 :             :          * need to do the cleanup and return gracefully on this error, see
    2767                 :             :          * SetupCheckXidLive.
    2768                 :             :          *
    2769                 :             :          * This error code can be thrown by one of the callbacks we call
    2770                 :             :          * during decoding so we need to ensure that we return gracefully only
    2771                 :             :          * when we are sending the data in streaming mode and the streaming is
    2772                 :             :          * not finished yet or when we are sending the data out on a PREPARE
    2773                 :             :          * during a two-phase commit.
    2774                 :             :          */
    2775         [ +  + ]:          11 :         if (errdata->sqlerrcode == ERRCODE_TRANSACTION_ROLLBACK &&
    2776   [ -  +  -  - ]:           8 :             (stream_started || rbtxn_is_prepared(txn)))
    2777                 :             :         {
    2778                 :             :             /* curtxn must be set for streaming or prepared transactions */
    2779                 :             :             Assert(curtxn);
    2780                 :             : 
    2781                 :             :             /* Cleanup the temporary error state. */
    2782                 :           8 :             FlushErrorState();
    2783                 :           8 :             FreeErrorData(errdata);
    2784                 :           8 :             errdata = NULL;
    2785                 :             : 
    2786                 :             :             /* Remember the transaction is aborted. */
    2787                 :             :             Assert(!rbtxn_is_committed(curtxn));
    2788                 :           8 :             curtxn->txn_flags |= RBTXN_IS_ABORTED;
    2789                 :             : 
    2790                 :             :             /* Mark the transaction is streamed if appropriate */
    2791         [ +  - ]:           8 :             if (stream_started)
    2792                 :           8 :                 ReorderBufferMaybeMarkTXNStreamed(rb, txn);
    2793                 :             : 
    2794                 :             :             /* Reset the TXN so that it is allowed to stream remaining data. */
    2795                 :           8 :             ReorderBufferResetTXN(rb, txn, snapshot_now,
    2796                 :             :                                   command_id, prev_lsn);
    2797                 :             :         }
    2798                 :             :         else
    2799                 :             :         {
    2800                 :           3 :             ReorderBufferCleanupTXN(rb, txn);
    2801                 :           3 :             MemoryContextSwitchTo(ecxt);
    2802                 :           3 :             PG_RE_THROW();
    2803                 :             :         }
    2804                 :             :     }
    2805         [ -  + ]:        2564 :     PG_END_TRY();
    2806                 :        2564 : }
    2807                 :             : 
    2808                 :             : /*
    2809                 :             :  * Perform the replay of a transaction and its non-aborted subtransactions.
    2810                 :             :  *
    2811                 :             :  * Subtransactions previously have to be processed by
    2812                 :             :  * ReorderBufferCommitChild(), even if previously assigned to the toplevel
    2813                 :             :  * transaction with ReorderBufferAssignChild.
    2814                 :             :  *
    2815                 :             :  * This interface is called once a prepare or toplevel commit is read for both
    2816                 :             :  * streamed as well as non-streamed transactions.
    2817                 :             :  */
    2818                 :             : static void
    2819                 :        1951 : ReorderBufferReplay(ReorderBufferTXN *txn,
    2820                 :             :                     ReorderBuffer *rb, TransactionId xid,
    2821                 :             :                     XLogRecPtr commit_lsn, XLogRecPtr end_lsn,
    2822                 :             :                     TimestampTz commit_time,
    2823                 :             :                     ReplOriginId origin_id, XLogRecPtr origin_lsn)
    2824                 :             : {
    2825                 :             :     Snapshot    snapshot_now;
    2826                 :        1951 :     CommandId   command_id = FirstCommandId;
    2827                 :             : 
    2828                 :        1951 :     txn->final_lsn = commit_lsn;
    2829                 :        1951 :     txn->end_lsn = end_lsn;
    2830                 :        1951 :     txn->commit_time = commit_time;
    2831                 :        1951 :     txn->origin_id = origin_id;
    2832                 :        1951 :     txn->origin_lsn = origin_lsn;
    2833                 :             : 
    2834                 :             :     /*
    2835                 :             :      * If the transaction was (partially) streamed, we need to commit it in a
    2836                 :             :      * 'streamed' way. That is, we first stream the remaining part of the
    2837                 :             :      * transaction, and then invoke stream_commit message.
    2838                 :             :      *
    2839                 :             :      * Called after everything (origin ID, LSN, ...) is stored in the
    2840                 :             :      * transaction to avoid passing that information directly.
    2841                 :             :      */
    2842         [ +  + ]:        1951 :     if (rbtxn_is_streamed(txn))
    2843                 :             :     {
    2844                 :          64 :         ReorderBufferStreamCommit(rb, txn);
    2845                 :          64 :         return;
    2846                 :             :     }
    2847                 :             : 
    2848                 :             :     /*
    2849                 :             :      * If this transaction has no snapshot, it didn't make any changes to the
    2850                 :             :      * database, so there's nothing to decode.  Note that
    2851                 :             :      * ReorderBufferCommitChild will have transferred any snapshots from
    2852                 :             :      * subtransactions if there were any.
    2853                 :             :      */
    2854         [ +  + ]:        1887 :     if (txn->base_snapshot == NULL)
    2855                 :             :     {
    2856                 :             :         Assert(txn->ninvalidations == 0);
    2857                 :             : 
    2858                 :             :         /*
    2859                 :             :          * Removing this txn before a commit might result in the computation
    2860                 :             :          * of an incorrect restart_lsn. See SnapBuildProcessRunningXacts.
    2861                 :             :          */
    2862         [ +  - ]:           3 :         if (!rbtxn_is_prepared(txn))
    2863                 :           3 :             ReorderBufferCleanupTXN(rb, txn);
    2864                 :           3 :         return;
    2865                 :             :     }
    2866                 :             : 
    2867                 :        1884 :     snapshot_now = txn->base_snapshot;
    2868                 :             : 
    2869                 :             :     /* Process and send the changes to output plugin. */
    2870                 :        1884 :     ReorderBufferProcessTXN(rb, txn, commit_lsn, snapshot_now,
    2871                 :             :                             command_id, false);
    2872                 :             : }
    2873                 :             : 
    2874                 :             : /*
    2875                 :             :  * Commit a transaction.
    2876                 :             :  *
    2877                 :             :  * See comments for ReorderBufferReplay().
    2878                 :             :  */
    2879                 :             : void
    2880                 :        1954 : ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
    2881                 :             :                     XLogRecPtr commit_lsn, XLogRecPtr end_lsn,
    2882                 :             :                     TimestampTz commit_time,
    2883                 :             :                     ReplOriginId origin_id, XLogRecPtr origin_lsn)
    2884                 :             : {
    2885                 :             :     ReorderBufferTXN *txn;
    2886                 :             : 
    2887                 :        1954 :     txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
    2888                 :             :                                 false);
    2889                 :             : 
    2890                 :             :     /* unknown transaction, nothing to replay */
    2891         [ +  + ]:        1954 :     if (txn == NULL)
    2892                 :          48 :         return;
    2893                 :             : 
    2894                 :        1906 :     ReorderBufferReplay(txn, rb, xid, commit_lsn, end_lsn, commit_time,
    2895                 :             :                         origin_id, origin_lsn);
    2896                 :             : }
    2897                 :             : 
    2898                 :             : /*
    2899                 :             :  * Record the prepare information for a transaction. Also, mark the transaction
    2900                 :             :  * as a prepared transaction.
    2901                 :             :  */
    2902                 :             : bool
    2903                 :         163 : ReorderBufferRememberPrepareInfo(ReorderBuffer *rb, TransactionId xid,
    2904                 :             :                                  XLogRecPtr prepare_lsn, XLogRecPtr end_lsn,
    2905                 :             :                                  TimestampTz prepare_time,
    2906                 :             :                                  ReplOriginId origin_id, XLogRecPtr origin_lsn)
    2907                 :             : {
    2908                 :             :     ReorderBufferTXN *txn;
    2909                 :             : 
    2910                 :         163 :     txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr, false);
    2911                 :             : 
    2912                 :             :     /* unknown transaction, nothing to do */
    2913         [ -  + ]:         163 :     if (txn == NULL)
    2914                 :           0 :         return false;
    2915                 :             : 
    2916                 :             :     /*
    2917                 :             :      * Remember the prepare information to be later used by commit prepared in
    2918                 :             :      * case we skip doing prepare.
    2919                 :             :      */
    2920                 :         163 :     txn->final_lsn = prepare_lsn;
    2921                 :         163 :     txn->end_lsn = end_lsn;
    2922                 :         163 :     txn->prepare_time = prepare_time;
    2923                 :         163 :     txn->origin_id = origin_id;
    2924                 :         163 :     txn->origin_lsn = origin_lsn;
    2925                 :             : 
    2926                 :             :     /* Mark this transaction as a prepared transaction */
    2927                 :             :     Assert((txn->txn_flags & RBTXN_PREPARE_STATUS_MASK) == 0);
    2928                 :         163 :     txn->txn_flags |= RBTXN_IS_PREPARED;
    2929                 :             : 
    2930                 :         163 :     return true;
    2931                 :             : }
    2932                 :             : 
    2933                 :             : /* Remember that we have skipped prepare */
    2934                 :             : void
    2935                 :         121 : ReorderBufferSkipPrepare(ReorderBuffer *rb, TransactionId xid)
    2936                 :             : {
    2937                 :             :     ReorderBufferTXN *txn;
    2938                 :             : 
    2939                 :         121 :     txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr, false);
    2940                 :             : 
    2941                 :             :     /* unknown transaction, nothing to do */
    2942         [ -  + ]:         121 :     if (txn == NULL)
    2943                 :           0 :         return;
    2944                 :             : 
    2945                 :             :     /* txn must have been marked as a prepared transaction */
    2946                 :             :     Assert((txn->txn_flags & RBTXN_PREPARE_STATUS_MASK) == RBTXN_IS_PREPARED);
    2947                 :         121 :     txn->txn_flags |= RBTXN_SKIPPED_PREPARE;
    2948                 :             : }
    2949                 :             : 
    2950                 :             : /*
    2951                 :             :  * Prepare a two-phase transaction.
    2952                 :             :  *
    2953                 :             :  * See comments for ReorderBufferReplay().
    2954                 :             :  */
    2955                 :             : void
    2956                 :          42 : ReorderBufferPrepare(ReorderBuffer *rb, TransactionId xid,
    2957                 :             :                      char *gid)
    2958                 :             : {
    2959                 :             :     ReorderBufferTXN *txn;
    2960                 :             : 
    2961                 :          42 :     txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
    2962                 :             :                                 false);
    2963                 :             : 
    2964                 :             :     /* unknown transaction, nothing to replay */
    2965         [ -  + ]:          42 :     if (txn == NULL)
    2966                 :           0 :         return;
    2967                 :             : 
    2968                 :             :     /*
    2969                 :             :      * txn must have been marked as a prepared transaction and must have
    2970                 :             :      * neither been skipped nor sent a prepare. Also, the prepare info must
    2971                 :             :      * have been updated in it by now.
    2972                 :             :      */
    2973                 :             :     Assert((txn->txn_flags & RBTXN_PREPARE_STATUS_MASK) == RBTXN_IS_PREPARED);
    2974                 :             :     Assert(XLogRecPtrIsValid(txn->final_lsn));
    2975                 :             : 
    2976                 :          42 :     txn->gid = pstrdup(gid);
    2977                 :             : 
    2978                 :          42 :     ReorderBufferReplay(txn, rb, xid, txn->final_lsn, txn->end_lsn,
    2979                 :          42 :                         txn->prepare_time, txn->origin_id, txn->origin_lsn);
    2980                 :             : 
    2981                 :             :     /*
    2982                 :             :      * Send a prepare if not already done so. This might occur if we have
    2983                 :             :      * detected a concurrent abort while replaying the non-streaming
    2984                 :             :      * transaction.
    2985                 :             :      */
    2986         [ -  + ]:          42 :     if (!rbtxn_sent_prepare(txn))
    2987                 :             :     {
    2988                 :           0 :         rb->prepare(rb, txn, txn->final_lsn);
    2989                 :           0 :         txn->txn_flags |= RBTXN_SENT_PREPARE;
    2990                 :             :     }
    2991                 :             : }
    2992                 :             : 
    2993                 :             : /*
    2994                 :             :  * This is used to handle COMMIT/ROLLBACK PREPARED.
    2995                 :             :  */
    2996                 :             : void
    2997                 :          43 : ReorderBufferFinishPrepared(ReorderBuffer *rb, TransactionId xid,
    2998                 :             :                             XLogRecPtr commit_lsn, XLogRecPtr end_lsn,
    2999                 :             :                             XLogRecPtr two_phase_at,
    3000                 :             :                             TimestampTz commit_time, ReplOriginId origin_id,
    3001                 :             :                             XLogRecPtr origin_lsn, char *gid, bool is_commit)
    3002                 :             : {
    3003                 :             :     ReorderBufferTXN *txn;
    3004                 :             :     XLogRecPtr  prepare_end_lsn;
    3005                 :             :     TimestampTz prepare_time;
    3006                 :             : 
    3007                 :          43 :     txn = ReorderBufferTXNByXid(rb, xid, false, NULL, commit_lsn, false);
    3008                 :             : 
    3009                 :             :     /* unknown transaction, nothing to do */
    3010         [ -  + ]:          43 :     if (txn == NULL)
    3011                 :           0 :         return;
    3012                 :             : 
    3013                 :             :     /*
    3014                 :             :      * By this time the txn has the prepare record information, remember it to
    3015                 :             :      * be later used for rollback.
    3016                 :             :      */
    3017                 :          43 :     prepare_end_lsn = txn->end_lsn;
    3018                 :          43 :     prepare_time = txn->prepare_time;
    3019                 :             : 
    3020                 :             :     /* add the gid in the txn */
    3021                 :          43 :     txn->gid = pstrdup(gid);
    3022                 :             : 
    3023                 :             :     /*
    3024                 :             :      * It is possible that this transaction is not decoded at prepare time
    3025                 :             :      * either because by that time we didn't have a consistent snapshot, or
    3026                 :             :      * two_phase was not enabled, or it was decoded earlier but we have
    3027                 :             :      * restarted. We only need to send the prepare if it was not decoded
    3028                 :             :      * earlier. We don't need to decode the xact for aborts if it is not done
    3029                 :             :      * already.
    3030                 :             :      */
    3031   [ +  +  +  - ]:          43 :     if ((txn->final_lsn < two_phase_at) && is_commit)
    3032                 :             :     {
    3033                 :             :         /*
    3034                 :             :          * txn must have been marked as a prepared transaction and skipped but
    3035                 :             :          * not sent a prepare. Also, the prepare info must have been updated
    3036                 :             :          * in txn even if we skip prepare.
    3037                 :             :          */
    3038                 :             :         Assert((txn->txn_flags & RBTXN_PREPARE_STATUS_MASK) ==
    3039                 :             :                (RBTXN_IS_PREPARED | RBTXN_SKIPPED_PREPARE));
    3040                 :             :         Assert(XLogRecPtrIsValid(txn->final_lsn));
    3041                 :             : 
    3042                 :             :         /*
    3043                 :             :          * By this time the txn has the prepare record information and it is
    3044                 :             :          * important to use that so that downstream gets the accurate
    3045                 :             :          * information. If instead, we have passed commit information here
    3046                 :             :          * then downstream can behave as it has already replayed commit
    3047                 :             :          * prepared after the restart.
    3048                 :             :          */
    3049                 :           3 :         ReorderBufferReplay(txn, rb, xid, txn->final_lsn, txn->end_lsn,
    3050                 :           3 :                             txn->prepare_time, txn->origin_id, txn->origin_lsn);
    3051                 :             :     }
    3052                 :             : 
    3053                 :          43 :     txn->final_lsn = commit_lsn;
    3054                 :          43 :     txn->end_lsn = end_lsn;
    3055                 :          43 :     txn->commit_time = commit_time;
    3056                 :          43 :     txn->origin_id = origin_id;
    3057                 :          43 :     txn->origin_lsn = origin_lsn;
    3058                 :             : 
    3059         [ +  + ]:          43 :     if (is_commit)
    3060                 :          33 :         rb->commit_prepared(rb, txn, commit_lsn);
    3061                 :             :     else
    3062                 :          10 :         rb->rollback_prepared(rb, txn, prepare_end_lsn, prepare_time);
    3063                 :             : 
    3064                 :             :     /* cleanup: make sure there's no cache pollution */
    3065                 :          43 :     ReorderBufferExecuteInvalidations(txn->ninvalidations,
    3066                 :             :                                       txn->invalidations);
    3067                 :          43 :     ReorderBufferCleanupTXN(rb, txn);
    3068                 :             : }
    3069                 :             : 
    3070                 :             : /*
    3071                 :             :  * Abort a transaction that possibly has previous changes. Needs to be first
    3072                 :             :  * called for subtransactions and then for the toplevel xid.
    3073                 :             :  *
    3074                 :             :  * NB: Transactions handled here have to have actively aborted (i.e. have
    3075                 :             :  * produced an abort record). Implicitly aborted transactions are handled via
    3076                 :             :  * ReorderBufferAbortOld(); transactions we're just not interested in, but
    3077                 :             :  * which have committed are handled in ReorderBufferForget().
    3078                 :             :  *
    3079                 :             :  * This function purges this transaction and its contents from memory and
    3080                 :             :  * disk.
    3081                 :             :  */
    3082                 :             : void
    3083                 :         243 : ReorderBufferAbort(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn,
    3084                 :             :                    TimestampTz abort_time)
    3085                 :             : {
    3086                 :             :     ReorderBufferTXN *txn;
    3087                 :             : 
    3088                 :         243 :     txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
    3089                 :             :                                 false);
    3090                 :             : 
    3091                 :             :     /* unknown, nothing to remove */
    3092         [ -  + ]:         243 :     if (txn == NULL)
    3093                 :           0 :         return;
    3094                 :             : 
    3095                 :         243 :     txn->abort_time = abort_time;
    3096                 :             : 
    3097                 :             :     /* For streamed transactions notify the remote node about the abort. */
    3098         [ +  + ]:         243 :     if (rbtxn_is_streamed(txn))
    3099                 :             :     {
    3100                 :          30 :         rb->stream_abort(rb, txn, lsn);
    3101                 :             : 
    3102                 :             :         /*
    3103                 :             :          * We might have decoded changes for this transaction that could load
    3104                 :             :          * the cache as per the current transaction's view (consider DDL's
    3105                 :             :          * happened in this transaction). We don't want the decoding of future
    3106                 :             :          * transactions to use those cache entries so execute only the inval
    3107                 :             :          * messages in this transaction.
    3108                 :             :          */
    3109         [ -  + ]:          30 :         if (txn->ninvalidations > 0)
    3110                 :           0 :             ReorderBufferImmediateInvalidation(rb, txn->ninvalidations,
    3111                 :             :                                                txn->invalidations);
    3112                 :             :     }
    3113                 :             : 
    3114                 :             :     /* cosmetic... */
    3115                 :         243 :     txn->final_lsn = lsn;
    3116                 :             : 
    3117                 :             :     /* remove potential on-disk data, and deallocate */
    3118                 :         243 :     ReorderBufferCleanupTXN(rb, txn);
    3119                 :             : }
    3120                 :             : 
    3121                 :             : /*
    3122                 :             :  * Abort all transactions that aren't actually running anymore because the
    3123                 :             :  * server restarted.
    3124                 :             :  *
    3125                 :             :  * NB: These really have to be transactions that have aborted due to a server
    3126                 :             :  * crash/immediate restart, as we don't deal with invalidations here.
    3127                 :             :  */
    3128                 :             : void
    3129                 :        1713 : ReorderBufferAbortOld(ReorderBuffer *rb, TransactionId oldestRunningXid)
    3130                 :             : {
    3131                 :             :     dlist_mutable_iter it;
    3132                 :             : 
    3133                 :             :     /*
    3134                 :             :      * Iterate through all (potential) toplevel TXNs and abort all that are
    3135                 :             :      * older than what possibly can be running. Once we've found the first
    3136                 :             :      * that is alive we stop, there might be some that acquired an xid earlier
    3137                 :             :      * but started writing later, but it's unlikely and they will be cleaned
    3138                 :             :      * up in a later call to this function.
    3139                 :             :      */
    3140   [ +  -  +  + ]:        1718 :     dlist_foreach_modify(it, &rb->toplevel_by_lsn)
    3141                 :             :     {
    3142                 :             :         ReorderBufferTXN *txn;
    3143                 :             : 
    3144                 :          64 :         txn = dlist_container(ReorderBufferTXN, node, it.cur);
    3145                 :             : 
    3146         [ +  + ]:          64 :         if (TransactionIdPrecedes(txn->xid, oldestRunningXid))
    3147                 :             :         {
    3148         [ -  + ]:           5 :             elog(DEBUG2, "aborting old transaction %u", txn->xid);
    3149                 :             : 
    3150                 :             :             /* Notify the remote node about the crash/immediate restart. */
    3151         [ -  + ]:           5 :             if (rbtxn_is_streamed(txn))
    3152                 :           0 :                 rb->stream_abort(rb, txn, InvalidXLogRecPtr);
    3153                 :             : 
    3154                 :             :             /* remove potential on-disk data, and deallocate this tx */
    3155                 :           5 :             ReorderBufferCleanupTXN(rb, txn);
    3156                 :             :         }
    3157                 :             :         else
    3158                 :          59 :             return;
    3159                 :             :     }
    3160                 :             : }
    3161                 :             : 
    3162                 :             : /*
    3163                 :             :  * Forget the contents of a transaction if we aren't interested in its
    3164                 :             :  * contents. Needs to be first called for subtransactions and then for the
    3165                 :             :  * toplevel xid.
    3166                 :             :  *
    3167                 :             :  * This is significantly different to ReorderBufferAbort() because
    3168                 :             :  * transactions that have committed need to be treated differently from aborted
    3169                 :             :  * ones since they may have modified the catalog.
    3170                 :             :  *
    3171                 :             :  * Note that this is only allowed to be called in the moment a transaction
    3172                 :             :  * commit has just been read, not earlier; otherwise later records referring
    3173                 :             :  * to this xid might re-create the transaction incompletely.
    3174                 :             :  */
    3175                 :             : void
    3176                 :        2995 : ReorderBufferForget(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn)
    3177                 :             : {
    3178                 :             :     ReorderBufferTXN *txn;
    3179                 :             : 
    3180                 :        2995 :     txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
    3181                 :             :                                 false);
    3182                 :             : 
    3183                 :             :     /* unknown, nothing to forget */
    3184         [ +  + ]:        2995 :     if (txn == NULL)
    3185                 :         565 :         return;
    3186                 :             : 
    3187                 :             :     /* this transaction mustn't be streamed */
    3188                 :             :     Assert(!rbtxn_is_streamed(txn));
    3189                 :             : 
    3190                 :             :     /* cosmetic... */
    3191                 :        2430 :     txn->final_lsn = lsn;
    3192                 :             : 
    3193                 :             :     /*
    3194                 :             :      * Process only cache invalidation messages in this transaction if there
    3195                 :             :      * are any. Even if we're not interested in the transaction's contents, it
    3196                 :             :      * could have manipulated the catalog and we need to update the caches
    3197                 :             :      * according to that.
    3198                 :             :      */
    3199   [ +  +  +  + ]:        2430 :     if (txn->base_snapshot != NULL && txn->ninvalidations > 0)
    3200                 :         668 :         ReorderBufferImmediateInvalidation(rb, txn->ninvalidations,
    3201                 :             :                                            txn->invalidations);
    3202                 :             :     else
    3203                 :             :         Assert(txn->ninvalidations == 0);
    3204                 :             : 
    3205                 :             :     /* remove potential on-disk data, and deallocate */
    3206                 :        2430 :     ReorderBufferCleanupTXN(rb, txn);
    3207                 :             : }
    3208                 :             : 
    3209                 :             : /*
    3210                 :             :  * Invalidate cache for those transactions that need to be skipped just in case
    3211                 :             :  * catalogs were manipulated as part of the transaction.
    3212                 :             :  *
    3213                 :             :  * Note that this is a special-purpose function for prepared transactions where
    3214                 :             :  * we don't want to clean up the TXN even when we decide to skip it. See
    3215                 :             :  * DecodePrepare.
    3216                 :             :  */
    3217                 :             : void
    3218                 :         118 : ReorderBufferInvalidate(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn)
    3219                 :             : {
    3220                 :             :     ReorderBufferTXN *txn;
    3221                 :             : 
    3222                 :         118 :     txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
    3223                 :             :                                 false);
    3224                 :             : 
    3225                 :             :     /* unknown, nothing to do */
    3226         [ -  + ]:         118 :     if (txn == NULL)
    3227                 :           0 :         return;
    3228                 :             : 
    3229                 :             :     /*
    3230                 :             :      * Process cache invalidation messages if there are any. Even if we're not
    3231                 :             :      * interested in the transaction's contents, it could have manipulated the
    3232                 :             :      * catalog and we need to update the caches according to that.
    3233                 :             :      */
    3234   [ +  -  +  + ]:         118 :     if (txn->base_snapshot != NULL && txn->ninvalidations > 0)
    3235                 :          29 :         ReorderBufferImmediateInvalidation(rb, txn->ninvalidations,
    3236                 :             :                                            txn->invalidations);
    3237                 :             :     else
    3238                 :             :         Assert(txn->ninvalidations == 0);
    3239                 :             : }
    3240                 :             : 
    3241                 :             : 
    3242                 :             : /*
    3243                 :             :  * Execute invalidations happening outside the context of a decoded
    3244                 :             :  * transaction. That currently happens either for xid-less commits
    3245                 :             :  * (cf. RecordTransactionCommit()) or for invalidations in uninteresting
    3246                 :             :  * transactions (via ReorderBufferForget()).
    3247                 :             :  */
    3248                 :             : void
    3249                 :         730 : ReorderBufferImmediateInvalidation(ReorderBuffer *rb, uint32 ninvalidations,
    3250                 :             :                                    SharedInvalidationMessage *invalidations)
    3251                 :             : {
    3252                 :         730 :     bool        use_subtxn = IsTransactionOrTransactionBlock();
    3253                 :         730 :     MemoryContext ccxt = CurrentMemoryContext;
    3254                 :         730 :     ResourceOwner cowner = CurrentResourceOwner;
    3255                 :             : 
    3256         [ +  + ]:         730 :     if (use_subtxn)
    3257                 :         476 :         BeginInternalSubTransaction("replay");
    3258                 :             : 
    3259                 :             :     /*
    3260                 :             :      * Force invalidations to happen outside of a valid transaction - that way
    3261                 :             :      * entries will just be marked as invalid without accessing the catalog.
    3262                 :             :      * That's advantageous because we don't need to setup the full state
    3263                 :             :      * necessary for catalog access.
    3264                 :             :      */
    3265         [ +  + ]:         730 :     if (use_subtxn)
    3266                 :         476 :         AbortCurrentTransaction();
    3267                 :             : 
    3268         [ +  + ]:       28987 :     for (uint32 i = 0; i < ninvalidations; i++)
    3269                 :       28257 :         LocalExecuteInvalidationMessage(&invalidations[i]);
    3270                 :             : 
    3271         [ +  + ]:         730 :     if (use_subtxn)
    3272                 :             :     {
    3273                 :         476 :         RollbackAndReleaseCurrentSubTransaction();
    3274                 :         476 :         MemoryContextSwitchTo(ccxt);
    3275                 :         476 :         CurrentResourceOwner = cowner;
    3276                 :             :     }
    3277                 :         730 : }
    3278                 :             : 
    3279                 :             : /*
    3280                 :             :  * Tell reorderbuffer about an xid seen in the WAL stream. Has to be called at
    3281                 :             :  * least once for every xid in XLogRecord->xl_xid (other places in records
    3282                 :             :  * may, but do not have to be passed through here).
    3283                 :             :  *
    3284                 :             :  * Reorderbuffer keeps some data structures about transactions in LSN order,
    3285                 :             :  * for efficiency. To do that it has to know about when transactions are seen
    3286                 :             :  * first in the WAL. As many types of records are not actually interesting for
    3287                 :             :  * logical decoding, they do not necessarily pass through here.
    3288                 :             :  */
    3289                 :             : void
    3290                 :     2425072 : ReorderBufferProcessXid(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn)
    3291                 :             : {
    3292                 :             :     /* many records won't have an xid assigned, centralize check here */
    3293         [ +  + ]:     2425072 :     if (xid != InvalidTransactionId)
    3294                 :     2422444 :         ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
    3295                 :     2425072 : }
    3296                 :             : 
    3297                 :             : /*
    3298                 :             :  * Add a new snapshot to this transaction that may only used after lsn 'lsn'
    3299                 :             :  * because the previous snapshot doesn't describe the catalog correctly for
    3300                 :             :  * following rows.
    3301                 :             :  */
    3302                 :             : void
    3303                 :        1675 : ReorderBufferAddSnapshot(ReorderBuffer *rb, TransactionId xid,
    3304                 :             :                          XLogRecPtr lsn, Snapshot snap)
    3305                 :             : {
    3306                 :        1675 :     ReorderBufferChange *change = ReorderBufferAllocChange(rb);
    3307                 :             : 
    3308                 :        1675 :     change->data.snapshot = snap;
    3309                 :        1675 :     change->action = REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT;
    3310                 :             : 
    3311                 :        1675 :     ReorderBufferQueueChange(rb, xid, lsn, change, false);
    3312                 :        1675 : }
    3313                 :             : 
    3314                 :             : /*
    3315                 :             :  * Set up the transaction's base snapshot.
    3316                 :             :  *
    3317                 :             :  * If we know that xid is a subtransaction, set the base snapshot on the
    3318                 :             :  * top-level transaction instead.
    3319                 :             :  */
    3320                 :             : void
    3321                 :        4193 : ReorderBufferSetBaseSnapshot(ReorderBuffer *rb, TransactionId xid,
    3322                 :             :                              XLogRecPtr lsn, Snapshot snap)
    3323                 :             : {
    3324                 :             :     ReorderBufferTXN *txn;
    3325                 :             :     bool        is_new;
    3326                 :             : 
    3327                 :             :     Assert(snap != NULL);
    3328                 :             : 
    3329                 :             :     /*
    3330                 :             :      * Fetch the transaction to operate on.  If we know it's a subtransaction,
    3331                 :             :      * operate on its top-level transaction instead.
    3332                 :             :      */
    3333                 :        4193 :     txn = ReorderBufferTXNByXid(rb, xid, true, &is_new, lsn, true);
    3334         [ +  + ]:        4193 :     if (rbtxn_is_known_subxact(txn))
    3335                 :         107 :         txn = ReorderBufferTXNByXid(rb, txn->toplevel_xid, false,
    3336                 :             :                                     NULL, InvalidXLogRecPtr, false);
    3337                 :             :     Assert(txn->base_snapshot == NULL);
    3338                 :             : 
    3339                 :        4193 :     txn->base_snapshot = snap;
    3340                 :        4193 :     txn->base_snapshot_lsn = lsn;
    3341                 :        4193 :     dlist_push_tail(&rb->txns_by_base_snapshot_lsn, &txn->base_snapshot_node);
    3342                 :             : 
    3343                 :        4193 :     AssertTXNLsnOrder(rb);
    3344                 :        4193 : }
    3345                 :             : 
    3346                 :             : /*
    3347                 :             :  * Access the catalog with this CommandId at this point in the changestream.
    3348                 :             :  *
    3349                 :             :  * May only be called for command ids > 1
    3350                 :             :  */
    3351                 :             : void
    3352                 :       28117 : ReorderBufferAddNewCommandId(ReorderBuffer *rb, TransactionId xid,
    3353                 :             :                              XLogRecPtr lsn, CommandId cid)
    3354                 :             : {
    3355                 :       28117 :     ReorderBufferChange *change = ReorderBufferAllocChange(rb);
    3356                 :             : 
    3357                 :       28117 :     change->data.command_id = cid;
    3358                 :       28117 :     change->action = REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID;
    3359                 :             : 
    3360                 :       28117 :     ReorderBufferQueueChange(rb, xid, lsn, change, false);
    3361                 :       28117 : }
    3362                 :             : 
    3363                 :             : /*
    3364                 :             :  * Update memory counters to account for the new or removed change.
    3365                 :             :  *
    3366                 :             :  * We update two counters - in the reorder buffer, and in the transaction
    3367                 :             :  * containing the change. The reorder buffer counter allows us to quickly
    3368                 :             :  * decide if we reached the memory limit, the transaction counter allows
    3369                 :             :  * us to quickly pick the largest transaction for eviction.
    3370                 :             :  *
    3371                 :             :  * Either txn or change must be non-NULL at least. We update the memory
    3372                 :             :  * counter of txn if it's non-NULL, otherwise change->txn.
    3373                 :             :  *
    3374                 :             :  * When streaming is enabled, we need to update the toplevel transaction
    3375                 :             :  * counters instead - we don't really care about subtransactions as we
    3376                 :             :  * can't stream them individually anyway, and we only pick toplevel
    3377                 :             :  * transactions for eviction. So only toplevel transactions matter.
    3378                 :             :  */
    3379                 :             : static void
    3380                 :     1999146 : ReorderBufferChangeMemoryUpdate(ReorderBuffer *rb,
    3381                 :             :                                 ReorderBufferChange *change,
    3382                 :             :                                 ReorderBufferTXN *txn,
    3383                 :             :                                 bool addition, Size sz)
    3384                 :             : {
    3385                 :             :     ReorderBufferTXN *toptxn;
    3386                 :             : 
    3387                 :             :     Assert(txn || change);
    3388                 :             : 
    3389                 :             :     /*
    3390                 :             :      * Ignore tuple CID changes, because those are not evicted when reaching
    3391                 :             :      * memory limit. So we just don't count them, because it might easily
    3392                 :             :      * trigger a pointless attempt to spill.
    3393                 :             :      */
    3394   [ +  +  +  + ]:     1999146 :     if (change && change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID)
    3395                 :       27764 :         return;
    3396                 :             : 
    3397         [ +  + ]:     1971382 :     if (sz == 0)
    3398                 :        1094 :         return;
    3399                 :             : 
    3400         [ +  + ]:     1970288 :     if (txn == NULL)
    3401                 :     1961361 :         txn = change->txn;
    3402                 :             :     Assert(txn != NULL);
    3403                 :             : 
    3404                 :             :     /*
    3405                 :             :      * Update the total size in top level as well. This is later used to
    3406                 :             :      * compute the decoding stats.
    3407                 :             :      */
    3408         [ +  + ]:     1970288 :     toptxn = rbtxn_get_toptxn(txn);
    3409                 :             : 
    3410         [ +  + ]:     1970288 :     if (addition)
    3411                 :             :     {
    3412                 :     1782601 :         Size        oldsize = txn->size;
    3413                 :             : 
    3414                 :     1782601 :         txn->size += sz;
    3415                 :     1782601 :         rb->size += sz;
    3416                 :             : 
    3417                 :             :         /* Update the total size in the top transaction. */
    3418                 :     1782601 :         toptxn->total_size += sz;
    3419                 :             : 
    3420                 :             :         /* Update the max-heap */
    3421         [ +  + ]:     1782601 :         if (oldsize != 0)
    3422                 :     1773594 :             pairingheap_remove(rb->txn_heap, &txn->txn_node);
    3423                 :     1782601 :         pairingheap_add(rb->txn_heap, &txn->txn_node);
    3424                 :             :     }
    3425                 :             :     else
    3426                 :             :     {
    3427                 :             :         Assert((rb->size >= sz) && (txn->size >= sz));
    3428                 :      187687 :         txn->size -= sz;
    3429                 :      187687 :         rb->size -= sz;
    3430                 :             : 
    3431                 :             :         /* Update the total size in the top transaction. */
    3432                 :      187687 :         toptxn->total_size -= sz;
    3433                 :             : 
    3434                 :             :         /* Update the max-heap */
    3435                 :      187687 :         pairingheap_remove(rb->txn_heap, &txn->txn_node);
    3436         [ +  + ]:      187687 :         if (txn->size != 0)
    3437                 :      178731 :             pairingheap_add(rb->txn_heap, &txn->txn_node);
    3438                 :             :     }
    3439                 :             : 
    3440                 :             :     Assert(txn->size <= rb->size);
    3441                 :             : }
    3442                 :             : 
    3443                 :             : /*
    3444                 :             :  * Add new (relfilelocator, tid) -> (cmin, cmax) mappings.
    3445                 :             :  *
    3446                 :             :  * We do not include this change type in memory accounting, because we
    3447                 :             :  * keep CIDs in a separate list and do not evict them when reaching
    3448                 :             :  * the memory limit.
    3449                 :             :  */
    3450                 :             : void
    3451                 :       28117 : ReorderBufferAddNewTupleCids(ReorderBuffer *rb, TransactionId xid,
    3452                 :             :                              XLogRecPtr lsn, RelFileLocator locator,
    3453                 :             :                              ItemPointerData tid, CommandId cmin,
    3454                 :             :                              CommandId cmax, CommandId combocid)
    3455                 :             : {
    3456                 :       28117 :     ReorderBufferChange *change = ReorderBufferAllocChange(rb);
    3457                 :             :     ReorderBufferTXN *txn;
    3458                 :             : 
    3459                 :       28117 :     txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
    3460                 :             : 
    3461                 :       28117 :     change->data.tuplecid.locator = locator;
    3462                 :       28117 :     change->data.tuplecid.tid = tid;
    3463                 :       28117 :     change->data.tuplecid.cmin = cmin;
    3464                 :       28117 :     change->data.tuplecid.cmax = cmax;
    3465                 :       28117 :     change->data.tuplecid.combocid = combocid;
    3466                 :       28117 :     change->lsn = lsn;
    3467                 :       28117 :     change->txn = txn;
    3468                 :       28117 :     change->action = REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID;
    3469                 :             : 
    3470                 :       28117 :     dlist_push_tail(&txn->tuplecids, &change->node);
    3471                 :       28117 :     txn->ntuplecids++;
    3472                 :       28117 : }
    3473                 :             : 
    3474                 :             : /*
    3475                 :             :  * Add new invalidation messages to the reorder buffer queue.
    3476                 :             :  */
    3477                 :             : static void
    3478                 :        6046 : ReorderBufferQueueInvalidations(ReorderBuffer *rb, TransactionId xid,
    3479                 :             :                                 XLogRecPtr lsn, Size nmsgs,
    3480                 :             :                                 SharedInvalidationMessage *msgs)
    3481                 :             : {
    3482                 :             :     ReorderBufferChange *change;
    3483                 :             : 
    3484                 :        6046 :     change = ReorderBufferAllocChange(rb);
    3485                 :        6046 :     change->action = REORDER_BUFFER_CHANGE_INVALIDATION;
    3486                 :        6046 :     change->data.inval.ninvalidations = nmsgs;
    3487                 :        6046 :     change->data.inval.invalidations = palloc_array(SharedInvalidationMessage, nmsgs);
    3488                 :        6046 :     memcpy(change->data.inval.invalidations, msgs,
    3489                 :             :            sizeof(SharedInvalidationMessage) * nmsgs);
    3490                 :             : 
    3491                 :        6046 :     ReorderBufferQueueChange(rb, xid, lsn, change, false);
    3492                 :        6046 : }
    3493                 :             : 
    3494                 :             : /*
    3495                 :             :  * A helper function for ReorderBufferAddInvalidations() and
    3496                 :             :  * ReorderBufferAddDistributedInvalidations() to accumulate the invalidation
    3497                 :             :  * messages to the **invals_out.
    3498                 :             :  */
    3499                 :             : static void
    3500                 :        6046 : ReorderBufferAccumulateInvalidations(SharedInvalidationMessage **invals_out,
    3501                 :             :                                      uint32 *ninvals_out,
    3502                 :             :                                      SharedInvalidationMessage *msgs_new,
    3503                 :             :                                      Size nmsgs_new)
    3504                 :             : {
    3505         [ +  + ]:        6046 :     if (*ninvals_out == 0)
    3506                 :             :     {
    3507                 :        1676 :         *ninvals_out = nmsgs_new;
    3508                 :        1676 :         *invals_out = palloc_array(SharedInvalidationMessage, nmsgs_new);
    3509                 :        1676 :         memcpy(*invals_out, msgs_new, sizeof(SharedInvalidationMessage) * nmsgs_new);
    3510                 :             :     }
    3511                 :             :     else
    3512                 :             :     {
    3513                 :             :         /* Enlarge the array of inval messages */
    3514                 :        4370 :         *invals_out =
    3515                 :        4370 :             repalloc_array(*invals_out, SharedInvalidationMessage,
    3516                 :             :                            (*ninvals_out + nmsgs_new));
    3517                 :        4370 :         memcpy(*invals_out + *ninvals_out, msgs_new,
    3518                 :             :                nmsgs_new * sizeof(SharedInvalidationMessage));
    3519                 :        4370 :         *ninvals_out += nmsgs_new;
    3520                 :             :     }
    3521                 :        6046 : }
    3522                 :             : 
    3523                 :             : /*
    3524                 :             :  * Accumulate the invalidations for executing them later.
    3525                 :             :  *
    3526                 :             :  * This needs to be called for each XLOG_XACT_INVALIDATIONS message and
    3527                 :             :  * accumulates all the invalidation messages in the toplevel transaction, if
    3528                 :             :  * available, otherwise in the current transaction, as well as in the form of
    3529                 :             :  * change in reorder buffer.  We require to record it in form of the change
    3530                 :             :  * so that we can execute only the required invalidations instead of executing
    3531                 :             :  * all the invalidations on each CommandId increment.  We also need to
    3532                 :             :  * accumulate these in the txn buffer because in some cases where we skip
    3533                 :             :  * processing the transaction (see ReorderBufferForget), we need to execute
    3534                 :             :  * all the invalidations together.
    3535                 :             :  */
    3536                 :             : void
    3537                 :        6018 : ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid,
    3538                 :             :                               XLogRecPtr lsn, Size nmsgs,
    3539                 :             :                               SharedInvalidationMessage *msgs)
    3540                 :             : {
    3541                 :             :     ReorderBufferTXN *txn;
    3542                 :             :     MemoryContext oldcontext;
    3543                 :             : 
    3544                 :        6018 :     txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
    3545                 :             : 
    3546                 :        6018 :     oldcontext = MemoryContextSwitchTo(rb->context);
    3547                 :             : 
    3548                 :             :     /*
    3549                 :             :      * Collect all the invalidations under the top transaction, if available,
    3550                 :             :      * so that we can execute them all together.  See comments atop this
    3551                 :             :      * function.
    3552                 :             :      */
    3553         [ +  + ]:        6018 :     txn = rbtxn_get_toptxn(txn);
    3554                 :             : 
    3555                 :             :     Assert(nmsgs > 0);
    3556                 :             : 
    3557                 :        6018 :     ReorderBufferAccumulateInvalidations(&txn->invalidations,
    3558                 :             :                                          &txn->ninvalidations,
    3559                 :             :                                          msgs, nmsgs);
    3560                 :             : 
    3561                 :        6018 :     ReorderBufferQueueInvalidations(rb, xid, lsn, nmsgs, msgs);
    3562                 :             : 
    3563                 :        6018 :     MemoryContextSwitchTo(oldcontext);
    3564                 :        6018 : }
    3565                 :             : 
    3566                 :             : /*
    3567                 :             :  * Accumulate the invalidations distributed by other committed transactions
    3568                 :             :  * for executing them later.
    3569                 :             :  *
    3570                 :             :  * This function is similar to ReorderBufferAddInvalidations() but stores
    3571                 :             :  * the given inval messages to the txn->invalidations_distributed with the
    3572                 :             :  * overflow check.
    3573                 :             :  *
    3574                 :             :  * This needs to be called by committed transactions to distribute their
    3575                 :             :  * inval messages to in-progress transactions.
    3576                 :             :  */
    3577                 :             : void
    3578                 :          28 : ReorderBufferAddDistributedInvalidations(ReorderBuffer *rb, TransactionId xid,
    3579                 :             :                                          XLogRecPtr lsn, Size nmsgs,
    3580                 :             :                                          SharedInvalidationMessage *msgs)
    3581                 :             : {
    3582                 :             :     ReorderBufferTXN *txn;
    3583                 :             :     MemoryContext oldcontext;
    3584                 :             : 
    3585                 :          28 :     txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
    3586                 :             : 
    3587                 :          28 :     oldcontext = MemoryContextSwitchTo(rb->context);
    3588                 :             : 
    3589                 :             :     /*
    3590                 :             :      * Collect all the invalidations under the top transaction, if available,
    3591                 :             :      * so that we can execute them all together.  See comments
    3592                 :             :      * ReorderBufferAddInvalidations.
    3593                 :             :      */
    3594         [ -  + ]:          28 :     txn = rbtxn_get_toptxn(txn);
    3595                 :             : 
    3596                 :             :     Assert(nmsgs > 0);
    3597                 :             : 
    3598         [ +  - ]:          28 :     if (!rbtxn_distr_inval_overflowed(txn))
    3599                 :             :     {
    3600                 :             :         /*
    3601                 :             :          * Check the transaction has enough space for storing distributed
    3602                 :             :          * invalidation messages.
    3603                 :             :          */
    3604         [ -  + ]:          28 :         if (txn->ninvalidations_distributed + nmsgs >= MAX_DISTR_INVAL_MSG_PER_TXN)
    3605                 :             :         {
    3606                 :             :             /*
    3607                 :             :              * Mark the invalidation message as overflowed and free up the
    3608                 :             :              * messages accumulated so far.
    3609                 :             :              */
    3610                 :           0 :             txn->txn_flags |= RBTXN_DISTR_INVAL_OVERFLOWED;
    3611                 :             : 
    3612         [ #  # ]:           0 :             if (txn->invalidations_distributed)
    3613                 :             :             {
    3614                 :           0 :                 pfree(txn->invalidations_distributed);
    3615                 :           0 :                 txn->invalidations_distributed = NULL;
    3616                 :           0 :                 txn->ninvalidations_distributed = 0;
    3617                 :             :             }
    3618                 :             :         }
    3619                 :             :         else
    3620                 :          28 :             ReorderBufferAccumulateInvalidations(&txn->invalidations_distributed,
    3621                 :             :                                                  &txn->ninvalidations_distributed,
    3622                 :             :                                                  msgs, nmsgs);
    3623                 :             :     }
    3624                 :             : 
    3625                 :             :     /* Queue the invalidation messages into the transaction */
    3626                 :          28 :     ReorderBufferQueueInvalidations(rb, xid, lsn, nmsgs, msgs);
    3627                 :             : 
    3628                 :          28 :     MemoryContextSwitchTo(oldcontext);
    3629                 :          28 : }
    3630                 :             : 
    3631                 :             : /*
    3632                 :             :  * Apply all invalidations we know. Possibly we only need parts at this point
    3633                 :             :  * in the changestream but we don't know which those are.
    3634                 :             :  */
    3635                 :             : static void
    3636                 :        8057 : ReorderBufferExecuteInvalidations(uint32 nmsgs, SharedInvalidationMessage *msgs)
    3637                 :             : {
    3638         [ +  + ]:       55667 :     for (uint32 i = 0; i < nmsgs; i++)
    3639                 :       47610 :         LocalExecuteInvalidationMessage(&msgs[i]);
    3640                 :        8057 : }
    3641                 :             : 
    3642                 :             : /*
    3643                 :             :  * Mark a transaction as containing catalog changes
    3644                 :             :  */
    3645                 :             : void
    3646                 :       34210 : ReorderBufferXidSetCatalogChanges(ReorderBuffer *rb, TransactionId xid,
    3647                 :             :                                   XLogRecPtr lsn)
    3648                 :             : {
    3649                 :             :     ReorderBufferTXN *txn;
    3650                 :             : 
    3651                 :       34210 :     txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
    3652                 :             : 
    3653         [ +  + ]:       34210 :     if (!rbtxn_has_catalog_changes(txn))
    3654                 :             :     {
    3655                 :        1704 :         txn->txn_flags |= RBTXN_HAS_CATALOG_CHANGES;
    3656                 :        1704 :         dclist_push_tail(&rb->catchange_txns, &txn->catchange_node);
    3657                 :             :     }
    3658                 :             : 
    3659                 :             :     /*
    3660                 :             :      * Mark top-level transaction as having catalog changes too if one of its
    3661                 :             :      * children has so that the ReorderBufferBuildTupleCidHash can
    3662                 :             :      * conveniently check just top-level transaction and decide whether to
    3663                 :             :      * build the hash table or not.
    3664                 :             :      */
    3665         [ +  + ]:       34210 :     if (rbtxn_is_subtxn(txn))
    3666                 :             :     {
    3667         [ +  - ]:         896 :         ReorderBufferTXN *toptxn = rbtxn_get_toptxn(txn);
    3668                 :             : 
    3669         [ +  + ]:         896 :         if (!rbtxn_has_catalog_changes(toptxn))
    3670                 :             :         {
    3671                 :          20 :             toptxn->txn_flags |= RBTXN_HAS_CATALOG_CHANGES;
    3672                 :          20 :             dclist_push_tail(&rb->catchange_txns, &toptxn->catchange_node);
    3673                 :             :         }
    3674                 :             :     }
    3675                 :       34210 : }
    3676                 :             : 
    3677                 :             : /*
    3678                 :             :  * Return palloc'ed array of the transactions that have changed catalogs.
    3679                 :             :  * The returned array is sorted in xidComparator order.
    3680                 :             :  *
    3681                 :             :  * The caller must free the returned array when done with it.
    3682                 :             :  */
    3683                 :             : TransactionId *
    3684                 :         336 : ReorderBufferGetCatalogChangesXacts(ReorderBuffer *rb)
    3685                 :             : {
    3686                 :             :     dlist_iter  iter;
    3687                 :         336 :     TransactionId *xids = NULL;
    3688                 :         336 :     size_t      xcnt = 0;
    3689                 :             : 
    3690                 :             :     /* Quick return if the list is empty */
    3691         [ +  + ]:         336 :     if (dclist_count(&rb->catchange_txns) == 0)
    3692                 :         328 :         return NULL;
    3693                 :             : 
    3694                 :             :     /* Initialize XID array */
    3695                 :           8 :     xids = palloc_array(TransactionId, dclist_count(&rb->catchange_txns));
    3696   [ +  -  +  + ]:          19 :     dclist_foreach(iter, &rb->catchange_txns)
    3697                 :             :     {
    3698                 :          11 :         ReorderBufferTXN *txn = dclist_container(ReorderBufferTXN,
    3699                 :             :                                                  catchange_node,
    3700                 :             :                                                  iter.cur);
    3701                 :             : 
    3702                 :             :         Assert(rbtxn_has_catalog_changes(txn));
    3703                 :             : 
    3704                 :          11 :         xids[xcnt++] = txn->xid;
    3705                 :             :     }
    3706                 :             : 
    3707                 :           8 :     qsort(xids, xcnt, sizeof(TransactionId), xidComparator);
    3708                 :             : 
    3709                 :             :     Assert(xcnt == dclist_count(&rb->catchange_txns));
    3710                 :           8 :     return xids;
    3711                 :             : }
    3712                 :             : 
    3713                 :             : /*
    3714                 :             :  * Query whether a transaction is already *known* to contain catalog
    3715                 :             :  * changes. This can be wrong until directly before the commit!
    3716                 :             :  */
    3717                 :             : bool
    3718                 :        5251 : ReorderBufferXidHasCatalogChanges(ReorderBuffer *rb, TransactionId xid)
    3719                 :             : {
    3720                 :             :     ReorderBufferTXN *txn;
    3721                 :             : 
    3722                 :        5251 :     txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
    3723                 :             :                                 false);
    3724         [ +  + ]:        5251 :     if (txn == NULL)
    3725                 :         697 :         return false;
    3726                 :             : 
    3727                 :        4554 :     return rbtxn_has_catalog_changes(txn);
    3728                 :             : }
    3729                 :             : 
    3730                 :             : /*
    3731                 :             :  * ReorderBufferXidHasBaseSnapshot
    3732                 :             :  *      Have we already set the base snapshot for the given txn/subtxn?
    3733                 :             :  */
    3734                 :             : bool
    3735                 :     1592563 : ReorderBufferXidHasBaseSnapshot(ReorderBuffer *rb, TransactionId xid)
    3736                 :             : {
    3737                 :             :     ReorderBufferTXN *txn;
    3738                 :             : 
    3739                 :     1592563 :     txn = ReorderBufferTXNByXid(rb, xid, false,
    3740                 :             :                                 NULL, InvalidXLogRecPtr, false);
    3741                 :             : 
    3742                 :             :     /* transaction isn't known yet, ergo no snapshot */
    3743         [ +  + ]:     1592563 :     if (txn == NULL)
    3744                 :           3 :         return false;
    3745                 :             : 
    3746                 :             :     /* a known subtxn? operate on top-level txn instead */
    3747         [ +  + ]:     1592560 :     if (rbtxn_is_known_subxact(txn))
    3748                 :      382035 :         txn = ReorderBufferTXNByXid(rb, txn->toplevel_xid, false,
    3749                 :             :                                     NULL, InvalidXLogRecPtr, false);
    3750                 :             : 
    3751                 :     1592560 :     return txn->base_snapshot != NULL;
    3752                 :             : }
    3753                 :             : 
    3754                 :             : 
    3755                 :             : /*
    3756                 :             :  * ---------------------------------------
    3757                 :             :  * Disk serialization support
    3758                 :             :  * ---------------------------------------
    3759                 :             :  */
    3760                 :             : 
    3761                 :             : /*
    3762                 :             :  * Ensure the IO buffer is >= sz.
    3763                 :             :  */
    3764                 :             : static void
    3765                 :     2989918 : ReorderBufferSerializeReserve(ReorderBuffer *rb, Size sz)
    3766                 :             : {
    3767         [ +  + ]:     2989918 :     if (!rb->outbufsize)
    3768                 :             :     {
    3769                 :          48 :         rb->outbuf = MemoryContextAlloc(rb->context, sz);
    3770                 :          48 :         rb->outbufsize = sz;
    3771                 :             :     }
    3772         [ +  + ]:     2989870 :     else if (rb->outbufsize < sz)
    3773                 :             :     {
    3774                 :         267 :         rb->outbuf = repalloc(rb->outbuf, sz);
    3775                 :         267 :         rb->outbufsize = sz;
    3776                 :             :     }
    3777                 :     2989918 : }
    3778                 :             : 
    3779                 :             : 
    3780                 :             : /* Compare two transactions by size */
    3781                 :             : static int
    3782                 :      346705 : ReorderBufferTXNSizeCompare(const pairingheap_node *a, const pairingheap_node *b, void *arg)
    3783                 :             : {
    3784                 :      346705 :     const ReorderBufferTXN *ta = pairingheap_const_container(ReorderBufferTXN, txn_node, a);
    3785                 :      346705 :     const ReorderBufferTXN *tb = pairingheap_const_container(ReorderBufferTXN, txn_node, b);
    3786                 :             : 
    3787         [ +  + ]:      346705 :     if (ta->size < tb->size)
    3788                 :      245446 :         return -1;
    3789         [ +  + ]:      101259 :     if (ta->size > tb->size)
    3790                 :      100355 :         return 1;
    3791                 :         904 :     return 0;
    3792                 :             : }
    3793                 :             : 
    3794                 :             : /*
    3795                 :             :  * Find the largest transaction (toplevel or subxact) to evict (spill to disk).
    3796                 :             :  */
    3797                 :             : static ReorderBufferTXN *
    3798                 :        3920 : ReorderBufferLargestTXN(ReorderBuffer *rb)
    3799                 :             : {
    3800                 :             :     ReorderBufferTXN *largest;
    3801                 :             : 
    3802                 :             :     /* Get the largest transaction from the max-heap */
    3803                 :        3920 :     largest = pairingheap_container(ReorderBufferTXN, txn_node,
    3804                 :             :                                     pairingheap_first(rb->txn_heap));
    3805                 :             : 
    3806                 :             :     Assert(largest);
    3807                 :             :     Assert(largest->size > 0);
    3808                 :             :     Assert(largest->size <= rb->size);
    3809                 :             : 
    3810                 :        3920 :     return largest;
    3811                 :             : }
    3812                 :             : 
    3813                 :             : /*
    3814                 :             :  * Find the largest streamable (and non-aborted) toplevel transaction to evict
    3815                 :             :  * (by streaming).
    3816                 :             :  *
    3817                 :             :  * This can be seen as an optimized version of ReorderBufferLargestTXN, which
    3818                 :             :  * should give us the same transaction (because we don't update memory account
    3819                 :             :  * for subtransaction with streaming, so it's always 0). But we can simply
    3820                 :             :  * iterate over the limited number of toplevel transactions that have a base
    3821                 :             :  * snapshot. There is no use of selecting a transaction that doesn't have base
    3822                 :             :  * snapshot because we don't decode such transactions.  Also, we do not select
    3823                 :             :  * the transaction which doesn't have any streamable change.
    3824                 :             :  *
    3825                 :             :  * Note that, we skip transactions that contain incomplete changes. There
    3826                 :             :  * is a scope of optimization here such that we can select the largest
    3827                 :             :  * transaction which has incomplete changes.  But that will make the code and
    3828                 :             :  * design quite complex and that might not be worth the benefit.  If we plan to
    3829                 :             :  * stream the transactions that contain incomplete changes then we need to
    3830                 :             :  * find a way to partially stream/truncate the transaction changes in-memory
    3831                 :             :  * and build a mechanism to partially truncate the spilled files.
    3832                 :             :  * Additionally, whenever we partially stream the transaction we need to
    3833                 :             :  * maintain the last streamed lsn and next time we need to restore from that
    3834                 :             :  * segment and the offset in WAL.  As we stream the changes from the top
    3835                 :             :  * transaction and restore them subtransaction wise, we need to even remember
    3836                 :             :  * the subxact from where we streamed the last change.
    3837                 :             :  */
    3838                 :             : static ReorderBufferTXN *
    3839                 :         800 : ReorderBufferLargestStreamableTopTXN(ReorderBuffer *rb)
    3840                 :             : {
    3841                 :             :     dlist_iter  iter;
    3842                 :         800 :     Size        largest_size = 0;
    3843                 :         800 :     ReorderBufferTXN *largest = NULL;
    3844                 :             : 
    3845                 :             :     /* Find the largest top-level transaction having a base snapshot. */
    3846   [ +  -  +  + ]:        1712 :     dlist_foreach(iter, &rb->txns_by_base_snapshot_lsn)
    3847                 :             :     {
    3848                 :             :         ReorderBufferTXN *txn;
    3849                 :             : 
    3850                 :         912 :         txn = dlist_container(ReorderBufferTXN, base_snapshot_node, iter.cur);
    3851                 :             : 
    3852                 :             :         /* must not be a subtxn */
    3853                 :             :         Assert(!rbtxn_is_known_subxact(txn));
    3854                 :             :         /* base_snapshot must be set */
    3855                 :             :         Assert(txn->base_snapshot != NULL);
    3856                 :             : 
    3857                 :             :         /* Don't consider these kinds of transactions for eviction. */
    3858         [ +  + ]:         912 :         if (rbtxn_has_partial_change(txn) ||
    3859         [ +  + ]:         765 :             !rbtxn_has_streamable_change(txn) ||
    3860         [ -  + ]:         735 :             rbtxn_is_aborted(txn))
    3861                 :         177 :             continue;
    3862                 :             : 
    3863                 :             :         /* Find the largest of the eviction candidates. */
    3864   [ +  +  +  - ]:         735 :         if ((largest == NULL || txn->total_size > largest_size) &&
    3865         [ +  + ]:         735 :             (txn->total_size > 0))
    3866                 :             :         {
    3867                 :         689 :             largest = txn;
    3868                 :         689 :             largest_size = txn->total_size;
    3869                 :             :         }
    3870                 :             :     }
    3871                 :             : 
    3872                 :         800 :     return largest;
    3873                 :             : }
    3874                 :             : 
    3875                 :             : /*
    3876                 :             :  * Check whether the logical_decoding_work_mem limit was reached, and if yes
    3877                 :             :  * pick the largest (sub)transaction at-a-time to evict and spill its changes to
    3878                 :             :  * disk or send to the output plugin until we reach under the memory limit.
    3879                 :             :  *
    3880                 :             :  * If debug_logical_replication_streaming is set to "immediate", stream or
    3881                 :             :  * serialize the changes immediately.
    3882                 :             :  *
    3883                 :             :  * XXX At this point we select the transactions until we reach under the memory
    3884                 :             :  * limit, but we might also adapt a more elaborate eviction strategy - for example
    3885                 :             :  * evicting enough transactions to free certain fraction (e.g. 50%) of the memory
    3886                 :             :  * limit.
    3887                 :             :  */
    3888                 :             : static void
    3889                 :     1603872 : ReorderBufferCheckMemoryLimit(ReorderBuffer *rb)
    3890                 :             : {
    3891                 :             :     ReorderBufferTXN *txn;
    3892                 :     1603872 :     bool        update_stats = true;
    3893                 :             : 
    3894         [ +  + ]:     1603872 :     if (rb->size >= logical_decoding_work_mem * (Size) 1024)
    3895                 :             :     {
    3896                 :             :         /*
    3897                 :             :          * Update the statistics as the memory usage has reached the limit. We
    3898                 :             :          * report the statistics update later in this function since we can
    3899                 :             :          * update the slot statistics altogether while streaming or
    3900                 :             :          * serializing transactions in most cases.
    3901                 :             :          */
    3902                 :        3477 :         rb->memExceededCount += 1;
    3903                 :             :     }
    3904         [ +  + ]:     1600395 :     else if (debug_logical_replication_streaming == DEBUG_LOGICAL_REP_STREAMING_BUFFERED)
    3905                 :             :     {
    3906                 :             :         /*
    3907                 :             :          * Bail out if debug_logical_replication_streaming is buffered and we
    3908                 :             :          * haven't exceeded the memory limit.
    3909                 :             :          */
    3910                 :     1599326 :         return;
    3911                 :             :     }
    3912                 :             : 
    3913                 :             :     /*
    3914                 :             :      * If debug_logical_replication_streaming is immediate, loop until there's
    3915                 :             :      * no change. Otherwise, loop until we reach under the memory limit. One
    3916                 :             :      * might think that just by evicting the largest (sub)transaction we will
    3917                 :             :      * come under the memory limit based on assumption that the selected
    3918                 :             :      * transaction is at least as large as the most recent change (which
    3919                 :             :      * caused us to go over the memory limit). However, that is not true
    3920                 :             :      * because a user can reduce the logical_decoding_work_mem to a smaller
    3921                 :             :      * value before the most recent change.
    3922                 :             :      */
    3923         [ +  + ]:        9089 :     while (rb->size >= logical_decoding_work_mem * (Size) 1024 ||
    3924         [ +  + ]:        5612 :            (debug_logical_replication_streaming == DEBUG_LOGICAL_REP_STREAMING_IMMEDIATE &&
    3925         [ +  + ]:        2135 :             rb->size > 0))
    3926                 :             :     {
    3927                 :             :         /*
    3928                 :             :          * Pick the largest non-aborted transaction and evict it from memory
    3929                 :             :          * by streaming, if possible.  Otherwise, spill to disk.
    3930                 :             :          */
    3931   [ +  +  +  + ]:        5343 :         if (ReorderBufferCanStartStreaming(rb) &&
    3932                 :         800 :             (txn = ReorderBufferLargestStreamableTopTXN(rb)) != NULL)
    3933                 :             :         {
    3934                 :             :             /* we know there has to be one, because the size is not zero */
    3935                 :             :             Assert(txn && rbtxn_is_toptxn(txn));
    3936                 :             :             Assert(txn->total_size > 0);
    3937                 :             :             Assert(rb->size >= txn->total_size);
    3938                 :             : 
    3939                 :             :             /* skip the transaction if aborted */
    3940         [ -  + ]:         623 :             if (ReorderBufferCheckAndTruncateAbortedTXN(rb, txn))
    3941                 :           0 :                 continue;
    3942                 :             : 
    3943                 :         623 :             ReorderBufferStreamTXN(rb, txn);
    3944                 :             :         }
    3945                 :             :         else
    3946                 :             :         {
    3947                 :             :             /*
    3948                 :             :              * Pick the largest transaction (or subtransaction) and evict it
    3949                 :             :              * from memory by serializing it to disk.
    3950                 :             :              */
    3951                 :        3920 :             txn = ReorderBufferLargestTXN(rb);
    3952                 :             : 
    3953                 :             :             /* we know there has to be one, because the size is not zero */
    3954                 :             :             Assert(txn);
    3955                 :             :             Assert(txn->size > 0);
    3956                 :             :             Assert(rb->size >= txn->size);
    3957                 :             : 
    3958                 :             :             /* skip the transaction if aborted */
    3959         [ +  + ]:        3920 :             if (ReorderBufferCheckAndTruncateAbortedTXN(rb, txn))
    3960                 :           9 :                 continue;
    3961                 :             : 
    3962                 :        3911 :             ReorderBufferSerializeTXN(rb, txn);
    3963                 :             :         }
    3964                 :             : 
    3965                 :             :         /*
    3966                 :             :          * After eviction, the transaction should have no entries in memory,
    3967                 :             :          * and should use 0 bytes for changes.
    3968                 :             :          */
    3969                 :             :         Assert(txn->size == 0);
    3970                 :             :         Assert(txn->nentries_mem == 0);
    3971                 :             : 
    3972                 :             :         /*
    3973                 :             :          * We've reported the memExceededCount update while streaming or
    3974                 :             :          * serializing the transaction.
    3975                 :             :          */
    3976                 :        4534 :         update_stats = false;
    3977                 :             :     }
    3978                 :             : 
    3979         [ +  + ]:        4546 :     if (update_stats)
    3980                 :          12 :         UpdateDecodingStats((LogicalDecodingContext *) rb->private_data);
    3981                 :             : 
    3982                 :             :     /* We must be under the memory limit now. */
    3983                 :             :     Assert(rb->size < logical_decoding_work_mem * (Size) 1024);
    3984                 :             : }
    3985                 :             : 
    3986                 :             : /*
    3987                 :             :  * Spill data of a large transaction (and its subtransactions) to disk.
    3988                 :             :  */
    3989                 :             : static void
    3990                 :        4172 : ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
    3991                 :             : {
    3992                 :             :     dlist_iter  subtxn_i;
    3993                 :             :     dlist_mutable_iter change_i;
    3994                 :        4172 :     int         fd = -1;
    3995                 :        4172 :     XLogSegNo   curOpenSegNo = 0;
    3996                 :        4172 :     Size        spilled = 0;
    3997                 :        4172 :     Size        size = txn->size;
    3998                 :             : 
    3999         [ -  + ]:        4172 :     elog(DEBUG2, "spill %u changes in XID %u to disk",
    4000                 :             :          (uint32) txn->nentries_mem, txn->xid);
    4001                 :             : 
    4002                 :             :     /* do the same to all child TXs */
    4003   [ +  -  +  + ]:        4393 :     dlist_foreach(subtxn_i, &txn->subtxns)
    4004                 :             :     {
    4005                 :             :         ReorderBufferTXN *subtxn;
    4006                 :             : 
    4007                 :         221 :         subtxn = dlist_container(ReorderBufferTXN, node, subtxn_i.cur);
    4008                 :         221 :         ReorderBufferSerializeTXN(rb, subtxn);
    4009                 :             :     }
    4010                 :             : 
    4011                 :             :     /* serialize changestream */
    4012   [ +  -  +  + ]:     1329291 :     dlist_foreach_modify(change_i, &txn->changes)
    4013                 :             :     {
    4014                 :             :         ReorderBufferChange *change;
    4015                 :             : 
    4016                 :     1325119 :         change = dlist_container(ReorderBufferChange, node, change_i.cur);
    4017                 :             : 
    4018                 :             :         /*
    4019                 :             :          * store in segment in which it belongs by start lsn, don't split over
    4020                 :             :          * multiple segments tho
    4021                 :             :          */
    4022         [ +  + ]:     1325119 :         if (fd == -1 ||
    4023         [ +  + ]:     1321155 :             !XLByteInSeg(change->lsn, curOpenSegNo, wal_segment_size))
    4024                 :             :         {
    4025                 :             :             char        path[MAXPGPATH];
    4026                 :             : 
    4027         [ +  + ]:        3982 :             if (fd != -1)
    4028                 :          18 :                 CloseTransientFile(fd);
    4029                 :             : 
    4030                 :        3982 :             XLByteToSeg(change->lsn, curOpenSegNo, wal_segment_size);
    4031                 :             : 
    4032                 :             :             /*
    4033                 :             :              * No need to care about TLIs here, only used during a single run,
    4034                 :             :              * so each LSN only maps to a specific WAL record.
    4035                 :             :              */
    4036                 :        3982 :             ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid,
    4037                 :             :                                         curOpenSegNo);
    4038                 :             : 
    4039                 :             :             /* open segment, create it if necessary */
    4040                 :        3982 :             fd = OpenTransientFile(path,
    4041                 :             :                                    O_CREAT | O_WRONLY | O_APPEND | PG_BINARY);
    4042                 :             : 
    4043         [ -  + ]:        3982 :             if (fd < 0)
    4044         [ #  # ]:           0 :                 ereport(ERROR,
    4045                 :             :                         (errcode_for_file_access(),
    4046                 :             :                          errmsg("could not open file \"%s\": %m", path)));
    4047                 :             :         }
    4048                 :             : 
    4049                 :     1325119 :         ReorderBufferSerializeChange(rb, txn, fd, change);
    4050                 :     1325119 :         dlist_delete(&change->node);
    4051                 :     1325119 :         ReorderBufferFreeChange(rb, change, false);
    4052                 :             : 
    4053                 :     1325119 :         spilled++;
    4054                 :             :     }
    4055                 :             : 
    4056                 :             :     /* Update the memory counter */
    4057                 :        4172 :     ReorderBufferChangeMemoryUpdate(rb, NULL, txn, false, size);
    4058                 :             : 
    4059                 :             :     /* update the statistics iff we have spilled anything */
    4060         [ +  + ]:        4172 :     if (spilled)
    4061                 :             :     {
    4062                 :        3964 :         rb->spillCount += 1;
    4063                 :        3964 :         rb->spillBytes += size;
    4064                 :             : 
    4065                 :             :         /* don't consider already serialized transactions */
    4066   [ +  +  +  - ]:        3964 :         rb->spillTxns += (rbtxn_is_serialized(txn) || rbtxn_is_serialized_clear(txn)) ? 0 : 1;
    4067                 :             : 
    4068                 :             :         /* update the decoding stats */
    4069                 :        3964 :         UpdateDecodingStats((LogicalDecodingContext *) rb->private_data);
    4070                 :             :     }
    4071                 :             : 
    4072                 :             :     Assert(spilled == txn->nentries_mem);
    4073                 :             :     Assert(dlist_is_empty(&txn->changes));
    4074                 :        4172 :     txn->nentries_mem = 0;
    4075                 :        4172 :     txn->txn_flags |= RBTXN_IS_SERIALIZED;
    4076                 :             : 
    4077         [ +  + ]:        4172 :     if (fd != -1)
    4078                 :        3964 :         CloseTransientFile(fd);
    4079                 :        4172 : }
    4080                 :             : 
    4081                 :             : /*
    4082                 :             :  * Serialize individual change to disk.
    4083                 :             :  */
    4084                 :             : static void
    4085                 :     1325119 : ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
    4086                 :             :                              int fd, ReorderBufferChange *change)
    4087                 :             : {
    4088                 :             :     ReorderBufferDiskChange *ondisk;
    4089                 :     1325119 :     Size        sz = sizeof(ReorderBufferDiskChange);
    4090                 :             : 
    4091                 :     1325119 :     ReorderBufferSerializeReserve(rb, sz);
    4092                 :             : 
    4093                 :     1325119 :     ondisk = (ReorderBufferDiskChange *) rb->outbuf;
    4094                 :     1325119 :     memcpy(&ondisk->change, change, sizeof(ReorderBufferChange));
    4095                 :             : 
    4096   [ +  +  +  +  :     1325119 :     switch (change->action)
                +  +  - ]
    4097                 :             :     {
    4098                 :             :             /* fall through these, they're all similar enough */
    4099                 :     1307631 :         case REORDER_BUFFER_CHANGE_INSERT:
    4100                 :             :         case REORDER_BUFFER_CHANGE_UPDATE:
    4101                 :             :         case REORDER_BUFFER_CHANGE_DELETE:
    4102                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
    4103                 :             :             {
    4104                 :             :                 char       *data;
    4105                 :             :                 HeapTuple   oldtup,
    4106                 :             :                             newtup;
    4107                 :     1307631 :                 Size        oldlen = 0;
    4108                 :     1307631 :                 Size        newlen = 0;
    4109                 :             : 
    4110                 :     1307631 :                 oldtup = change->data.tp.oldtuple;
    4111                 :     1307631 :                 newtup = change->data.tp.newtuple;
    4112                 :             : 
    4113         [ +  + ]:     1307631 :                 if (oldtup)
    4114                 :             :                 {
    4115                 :      160166 :                     sz += sizeof(HeapTupleData);
    4116                 :      160166 :                     oldlen = oldtup->t_len;
    4117                 :      160166 :                     sz += oldlen;
    4118                 :             :                 }
    4119                 :             : 
    4120         [ +  + ]:     1307631 :                 if (newtup)
    4121                 :             :                 {
    4122                 :     1093750 :                     sz += sizeof(HeapTupleData);
    4123                 :     1093750 :                     newlen = newtup->t_len;
    4124                 :     1093750 :                     sz += newlen;
    4125                 :             :                 }
    4126                 :             : 
    4127                 :             :                 /* make sure we have enough space */
    4128                 :     1307631 :                 ReorderBufferSerializeReserve(rb, sz);
    4129                 :             : 
    4130                 :     1307631 :                 data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
    4131                 :             :                 /* might have been reallocated above */
    4132                 :     1307631 :                 ondisk = (ReorderBufferDiskChange *) rb->outbuf;
    4133                 :             : 
    4134         [ +  + ]:     1307631 :                 if (oldlen)
    4135                 :             :                 {
    4136                 :      160166 :                     memcpy(data, oldtup, sizeof(HeapTupleData));
    4137                 :      160166 :                     data += sizeof(HeapTupleData);
    4138                 :             : 
    4139                 :      160166 :                     memcpy(data, oldtup->t_data, oldlen);
    4140                 :      160166 :                     data += oldlen;
    4141                 :             :                 }
    4142                 :             : 
    4143         [ +  + ]:     1307631 :                 if (newlen)
    4144                 :             :                 {
    4145                 :     1093750 :                     memcpy(data, newtup, sizeof(HeapTupleData));
    4146                 :     1093750 :                     data += sizeof(HeapTupleData);
    4147                 :             : 
    4148                 :     1093750 :                     memcpy(data, newtup->t_data, newlen);
    4149                 :     1093750 :                     data += newlen;
    4150                 :             :                 }
    4151                 :     1307631 :                 break;
    4152                 :             :             }
    4153                 :          13 :         case REORDER_BUFFER_CHANGE_MESSAGE:
    4154                 :             :             {
    4155                 :             :                 char       *data;
    4156                 :          13 :                 Size        prefix_size = strlen(change->data.msg.prefix) + 1;
    4157                 :             : 
    4158                 :          13 :                 sz += prefix_size + change->data.msg.message_size +
    4159                 :             :                     sizeof(Size) + sizeof(Size);
    4160                 :          13 :                 ReorderBufferSerializeReserve(rb, sz);
    4161                 :             : 
    4162                 :          13 :                 data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
    4163                 :             : 
    4164                 :             :                 /* might have been reallocated above */
    4165                 :          13 :                 ondisk = (ReorderBufferDiskChange *) rb->outbuf;
    4166                 :             : 
    4167                 :             :                 /* write the prefix including the size */
    4168                 :          13 :                 memcpy(data, &prefix_size, sizeof(Size));
    4169                 :          13 :                 data += sizeof(Size);
    4170                 :          13 :                 memcpy(data, change->data.msg.prefix,
    4171                 :             :                        prefix_size);
    4172                 :          13 :                 data += prefix_size;
    4173                 :             : 
    4174                 :             :                 /* write the message including the size */
    4175                 :          13 :                 memcpy(data, &change->data.msg.message_size, sizeof(Size));
    4176                 :          13 :                 data += sizeof(Size);
    4177                 :          13 :                 memcpy(data, change->data.msg.message,
    4178                 :             :                        change->data.msg.message_size);
    4179                 :          13 :                 data += change->data.msg.message_size;
    4180                 :             : 
    4181                 :          13 :                 break;
    4182                 :             :             }
    4183                 :         154 :         case REORDER_BUFFER_CHANGE_INVALIDATION:
    4184                 :             :             {
    4185                 :             :                 char       *data;
    4186                 :         154 :                 Size        inval_size = sizeof(SharedInvalidationMessage) *
    4187                 :         154 :                     change->data.inval.ninvalidations;
    4188                 :             : 
    4189                 :         154 :                 sz += inval_size;
    4190                 :             : 
    4191                 :         154 :                 ReorderBufferSerializeReserve(rb, sz);
    4192                 :         154 :                 data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
    4193                 :             : 
    4194                 :             :                 /* might have been reallocated above */
    4195                 :         154 :                 ondisk = (ReorderBufferDiskChange *) rb->outbuf;
    4196                 :         154 :                 memcpy(data, change->data.inval.invalidations, inval_size);
    4197                 :         154 :                 data += inval_size;
    4198                 :             : 
    4199                 :         154 :                 break;
    4200                 :             :             }
    4201                 :           8 :         case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
    4202                 :             :             {
    4203                 :             :                 Snapshot    snap;
    4204                 :             :                 char       *data;
    4205                 :             : 
    4206                 :           8 :                 snap = change->data.snapshot;
    4207                 :             : 
    4208                 :           8 :                 sz += sizeof(SnapshotData) +
    4209                 :           8 :                     sizeof(TransactionId) * snap->xcnt +
    4210                 :           8 :                     sizeof(TransactionId) * snap->subxcnt;
    4211                 :             : 
    4212                 :             :                 /* make sure we have enough space */
    4213                 :           8 :                 ReorderBufferSerializeReserve(rb, sz);
    4214                 :           8 :                 data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
    4215                 :             :                 /* might have been reallocated above */
    4216                 :           8 :                 ondisk = (ReorderBufferDiskChange *) rb->outbuf;
    4217                 :             : 
    4218                 :           8 :                 memcpy(data, snap, sizeof(SnapshotData));
    4219                 :           8 :                 data += sizeof(SnapshotData);
    4220                 :             : 
    4221         [ +  - ]:           8 :                 if (snap->xcnt)
    4222                 :             :                 {
    4223                 :           8 :                     memcpy(data, snap->xip,
    4224                 :           8 :                            sizeof(TransactionId) * snap->xcnt);
    4225                 :           8 :                     data += sizeof(TransactionId) * snap->xcnt;
    4226                 :             :                 }
    4227                 :             : 
    4228         [ -  + ]:           8 :                 if (snap->subxcnt)
    4229                 :             :                 {
    4230                 :           0 :                     memcpy(data, snap->subxip,
    4231                 :           0 :                            sizeof(TransactionId) * snap->subxcnt);
    4232                 :           0 :                     data += sizeof(TransactionId) * snap->subxcnt;
    4233                 :             :                 }
    4234                 :           8 :                 break;
    4235                 :             :             }
    4236                 :           2 :         case REORDER_BUFFER_CHANGE_TRUNCATE:
    4237                 :             :             {
    4238                 :             :                 Size        size;
    4239                 :             :                 char       *data;
    4240                 :             : 
    4241                 :             :                 /* account for the OIDs of truncated relations */
    4242                 :           2 :                 size = sizeof(Oid) * change->data.truncate.nrelids;
    4243                 :           2 :                 sz += size;
    4244                 :             : 
    4245                 :             :                 /* make sure we have enough space */
    4246                 :           2 :                 ReorderBufferSerializeReserve(rb, sz);
    4247                 :             : 
    4248                 :           2 :                 data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
    4249                 :             :                 /* might have been reallocated above */
    4250                 :           2 :                 ondisk = (ReorderBufferDiskChange *) rb->outbuf;
    4251                 :             : 
    4252                 :           2 :                 memcpy(data, change->data.truncate.relids, size);
    4253                 :           2 :                 data += size;
    4254                 :             : 
    4255                 :           2 :                 break;
    4256                 :             :             }
    4257                 :       17311 :         case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
    4258                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT:
    4259                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
    4260                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
    4261                 :             :             /* ReorderBufferChange contains everything important */
    4262                 :       17311 :             break;
    4263                 :             :     }
    4264                 :             : 
    4265                 :     1325119 :     ondisk->size = sz;
    4266                 :             : 
    4267                 :     1325119 :     errno = 0;
    4268                 :     1325119 :     pgstat_report_wait_start(WAIT_EVENT_REORDER_BUFFER_WRITE);
    4269         [ -  + ]:     1325119 :     if (write(fd, rb->outbuf, ondisk->size) != ondisk->size)
    4270                 :             :     {
    4271                 :           0 :         int         save_errno = errno;
    4272                 :             : 
    4273                 :           0 :         CloseTransientFile(fd);
    4274                 :             : 
    4275                 :             :         /* if write didn't set errno, assume problem is no disk space */
    4276         [ #  # ]:           0 :         errno = save_errno ? save_errno : ENOSPC;
    4277         [ #  # ]:           0 :         ereport(ERROR,
    4278                 :             :                 (errcode_for_file_access(),
    4279                 :             :                  errmsg("could not write to data file for XID %u: %m",
    4280                 :             :                         txn->xid)));
    4281                 :             :     }
    4282                 :     1325119 :     pgstat_report_wait_end();
    4283                 :             : 
    4284                 :             :     /*
    4285                 :             :      * Keep the transaction's final_lsn up to date with each change we send to
    4286                 :             :      * disk, so that ReorderBufferRestoreCleanup works correctly.  (We used to
    4287                 :             :      * only do this on commit and abort records, but that doesn't work if a
    4288                 :             :      * system crash leaves a transaction without its abort record).
    4289                 :             :      *
    4290                 :             :      * Make sure not to move it backwards.
    4291                 :             :      */
    4292         [ +  + ]:     1325119 :     if (txn->final_lsn < change->lsn)
    4293                 :     1320636 :         txn->final_lsn = change->lsn;
    4294                 :             : 
    4295                 :             :     Assert(ondisk->change.action == change->action);
    4296                 :     1325119 : }
    4297                 :             : 
    4298                 :             : /* Returns true, if the output plugin supports streaming, false, otherwise. */
    4299                 :             : static inline bool
    4300                 :     2159413 : ReorderBufferCanStream(ReorderBuffer *rb)
    4301                 :             : {
    4302                 :     2159413 :     LogicalDecodingContext *ctx = rb->private_data;
    4303                 :             : 
    4304                 :     2159413 :     return ctx->streaming;
    4305                 :             : }
    4306                 :             : 
    4307                 :             : /* Returns true, if the streaming can be started now, false, otherwise. */
    4308                 :             : static inline bool
    4309                 :      555541 : ReorderBufferCanStartStreaming(ReorderBuffer *rb)
    4310                 :             : {
    4311                 :      555541 :     LogicalDecodingContext *ctx = rb->private_data;
    4312                 :      555541 :     SnapBuild  *builder = ctx->snapshot_builder;
    4313                 :             : 
    4314                 :             :     /* We can't start streaming unless a consistent state is reached. */
    4315         [ -  + ]:      555541 :     if (SnapBuildCurrentState(builder) < SNAPBUILD_CONSISTENT)
    4316                 :           0 :         return false;
    4317                 :             : 
    4318                 :             :     /*
    4319                 :             :      * We can't start streaming immediately even if the streaming is enabled
    4320                 :             :      * because we previously decoded this transaction and now just are
    4321                 :             :      * restarting.
    4322                 :             :      */
    4323         [ +  + ]:      555541 :     if (ReorderBufferCanStream(rb) &&
    4324         [ +  + ]:      553283 :         !SnapBuildXactNeedsSkip(builder, ctx->reader->ReadRecPtr))
    4325                 :      206873 :         return true;
    4326                 :             : 
    4327                 :      348668 :     return false;
    4328                 :             : }
    4329                 :             : 
    4330                 :             : /*
    4331                 :             :  * Send data of a large transaction (and its subtransactions) to the
    4332                 :             :  * output plugin, but using the stream API.
    4333                 :             :  */
    4334                 :             : static void
    4335                 :         696 : ReorderBufferStreamTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
    4336                 :             : {
    4337                 :             :     Snapshot    snapshot_now;
    4338                 :             :     CommandId   command_id;
    4339                 :             :     Size        stream_bytes;
    4340                 :             :     bool        txn_is_streamed;
    4341                 :             : 
    4342                 :             :     /* We can never reach here for a subtransaction. */
    4343                 :             :     Assert(rbtxn_is_toptxn(txn));
    4344                 :             : 
    4345                 :             :     /*
    4346                 :             :      * We can't make any assumptions about base snapshot here, similar to what
    4347                 :             :      * ReorderBufferCommit() does. That relies on base_snapshot getting
    4348                 :             :      * transferred from subxact in ReorderBufferCommitChild(), but that was
    4349                 :             :      * not yet called as the transaction is in-progress.
    4350                 :             :      *
    4351                 :             :      * So just walk the subxacts and use the same logic here. But we only need
    4352                 :             :      * to do that once, when the transaction is streamed for the first time.
    4353                 :             :      * After that we need to reuse the snapshot from the previous run.
    4354                 :             :      *
    4355                 :             :      * Unlike DecodeCommit which adds xids of all the subtransactions in
    4356                 :             :      * snapshot's xip array via SnapBuildCommitTxn, we can't do that here but
    4357                 :             :      * we do add them to subxip array instead via ReorderBufferCopySnap. This
    4358                 :             :      * allows the catalog changes made in subtransactions decoded till now to
    4359                 :             :      * be visible.
    4360                 :             :      */
    4361         [ +  + ]:         696 :     if (txn->snapshot_now == NULL)
    4362                 :             :     {
    4363                 :             :         dlist_iter  subxact_i;
    4364                 :             : 
    4365                 :             :         /* make sure this transaction is streamed for the first time */
    4366                 :             :         Assert(!rbtxn_is_streamed(txn));
    4367                 :             : 
    4368                 :             :         /* at the beginning we should have invalid command ID */
    4369                 :             :         Assert(txn->command_id == InvalidCommandId);
    4370                 :             : 
    4371   [ +  -  +  + ]:          74 :         dlist_foreach(subxact_i, &txn->subtxns)
    4372                 :             :         {
    4373                 :             :             ReorderBufferTXN *subtxn;
    4374                 :             : 
    4375                 :           4 :             subtxn = dlist_container(ReorderBufferTXN, node, subxact_i.cur);
    4376                 :           4 :             ReorderBufferTransferSnapToParent(txn, subtxn);
    4377                 :             :         }
    4378                 :             : 
    4379                 :             :         /*
    4380                 :             :          * If this transaction has no snapshot, it didn't make any changes to
    4381                 :             :          * the database till now, so there's nothing to decode.
    4382                 :             :          */
    4383         [ -  + ]:          70 :         if (txn->base_snapshot == NULL)
    4384                 :             :         {
    4385                 :             :             Assert(txn->ninvalidations == 0);
    4386                 :           0 :             return;
    4387                 :             :         }
    4388                 :             : 
    4389                 :          70 :         command_id = FirstCommandId;
    4390                 :          70 :         snapshot_now = ReorderBufferCopySnap(rb, txn->base_snapshot,
    4391                 :             :                                              txn, command_id);
    4392                 :             :     }
    4393                 :             :     else
    4394                 :             :     {
    4395                 :             :         /* the transaction must have been already streamed */
    4396                 :             :         Assert(rbtxn_is_streamed(txn));
    4397                 :             : 
    4398                 :             :         /*
    4399                 :             :          * Nah, we already have snapshot from the previous streaming run. We
    4400                 :             :          * assume new subxacts can't move the LSN backwards, and so can't beat
    4401                 :             :          * the LSN condition in the previous branch (so no need to walk
    4402                 :             :          * through subxacts again). In fact, we must not do that as we may be
    4403                 :             :          * using snapshot half-way through the subxact.
    4404                 :             :          */
    4405                 :         626 :         command_id = txn->command_id;
    4406                 :             : 
    4407                 :             :         /*
    4408                 :             :          * We can't use txn->snapshot_now directly because after the last
    4409                 :             :          * streaming run, we might have got some new sub-transactions. So we
    4410                 :             :          * need to add them to the snapshot.
    4411                 :             :          */
    4412                 :         626 :         snapshot_now = ReorderBufferCopySnap(rb, txn->snapshot_now,
    4413                 :             :                                              txn, command_id);
    4414                 :             : 
    4415                 :             :         /* Free the previously copied snapshot. */
    4416                 :             :         Assert(txn->snapshot_now->copied);
    4417                 :         626 :         ReorderBufferFreeSnap(rb, txn->snapshot_now);
    4418                 :         626 :         txn->snapshot_now = NULL;
    4419                 :             :     }
    4420                 :             : 
    4421                 :             :     /*
    4422                 :             :      * Remember this information to be used later to update stats. We can't
    4423                 :             :      * update the stats here as an error while processing the changes would
    4424                 :             :      * lead to the accumulation of stats even though we haven't streamed all
    4425                 :             :      * the changes.
    4426                 :             :      */
    4427                 :         696 :     txn_is_streamed = rbtxn_is_streamed(txn);
    4428                 :         696 :     stream_bytes = txn->total_size;
    4429                 :             : 
    4430                 :             :     /* Process and send the changes to output plugin. */
    4431                 :         696 :     ReorderBufferProcessTXN(rb, txn, InvalidXLogRecPtr, snapshot_now,
    4432                 :             :                             command_id, true);
    4433                 :             : 
    4434                 :         696 :     rb->streamCount += 1;
    4435                 :         696 :     rb->streamBytes += stream_bytes;
    4436                 :             : 
    4437                 :             :     /* Don't consider already streamed transaction. */
    4438                 :         696 :     rb->streamTxns += (txn_is_streamed) ? 0 : 1;
    4439                 :             : 
    4440                 :             :     /* update the decoding stats */
    4441                 :         696 :     UpdateDecodingStats((LogicalDecodingContext *) rb->private_data);
    4442                 :             : 
    4443                 :             :     Assert(dlist_is_empty(&txn->changes));
    4444                 :             :     Assert(txn->nentries == 0);
    4445                 :             :     Assert(txn->nentries_mem == 0);
    4446                 :             : }
    4447                 :             : 
    4448                 :             : /*
    4449                 :             :  * Size of a change in memory.
    4450                 :             :  */
    4451                 :             : static Size
    4452                 :     2247433 : ReorderBufferChangeSize(ReorderBufferChange *change)
    4453                 :             : {
    4454                 :     2247433 :     Size        sz = sizeof(ReorderBufferChange);
    4455                 :             : 
    4456   [ +  +  +  +  :     2247433 :     switch (change->action)
                +  +  - ]
    4457                 :             :     {
    4458                 :             :             /* fall through these, they're all similar enough */
    4459                 :     2126088 :         case REORDER_BUFFER_CHANGE_INSERT:
    4460                 :             :         case REORDER_BUFFER_CHANGE_UPDATE:
    4461                 :             :         case REORDER_BUFFER_CHANGE_DELETE:
    4462                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
    4463                 :             :             {
    4464                 :             :                 HeapTuple   oldtup,
    4465                 :             :                             newtup;
    4466                 :     2126088 :                 Size        oldlen = 0;
    4467                 :     2126088 :                 Size        newlen = 0;
    4468                 :             : 
    4469                 :     2126088 :                 oldtup = change->data.tp.oldtuple;
    4470                 :     2126088 :                 newtup = change->data.tp.newtuple;
    4471                 :             : 
    4472         [ +  + ]:     2126088 :                 if (oldtup)
    4473                 :             :                 {
    4474                 :      262430 :                     sz += sizeof(HeapTupleData);
    4475                 :      262430 :                     oldlen = oldtup->t_len;
    4476                 :      262430 :                     sz += oldlen;
    4477                 :             :                 }
    4478                 :             : 
    4479         [ +  + ]:     2126088 :                 if (newtup)
    4480                 :             :                 {
    4481                 :     1779824 :                     sz += sizeof(HeapTupleData);
    4482                 :     1779824 :                     newlen = newtup->t_len;
    4483                 :     1779824 :                     sz += newlen;
    4484                 :             :                 }
    4485                 :             : 
    4486                 :     2126088 :                 break;
    4487                 :             :             }
    4488                 :          67 :         case REORDER_BUFFER_CHANGE_MESSAGE:
    4489                 :             :             {
    4490                 :          67 :                 Size        prefix_size = strlen(change->data.msg.prefix) + 1;
    4491                 :             : 
    4492                 :          67 :                 sz += prefix_size + change->data.msg.message_size +
    4493                 :             :                     sizeof(Size) + sizeof(Size);
    4494                 :             : 
    4495                 :          67 :                 break;
    4496                 :             :             }
    4497                 :       11830 :         case REORDER_BUFFER_CHANGE_INVALIDATION:
    4498                 :             :             {
    4499                 :       11830 :                 sz += sizeof(SharedInvalidationMessage) *
    4500                 :       11830 :                     change->data.inval.ninvalidations;
    4501                 :       11830 :                 break;
    4502                 :             :             }
    4503                 :        3338 :         case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
    4504                 :             :             {
    4505                 :             :                 Snapshot    snap;
    4506                 :             : 
    4507                 :        3338 :                 snap = change->data.snapshot;
    4508                 :             : 
    4509                 :        3338 :                 sz += sizeof(SnapshotData) +
    4510                 :        3338 :                     sizeof(TransactionId) * snap->xcnt +
    4511                 :        3338 :                     sizeof(TransactionId) * snap->subxcnt;
    4512                 :             : 
    4513                 :        3338 :                 break;
    4514                 :             :             }
    4515                 :          97 :         case REORDER_BUFFER_CHANGE_TRUNCATE:
    4516                 :             :             {
    4517                 :          97 :                 sz += sizeof(Oid) * change->data.truncate.nrelids;
    4518                 :             : 
    4519                 :          97 :                 break;
    4520                 :             :             }
    4521                 :      106013 :         case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
    4522                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT:
    4523                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
    4524                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
    4525                 :             :             /* ReorderBufferChange contains everything important */
    4526                 :      106013 :             break;
    4527                 :             :     }
    4528                 :             : 
    4529                 :     2247433 :     return sz;
    4530                 :             : }
    4531                 :             : 
    4532                 :             : 
    4533                 :             : /*
    4534                 :             :  * Restore a number of changes spilled to disk back into memory.
    4535                 :             :  */
    4536                 :             : static Size
    4537                 :         105 : ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn,
    4538                 :             :                             TXNEntryFile *file, XLogSegNo *segno)
    4539                 :             : {
    4540                 :         105 :     Size        restored = 0;
    4541                 :             :     XLogSegNo   last_segno;
    4542                 :             :     dlist_mutable_iter cleanup_iter;
    4543                 :         105 :     File       *fd = &file->vfd;
    4544                 :             : 
    4545                 :             :     Assert(XLogRecPtrIsValid(txn->first_lsn));
    4546                 :             :     Assert(XLogRecPtrIsValid(txn->final_lsn));
    4547                 :             : 
    4548                 :             :     /* free current entries, so we have memory for more */
    4549   [ +  -  +  + ]:      174916 :     dlist_foreach_modify(cleanup_iter, &txn->changes)
    4550                 :             :     {
    4551                 :      174811 :         ReorderBufferChange *cleanup =
    4552                 :      174811 :             dlist_container(ReorderBufferChange, node, cleanup_iter.cur);
    4553                 :             : 
    4554                 :      174811 :         dlist_delete(&cleanup->node);
    4555                 :      174811 :         ReorderBufferFreeChange(rb, cleanup, true);
    4556                 :             :     }
    4557                 :         105 :     txn->nentries_mem = 0;
    4558                 :             :     Assert(dlist_is_empty(&txn->changes));
    4559                 :             : 
    4560                 :         105 :     XLByteToSeg(txn->final_lsn, last_segno, wal_segment_size);
    4561                 :             : 
    4562   [ +  +  +  + ]:      178622 :     while (restored < max_changes_in_memory && *segno <= last_segno)
    4563                 :             :     {
    4564                 :             :         ssize_t     readBytes;
    4565                 :             :         ReorderBufferDiskChange *ondisk;
    4566                 :             : 
    4567         [ -  + ]:      178517 :         CHECK_FOR_INTERRUPTS();
    4568                 :             : 
    4569         [ +  + ]:      178517 :         if (*fd == -1)
    4570                 :             :         {
    4571                 :             :             char        path[MAXPGPATH];
    4572                 :             : 
    4573                 :             :             /* first time in */
    4574         [ +  + ]:          43 :             if (*segno == 0)
    4575                 :          40 :                 XLByteToSeg(txn->first_lsn, *segno, wal_segment_size);
    4576                 :             : 
    4577                 :             :             Assert(*segno != 0 || dlist_is_empty(&txn->changes));
    4578                 :             : 
    4579                 :             :             /*
    4580                 :             :              * No need to care about TLIs here, only used during a single run,
    4581                 :             :              * so each LSN only maps to a specific WAL record.
    4582                 :             :              */
    4583                 :          43 :             ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid,
    4584                 :             :                                         *segno);
    4585                 :             : 
    4586                 :          43 :             *fd = PathNameOpenFile(path, O_RDONLY | PG_BINARY);
    4587                 :             : 
    4588                 :             :             /* No harm in resetting the offset even in case of failure */
    4589                 :          43 :             file->curOffset = 0;
    4590                 :             : 
    4591   [ -  +  -  - ]:          43 :             if (*fd < 0 && errno == ENOENT)
    4592                 :             :             {
    4593                 :           0 :                 *fd = -1;
    4594                 :           0 :                 (*segno)++;
    4595                 :           0 :                 continue;
    4596                 :             :             }
    4597         [ -  + ]:          43 :             else if (*fd < 0)
    4598         [ #  # ]:           0 :                 ereport(ERROR,
    4599                 :             :                         (errcode_for_file_access(),
    4600                 :             :                          errmsg("could not open file \"%s\": %m",
    4601                 :             :                                 path)));
    4602                 :             :         }
    4603                 :             : 
    4604                 :             :         /*
    4605                 :             :          * Read the statically sized part of a change which has information
    4606                 :             :          * about the total size. If we couldn't read a record, we're at the
    4607                 :             :          * end of this file.
    4608                 :             :          */
    4609                 :      178517 :         ReorderBufferSerializeReserve(rb, sizeof(ReorderBufferDiskChange));
    4610                 :      178517 :         readBytes = FileRead(file->vfd, rb->outbuf,
    4611                 :             :                              sizeof(ReorderBufferDiskChange),
    4612                 :             :                              file->curOffset, WAIT_EVENT_REORDER_BUFFER_READ);
    4613                 :             : 
    4614                 :             :         /* eof */
    4615         [ +  + ]:      178517 :         if (readBytes == 0)
    4616                 :             :         {
    4617                 :          43 :             FileClose(*fd);
    4618                 :          43 :             *fd = -1;
    4619                 :          43 :             (*segno)++;
    4620                 :          43 :             continue;
    4621                 :             :         }
    4622         [ -  + ]:      178474 :         else if (readBytes < 0)
    4623         [ #  # ]:           0 :             ereport(ERROR,
    4624                 :             :                     (errcode_for_file_access(),
    4625                 :             :                      errmsg("could not read from reorderbuffer spill file: %m")));
    4626         [ -  + ]:      178474 :         else if (readBytes != sizeof(ReorderBufferDiskChange))
    4627         [ #  # ]:           0 :             ereport(ERROR,
    4628                 :             :                     (errcode_for_file_access(),
    4629                 :             :                      errmsg("could not read from reorderbuffer spill file: read %zd of %zu",
    4630                 :             :                             readBytes,
    4631                 :             :                             sizeof(ReorderBufferDiskChange))));
    4632                 :             : 
    4633                 :      178474 :         file->curOffset += readBytes;
    4634                 :             : 
    4635                 :      178474 :         ondisk = (ReorderBufferDiskChange *) rb->outbuf;
    4636                 :             : 
    4637                 :      178474 :         ReorderBufferSerializeReserve(rb,
    4638                 :      178474 :                                       sizeof(ReorderBufferDiskChange) + ondisk->size);
    4639                 :      178474 :         ondisk = (ReorderBufferDiskChange *) rb->outbuf;
    4640                 :             : 
    4641                 :      178474 :         readBytes = FileRead(file->vfd,
    4642                 :      178474 :                              rb->outbuf + sizeof(ReorderBufferDiskChange),
    4643                 :      178474 :                              ondisk->size - sizeof(ReorderBufferDiskChange),
    4644                 :             :                              file->curOffset,
    4645                 :             :                              WAIT_EVENT_REORDER_BUFFER_READ);
    4646                 :             : 
    4647         [ -  + ]:      178474 :         if (readBytes < 0)
    4648         [ #  # ]:           0 :             ereport(ERROR,
    4649                 :             :                     (errcode_for_file_access(),
    4650                 :             :                      errmsg("could not read from reorderbuffer spill file: %m")));
    4651         [ -  + ]:      178474 :         else if (readBytes != ondisk->size - sizeof(ReorderBufferDiskChange))
    4652         [ #  # ]:           0 :             ereport(ERROR,
    4653                 :             :                     (errcode_for_file_access(),
    4654                 :             :                      errmsg("could not read from reorderbuffer spill file: read %zd of %zu",
    4655                 :             :                             readBytes,
    4656                 :             :                             (ondisk->size - sizeof(ReorderBufferDiskChange)))));
    4657                 :             : 
    4658                 :      178474 :         file->curOffset += readBytes;
    4659                 :             : 
    4660                 :             :         /*
    4661                 :             :          * ok, read a full change from disk, now restore it into proper
    4662                 :             :          * in-memory format
    4663                 :             :          */
    4664                 :      178474 :         ReorderBufferRestoreChange(rb, txn, rb->outbuf);
    4665                 :      178474 :         restored++;
    4666                 :             :     }
    4667                 :             : 
    4668                 :         105 :     return restored;
    4669                 :             : }
    4670                 :             : 
    4671                 :             : /*
    4672                 :             :  * Convert change from its on-disk format to in-memory format and queue it onto
    4673                 :             :  * the TXN's ->changes list.
    4674                 :             :  *
    4675                 :             :  * Note: although "data" is declared char*, at entry it points to a
    4676                 :             :  * maxalign'd buffer, making it safe in most of this function to assume
    4677                 :             :  * that the pointed-to data is suitably aligned for direct access.
    4678                 :             :  */
    4679                 :             : static void
    4680                 :      178474 : ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
    4681                 :             :                            char *data)
    4682                 :             : {
    4683                 :             :     ReorderBufferDiskChange *ondisk;
    4684                 :             :     ReorderBufferChange *change;
    4685                 :             : 
    4686                 :      178474 :     ondisk = (ReorderBufferDiskChange *) data;
    4687                 :             : 
    4688                 :      178474 :     change = ReorderBufferAllocChange(rb);
    4689                 :             : 
    4690                 :             :     /* copy static part */
    4691                 :      178474 :     memcpy(change, &ondisk->change, sizeof(ReorderBufferChange));
    4692                 :             : 
    4693                 :      178474 :     data += sizeof(ReorderBufferDiskChange);
    4694                 :             : 
    4695                 :             :     /* restore individual stuff */
    4696   [ +  +  +  +  :      178474 :     switch (change->action)
                -  +  - ]
    4697                 :             :     {
    4698                 :             :             /* fall through these, they're all similar enough */
    4699                 :      176545 :         case REORDER_BUFFER_CHANGE_INSERT:
    4700                 :             :         case REORDER_BUFFER_CHANGE_UPDATE:
    4701                 :             :         case REORDER_BUFFER_CHANGE_DELETE:
    4702                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
    4703         [ +  + ]:      176545 :             if (change->data.tp.oldtuple)
    4704                 :             :             {
    4705                 :        5006 :                 uint32      tuplelen = ((HeapTuple) data)->t_len;
    4706                 :             : 
    4707                 :        5006 :                 change->data.tp.oldtuple =
    4708                 :        5006 :                     ReorderBufferAllocTupleBuf(rb, tuplelen - SizeofHeapTupleHeader);
    4709                 :             : 
    4710                 :             :                 /* restore ->tuple */
    4711                 :        5006 :                 memcpy(change->data.tp.oldtuple, data,
    4712                 :             :                        sizeof(HeapTupleData));
    4713                 :        5006 :                 data += sizeof(HeapTupleData);
    4714                 :             : 
    4715                 :             :                 /* reset t_data pointer into the new tuplebuf */
    4716                 :        5006 :                 change->data.tp.oldtuple->t_data =
    4717                 :        5006 :                     (HeapTupleHeader) ((char *) change->data.tp.oldtuple + HEAPTUPLESIZE);
    4718                 :             : 
    4719                 :             :                 /* restore tuple data itself */
    4720                 :        5006 :                 memcpy(change->data.tp.oldtuple->t_data, data, tuplelen);
    4721                 :        5006 :                 data += tuplelen;
    4722                 :             :             }
    4723                 :             : 
    4724         [ +  + ]:      176545 :             if (change->data.tp.newtuple)
    4725                 :             :             {
    4726                 :             :                 /* here, data might not be suitably aligned! */
    4727                 :             :                 uint32      tuplelen;
    4728                 :             : 
    4729                 :      166324 :                 memcpy(&tuplelen, data + offsetof(HeapTupleData, t_len),
    4730                 :             :                        sizeof(uint32));
    4731                 :             : 
    4732                 :      166324 :                 change->data.tp.newtuple =
    4733                 :      166324 :                     ReorderBufferAllocTupleBuf(rb, tuplelen - SizeofHeapTupleHeader);
    4734                 :             : 
    4735                 :             :                 /* restore ->tuple */
    4736                 :      166324 :                 memcpy(change->data.tp.newtuple, data,
    4737                 :             :                        sizeof(HeapTupleData));
    4738                 :      166324 :                 data += sizeof(HeapTupleData);
    4739                 :             : 
    4740                 :             :                 /* reset t_data pointer into the new tuplebuf */
    4741                 :      166324 :                 change->data.tp.newtuple->t_data =
    4742                 :      166324 :                     (HeapTupleHeader) ((char *) change->data.tp.newtuple + HEAPTUPLESIZE);
    4743                 :             : 
    4744                 :             :                 /* restore tuple data itself */
    4745                 :      166324 :                 memcpy(change->data.tp.newtuple->t_data, data, tuplelen);
    4746                 :      166324 :                 data += tuplelen;
    4747                 :             :             }
    4748                 :             : 
    4749                 :      176545 :             break;
    4750                 :           1 :         case REORDER_BUFFER_CHANGE_MESSAGE:
    4751                 :             :             {
    4752                 :             :                 Size        prefix_size;
    4753                 :             : 
    4754                 :             :                 /* read prefix */
    4755                 :           1 :                 memcpy(&prefix_size, data, sizeof(Size));
    4756                 :           1 :                 data += sizeof(Size);
    4757                 :           1 :                 change->data.msg.prefix = MemoryContextAlloc(rb->context,
    4758                 :             :                                                              prefix_size);
    4759                 :           1 :                 memcpy(change->data.msg.prefix, data, prefix_size);
    4760                 :             :                 Assert(change->data.msg.prefix[prefix_size - 1] == '\0');
    4761                 :           1 :                 data += prefix_size;
    4762                 :             : 
    4763                 :             :                 /* read the message */
    4764                 :           1 :                 memcpy(&change->data.msg.message_size, data, sizeof(Size));
    4765                 :           1 :                 data += sizeof(Size);
    4766                 :           1 :                 change->data.msg.message = MemoryContextAlloc(rb->context,
    4767                 :             :                                                               change->data.msg.message_size);
    4768                 :           1 :                 memcpy(change->data.msg.message, data,
    4769                 :             :                        change->data.msg.message_size);
    4770                 :           1 :                 data += change->data.msg.message_size;
    4771                 :             : 
    4772                 :           1 :                 break;
    4773                 :             :             }
    4774                 :          23 :         case REORDER_BUFFER_CHANGE_INVALIDATION:
    4775                 :             :             {
    4776                 :          23 :                 Size        inval_size = sizeof(SharedInvalidationMessage) *
    4777                 :          23 :                     change->data.inval.ninvalidations;
    4778                 :             : 
    4779                 :          23 :                 change->data.inval.invalidations =
    4780                 :          23 :                     MemoryContextAlloc(rb->context, inval_size);
    4781                 :             : 
    4782                 :             :                 /* read the message */
    4783                 :          23 :                 memcpy(change->data.inval.invalidations, data, inval_size);
    4784                 :             : 
    4785                 :          23 :                 break;
    4786                 :             :             }
    4787                 :           2 :         case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
    4788                 :             :             {
    4789                 :             :                 Snapshot    oldsnap;
    4790                 :             :                 Snapshot    newsnap;
    4791                 :             :                 Size        size;
    4792                 :             : 
    4793                 :           2 :                 oldsnap = (Snapshot) data;
    4794                 :             : 
    4795                 :           2 :                 size = sizeof(SnapshotData) +
    4796                 :           2 :                     sizeof(TransactionId) * oldsnap->xcnt +
    4797                 :           2 :                     sizeof(TransactionId) * (oldsnap->subxcnt + 0);
    4798                 :             : 
    4799                 :           2 :                 change->data.snapshot = MemoryContextAllocZero(rb->context, size);
    4800                 :             : 
    4801                 :           2 :                 newsnap = change->data.snapshot;
    4802                 :             : 
    4803                 :           2 :                 memcpy(newsnap, data, size);
    4804                 :           2 :                 newsnap->xip = (TransactionId *)
    4805                 :             :                     (((char *) newsnap) + sizeof(SnapshotData));
    4806                 :           2 :                 newsnap->subxip = newsnap->xip + newsnap->xcnt;
    4807                 :           2 :                 newsnap->copied = true;
    4808                 :           2 :                 break;
    4809                 :             :             }
    4810                 :             :             /* the base struct contains all the data, easy peasy */
    4811                 :           0 :         case REORDER_BUFFER_CHANGE_TRUNCATE:
    4812                 :             :             {
    4813                 :             :                 Oid        *relids;
    4814                 :             : 
    4815                 :           0 :                 relids = ReorderBufferAllocRelids(rb, change->data.truncate.nrelids);
    4816                 :           0 :                 memcpy(relids, data, change->data.truncate.nrelids * sizeof(Oid));
    4817                 :           0 :                 change->data.truncate.relids = relids;
    4818                 :             : 
    4819                 :           0 :                 break;
    4820                 :             :             }
    4821                 :        1903 :         case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
    4822                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT:
    4823                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
    4824                 :             :         case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
    4825                 :        1903 :             break;
    4826                 :             :     }
    4827                 :             : 
    4828                 :      178474 :     dlist_push_tail(&txn->changes, &change->node);
    4829                 :      178474 :     txn->nentries_mem++;
    4830                 :             : 
    4831                 :             :     /*
    4832                 :             :      * Update memory accounting for the restored change.  We need to do this
    4833                 :             :      * although we don't check the memory limit when restoring the changes in
    4834                 :             :      * this branch (we only do that when initially queueing the changes after
    4835                 :             :      * decoding), because we will release the changes later, and that will
    4836                 :             :      * update the accounting too (subtracting the size from the counters). And
    4837                 :             :      * we don't want to underflow there.
    4838                 :             :      */
    4839                 :      178474 :     ReorderBufferChangeMemoryUpdate(rb, change, NULL, true,
    4840                 :             :                                     ReorderBufferChangeSize(change));
    4841                 :      178474 : }
    4842                 :             : 
    4843                 :             : /*
    4844                 :             :  * Remove all on-disk stored for the passed in transaction.
    4845                 :             :  */
    4846                 :             : static void
    4847                 :         309 : ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn)
    4848                 :             : {
    4849                 :             :     XLogSegNo   first;
    4850                 :             :     XLogSegNo   cur;
    4851                 :             :     XLogSegNo   last;
    4852                 :             : 
    4853                 :             :     Assert(XLogRecPtrIsValid(txn->first_lsn));
    4854                 :             :     Assert(XLogRecPtrIsValid(txn->final_lsn));
    4855                 :             : 
    4856                 :         309 :     XLByteToSeg(txn->first_lsn, first, wal_segment_size);
    4857                 :         309 :     XLByteToSeg(txn->final_lsn, last, wal_segment_size);
    4858                 :             : 
    4859                 :             :     /* iterate over all possible filenames, and delete them */
    4860         [ +  + ]:         636 :     for (cur = first; cur <= last; cur++)
    4861                 :             :     {
    4862                 :             :         char        path[MAXPGPATH];
    4863                 :             : 
    4864                 :         327 :         ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid, cur);
    4865   [ -  +  -  - ]:         327 :         if (unlink(path) != 0 && errno != ENOENT)
    4866         [ #  # ]:           0 :             ereport(ERROR,
    4867                 :             :                     (errcode_for_file_access(),
    4868                 :             :                      errmsg("could not remove file \"%s\": %m", path)));
    4869                 :             :     }
    4870                 :         309 : }
    4871                 :             : 
    4872                 :             : /*
    4873                 :             :  * Remove any leftover serialized reorder buffers from a slot directory after a
    4874                 :             :  * prior crash or decoding session exit.
    4875                 :             :  */
    4876                 :             : static void
    4877                 :        2313 : ReorderBufferCleanupSerializedTXNs(const char *slotname)
    4878                 :             : {
    4879                 :             :     DIR        *spill_dir;
    4880                 :             :     struct dirent *spill_de;
    4881                 :             :     struct stat statbuf;
    4882                 :             :     char        path[MAXPGPATH * 2 + sizeof(PG_REPLSLOT_DIR)];
    4883                 :             : 
    4884                 :        2313 :     sprintf(path, "%s/%s", PG_REPLSLOT_DIR, slotname);
    4885                 :             : 
    4886                 :             :     /* we're only handling directories here, skip if it's not ours */
    4887   [ +  -  -  + ]:        2313 :     if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
    4888                 :           0 :         return;
    4889                 :             : 
    4890                 :        2313 :     spill_dir = AllocateDir(path);
    4891         [ +  + ]:       11565 :     while ((spill_de = ReadDirExtended(spill_dir, path, INFO)) != NULL)
    4892                 :             :     {
    4893                 :             :         /* only look at names that can be ours */
    4894         [ -  + ]:        6939 :         if (strncmp(spill_de->d_name, "xid", 3) == 0)
    4895                 :             :         {
    4896                 :           0 :             snprintf(path, sizeof(path),
    4897                 :             :                      "%s/%s/%s", PG_REPLSLOT_DIR, slotname,
    4898                 :           0 :                      spill_de->d_name);
    4899                 :             : 
    4900         [ #  # ]:           0 :             if (unlink(path) != 0)
    4901         [ #  # ]:           0 :                 ereport(ERROR,
    4902                 :             :                         (errcode_for_file_access(),
    4903                 :             :                          errmsg("could not remove file \"%s\" during removal of %s/%s/xid*: %m",
    4904                 :             :                                 path, PG_REPLSLOT_DIR, slotname)));
    4905                 :             :         }
    4906                 :             :     }
    4907                 :        2313 :     FreeDir(spill_dir);
    4908                 :             : }
    4909                 :             : 
    4910                 :             : /*
    4911                 :             :  * Given a replication slot, transaction ID and segment number, fill in the
    4912                 :             :  * corresponding spill file into 'path', which is a caller-owned buffer of size
    4913                 :             :  * at least MAXPGPATH.
    4914                 :             :  */
    4915                 :             : static void
    4916                 :        4352 : ReorderBufferSerializedPath(char *path, ReplicationSlot *slot, TransactionId xid,
    4917                 :             :                             XLogSegNo segno)
    4918                 :             : {
    4919                 :             :     XLogRecPtr  recptr;
    4920                 :             : 
    4921                 :        4352 :     XLogSegNoOffsetToRecPtr(segno, 0, wal_segment_size, recptr);
    4922                 :             : 
    4923                 :        4352 :     snprintf(path, MAXPGPATH, "%s/%s/xid-%u-lsn-%X-%X.spill",
    4924                 :             :              PG_REPLSLOT_DIR,
    4925                 :        4352 :              NameStr(MyReplicationSlot->data.name),
    4926                 :        4352 :              xid, LSN_FORMAT_ARGS(recptr));
    4927                 :        4352 : }
    4928                 :             : 
    4929                 :             : /*
    4930                 :             :  * Delete all data spilled to disk after we've restarted/crashed. It will be
    4931                 :             :  * recreated when the respective slots are reused.
    4932                 :             :  */
    4933                 :             : void
    4934                 :        1088 : StartupReorderBuffer(void)
    4935                 :             : {
    4936                 :             :     DIR        *logical_dir;
    4937                 :             :     struct dirent *logical_de;
    4938                 :             : 
    4939                 :        1088 :     logical_dir = AllocateDir(PG_REPLSLOT_DIR);
    4940         [ +  + ]:        3391 :     while ((logical_de = ReadDir(logical_dir, PG_REPLSLOT_DIR)) != NULL)
    4941                 :             :     {
    4942         [ +  + ]:        2303 :         if (strcmp(logical_de->d_name, ".") == 0 ||
    4943         [ +  + ]:        1215 :             strcmp(logical_de->d_name, "..") == 0)
    4944                 :        2176 :             continue;
    4945                 :             : 
    4946                 :             :         /* if it cannot be a slot, skip the directory */
    4947         [ -  + ]:         127 :         if (!ReplicationSlotValidateName(logical_de->d_name, true, DEBUG2))
    4948                 :           0 :             continue;
    4949                 :             : 
    4950                 :             :         /*
    4951                 :             :          * ok, has to be a surviving logical slot, iterate and delete
    4952                 :             :          * everything starting with xid-*
    4953                 :             :          */
    4954                 :         127 :         ReorderBufferCleanupSerializedTXNs(logical_de->d_name);
    4955                 :             :     }
    4956                 :        1088 :     FreeDir(logical_dir);
    4957                 :        1088 : }
    4958                 :             : 
    4959                 :             : /* ---------------------------------------
    4960                 :             :  * toast reassembly support
    4961                 :             :  * ---------------------------------------
    4962                 :             :  */
    4963                 :             : 
    4964                 :             : /*
    4965                 :             :  * Initialize per tuple toast reconstruction support.
    4966                 :             :  */
    4967                 :             : static void
    4968                 :          44 : ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
    4969                 :             : {
    4970                 :             :     HASHCTL     hash_ctl;
    4971                 :             : 
    4972                 :             :     Assert(txn->toast_hash == NULL);
    4973                 :             : 
    4974                 :          44 :     hash_ctl.keysize = sizeof(Oid);
    4975                 :          44 :     hash_ctl.entrysize = sizeof(ReorderBufferToastEnt);
    4976                 :          44 :     hash_ctl.hcxt = rb->context;
    4977                 :          44 :     txn->toast_hash = hash_create("ReorderBufferToastHash", 5, &hash_ctl,
    4978                 :             :                                   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
    4979                 :          44 : }
    4980                 :             : 
    4981                 :             : /*
    4982                 :             :  * Per toast-chunk handling for toast reconstruction
    4983                 :             :  *
    4984                 :             :  * Appends a toast chunk so we can reconstruct it when the tuple "owning" the
    4985                 :             :  * toasted Datum comes along.
    4986                 :             :  */
    4987                 :             : static void
    4988                 :        1847 : ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn,
    4989                 :             :                               Relation relation, ReorderBufferChange *change)
    4990                 :             : {
    4991                 :             :     ReorderBufferToastEnt *ent;
    4992                 :             :     HeapTuple   newtup;
    4993                 :             :     bool        found;
    4994                 :             :     int32       chunksize;
    4995                 :             :     bool        isnull;
    4996                 :             :     Pointer     chunk;
    4997                 :        1847 :     TupleDesc   desc = RelationGetDescr(relation);
    4998                 :             :     Oid         chunk_id;
    4999                 :             :     int32       chunk_seq;
    5000                 :             : 
    5001         [ +  + ]:        1847 :     if (txn->toast_hash == NULL)
    5002                 :          44 :         ReorderBufferToastInitHash(rb, txn);
    5003                 :             : 
    5004                 :             :     Assert(IsToastRelation(relation));
    5005                 :             : 
    5006                 :        1847 :     newtup = change->data.tp.newtuple;
    5007                 :        1847 :     chunk_id = DatumGetObjectId(fastgetattr(newtup, 1, desc, &isnull));
    5008                 :             :     Assert(!isnull);
    5009                 :        1847 :     chunk_seq = DatumGetInt32(fastgetattr(newtup, 2, desc, &isnull));
    5010                 :             :     Assert(!isnull);
    5011                 :             : 
    5012                 :             :     ent = (ReorderBufferToastEnt *)
    5013                 :        1847 :         hash_search(txn->toast_hash, &chunk_id, HASH_ENTER, &found);
    5014                 :             : 
    5015         [ +  + ]:        1847 :     if (!found)
    5016                 :             :     {
    5017                 :             :         Assert(ent->chunk_id == chunk_id);
    5018                 :          62 :         ent->num_chunks = 0;
    5019                 :          62 :         ent->last_chunk_seq = 0;
    5020                 :          62 :         ent->size = 0;
    5021                 :          62 :         ent->reconstructed = NULL;
    5022                 :          62 :         dlist_init(&ent->chunks);
    5023                 :             : 
    5024         [ -  + ]:          62 :         if (chunk_seq != 0)
    5025         [ #  # ]:           0 :             elog(ERROR, "got sequence entry %d for toast chunk %u instead of seq 0",
    5026                 :             :                  chunk_seq, chunk_id);
    5027                 :             :     }
    5028   [ +  -  -  + ]:        1785 :     else if (found && chunk_seq != ent->last_chunk_seq + 1)
    5029         [ #  # ]:           0 :         elog(ERROR, "got sequence entry %d for toast chunk %u instead of seq %d",
    5030                 :             :              chunk_seq, chunk_id, ent->last_chunk_seq + 1);
    5031                 :             : 
    5032                 :        1847 :     chunk = DatumGetPointer(fastgetattr(newtup, 3, desc, &isnull));
    5033                 :             :     Assert(!isnull);
    5034                 :             : 
    5035                 :             :     /* calculate size so we can allocate the right size at once later */
    5036         [ +  - ]:        1847 :     if (!VARATT_IS_EXTENDED(chunk))
    5037                 :        1847 :         chunksize = VARSIZE(chunk) - VARHDRSZ;
    5038         [ #  # ]:           0 :     else if (VARATT_IS_SHORT(chunk))
    5039                 :             :         /* could happen due to heap_form_tuple doing its thing */
    5040                 :           0 :         chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
    5041                 :             :     else
    5042         [ #  # ]:           0 :         elog(ERROR, "unexpected type of toast chunk");
    5043                 :             : 
    5044                 :        1847 :     ent->size += chunksize;
    5045                 :        1847 :     ent->last_chunk_seq = chunk_seq;
    5046                 :        1847 :     ent->num_chunks++;
    5047                 :        1847 :     dlist_push_tail(&ent->chunks, &change->node);
    5048                 :        1847 : }
    5049                 :             : 
    5050                 :             : /*
    5051                 :             :  * Rejigger change->newtuple to point to in-memory toast tuples instead of
    5052                 :             :  * on-disk toast tuples that may no longer exist (think DROP TABLE or VACUUM).
    5053                 :             :  *
    5054                 :             :  * We cannot replace unchanged toast tuples though, so those will still point
    5055                 :             :  * to on-disk toast data.
    5056                 :             :  *
    5057                 :             :  * While updating the existing change with detoasted tuple data, we need to
    5058                 :             :  * update the memory accounting info, because the change size will differ.
    5059                 :             :  * Otherwise the accounting may get out of sync, triggering serialization
    5060                 :             :  * at unexpected times.
    5061                 :             :  *
    5062                 :             :  * We simply subtract size of the change before rejiggering the tuple, and
    5063                 :             :  * then add the new size. This makes it look like the change was removed
    5064                 :             :  * and then added back, except it only tweaks the accounting info.
    5065                 :             :  *
    5066                 :             :  * In particular it can't trigger serialization, which would be pointless
    5067                 :             :  * anyway as it happens during commit processing right before handing
    5068                 :             :  * the change to the output plugin.
    5069                 :             :  */
    5070                 :             : static void
    5071                 :      358290 : ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
    5072                 :             :                           Relation relation, ReorderBufferChange *change)
    5073                 :             : {
    5074                 :             :     TupleDesc   desc;
    5075                 :             :     int         natt;
    5076                 :             :     Datum      *attrs;
    5077                 :             :     bool       *isnull;
    5078                 :             :     bool       *free;
    5079                 :             :     HeapTuple   tmphtup;
    5080                 :             :     Relation    toast_rel;
    5081                 :             :     TupleDesc   toast_desc;
    5082                 :             :     MemoryContext oldcontext;
    5083                 :             :     HeapTuple   newtup;
    5084                 :             :     Size        old_size;
    5085                 :             : 
    5086                 :             :     /* no toast tuples changed */
    5087         [ +  + ]:      358290 :     if (txn->toast_hash == NULL)
    5088                 :      358035 :         return;
    5089                 :             : 
    5090                 :             :     /*
    5091                 :             :      * We're going to modify the size of the change. So, to make sure the
    5092                 :             :      * accounting is correct we record the current change size and then after
    5093                 :             :      * re-computing the change we'll subtract the recorded size and then
    5094                 :             :      * re-add the new change size at the end. We don't immediately subtract
    5095                 :             :      * the old size because if there is any error before we add the new size,
    5096                 :             :      * we will release the changes and that will update the accounting info
    5097                 :             :      * (subtracting the size from the counters). And we don't want to
    5098                 :             :      * underflow there.
    5099                 :             :      */
    5100                 :         255 :     old_size = ReorderBufferChangeSize(change);
    5101                 :             : 
    5102                 :         255 :     oldcontext = MemoryContextSwitchTo(rb->context);
    5103                 :             : 
    5104                 :             :     /* we should only have toast tuples in an INSERT or UPDATE */
    5105                 :             :     Assert(change->data.tp.newtuple);
    5106                 :             : 
    5107                 :         255 :     desc = RelationGetDescr(relation);
    5108                 :             : 
    5109                 :         255 :     toast_rel = RelationIdGetRelation(relation->rd_rel->reltoastrelid);
    5110         [ -  + ]:         255 :     if (!RelationIsValid(toast_rel))
    5111         [ #  # ]:           0 :         elog(ERROR, "could not open toast relation with OID %u (base relation \"%s\")",
    5112                 :             :              relation->rd_rel->reltoastrelid, RelationGetRelationName(relation));
    5113                 :             : 
    5114                 :         255 :     toast_desc = RelationGetDescr(toast_rel);
    5115                 :             : 
    5116                 :             :     /* should we allocate from stack instead? */
    5117                 :         255 :     attrs = palloc0_array(Datum, desc->natts);
    5118                 :         255 :     isnull = palloc0_array(bool, desc->natts);
    5119                 :         255 :     free = palloc0_array(bool, desc->natts);
    5120                 :             : 
    5121                 :         255 :     newtup = change->data.tp.newtuple;
    5122                 :             : 
    5123                 :         255 :     heap_deform_tuple(newtup, desc, attrs, isnull);
    5124                 :             : 
    5125         [ +  + ]:         815 :     for (natt = 0; natt < desc->natts; natt++)
    5126                 :             :     {
    5127                 :         560 :         CompactAttribute *attr = TupleDescCompactAttr(desc, natt);
    5128                 :             :         ReorderBufferToastEnt *ent;
    5129                 :             :         varlena    *varlena_pointer;
    5130                 :             : 
    5131                 :             :         /* va_rawsize is the size of the original datum -- including header */
    5132                 :             :         varatt_external toast_pointer;
    5133                 :             :         varatt_indirect redirect_pointer;
    5134                 :         560 :         varlena    *new_datum = NULL;
    5135                 :             :         varlena    *reconstructed;
    5136                 :             :         dlist_iter  it;
    5137                 :         560 :         Size        data_done = 0;
    5138                 :             : 
    5139         [ +  + ]:         560 :         if (attr->attisdropped)
    5140                 :         499 :             continue;
    5141                 :             : 
    5142                 :             :         /* not a varlena datatype */
    5143         [ +  + ]:         538 :         if (attr->attlen != -1)
    5144                 :         249 :             continue;
    5145                 :             : 
    5146                 :             :         /* no data */
    5147         [ +  + ]:         289 :         if (isnull[natt])
    5148                 :          12 :             continue;
    5149                 :             : 
    5150                 :             :         /* ok, we know we have a toast datum */
    5151                 :         277 :         varlena_pointer = (varlena *) DatumGetPointer(attrs[natt]);
    5152                 :             : 
    5153                 :             :         /* no need to do anything if the tuple isn't external */
    5154         [ +  + ]:         277 :         if (!VARATT_IS_EXTERNAL(varlena_pointer))
    5155                 :         208 :             continue;
    5156                 :             : 
    5157                 :          69 :         VARATT_EXTERNAL_GET_POINTER(toast_pointer, varlena_pointer);
    5158                 :             : 
    5159                 :             :         /*
    5160                 :             :          * Check whether the toast tuple changed, replace if so.
    5161                 :             :          */
    5162                 :             :         ent = (ReorderBufferToastEnt *)
    5163                 :          69 :             hash_search(txn->toast_hash,
    5164                 :             :                         &toast_pointer.va_valueid,
    5165                 :             :                         HASH_FIND,
    5166                 :             :                         NULL);
    5167         [ +  + ]:          69 :         if (ent == NULL)
    5168                 :           8 :             continue;
    5169                 :             : 
    5170                 :             :         new_datum =
    5171                 :          61 :             (varlena *) palloc0(INDIRECT_POINTER_SIZE);
    5172                 :             : 
    5173                 :          61 :         free[natt] = true;
    5174                 :             : 
    5175                 :          61 :         reconstructed = palloc0(toast_pointer.va_rawsize);
    5176                 :             : 
    5177                 :          61 :         ent->reconstructed = reconstructed;
    5178                 :             : 
    5179                 :             :         /* stitch toast tuple back together from its parts */
    5180   [ +  -  +  + ]:        1857 :         dlist_foreach(it, &ent->chunks)
    5181                 :             :         {
    5182                 :             :             bool        cisnull;
    5183                 :             :             ReorderBufferChange *cchange;
    5184                 :             :             HeapTuple   ctup;
    5185                 :             :             Pointer     chunk;
    5186                 :             : 
    5187                 :        1796 :             cchange = dlist_container(ReorderBufferChange, node, it.cur);
    5188                 :        1796 :             ctup = cchange->data.tp.newtuple;
    5189                 :        1796 :             chunk = DatumGetPointer(fastgetattr(ctup, 3, toast_desc, &cisnull));
    5190                 :             : 
    5191                 :             :             Assert(!cisnull);
    5192                 :             :             Assert(!VARATT_IS_EXTERNAL(chunk));
    5193                 :             :             Assert(!VARATT_IS_SHORT(chunk));
    5194                 :             : 
    5195                 :        1796 :             memcpy(VARDATA(reconstructed) + data_done,
    5196                 :        1796 :                    VARDATA(chunk),
    5197                 :        1796 :                    VARSIZE(chunk) - VARHDRSZ);
    5198                 :        1796 :             data_done += VARSIZE(chunk) - VARHDRSZ;
    5199                 :             :         }
    5200                 :             :         Assert(data_done == VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer));
    5201                 :             : 
    5202                 :             :         /* make sure its marked as compressed or not */
    5203         [ +  + ]:          61 :         if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
    5204                 :          16 :             SET_VARSIZE_COMPRESSED(reconstructed, data_done + VARHDRSZ);
    5205                 :             :         else
    5206                 :          45 :             SET_VARSIZE(reconstructed, data_done + VARHDRSZ);
    5207                 :             : 
    5208                 :          61 :         memset(&redirect_pointer, 0, sizeof(redirect_pointer));
    5209                 :          61 :         redirect_pointer.pointer = reconstructed;
    5210                 :             : 
    5211                 :          61 :         SET_VARTAG_EXTERNAL(new_datum, VARTAG_INDIRECT);
    5212                 :          61 :         memcpy(VARDATA_EXTERNAL(new_datum), &redirect_pointer,
    5213                 :             :                sizeof(redirect_pointer));
    5214                 :             : 
    5215                 :          61 :         attrs[natt] = PointerGetDatum(new_datum);
    5216                 :             :     }
    5217                 :             : 
    5218                 :             :     /*
    5219                 :             :      * Build tuple in separate memory & copy tuple back into the tuplebuf
    5220                 :             :      * passed to the output plugin. We can't directly heap_fill_tuple() into
    5221                 :             :      * the tuplebuf because attrs[] will point back into the current content.
    5222                 :             :      */
    5223                 :         255 :     tmphtup = heap_form_tuple(desc, attrs, isnull);
    5224                 :             :     Assert(newtup->t_len <= MaxHeapTupleSize);
    5225                 :             :     Assert(newtup->t_data == (HeapTupleHeader) ((char *) newtup + HEAPTUPLESIZE));
    5226                 :             : 
    5227                 :         255 :     memcpy(newtup->t_data, tmphtup->t_data, tmphtup->t_len);
    5228                 :         255 :     newtup->t_len = tmphtup->t_len;
    5229                 :             : 
    5230                 :             :     /*
    5231                 :             :      * free resources we won't further need, more persistent stuff will be
    5232                 :             :      * free'd in ReorderBufferToastReset().
    5233                 :             :      */
    5234                 :         255 :     RelationClose(toast_rel);
    5235                 :         255 :     pfree(tmphtup);
    5236         [ +  + ]:         815 :     for (natt = 0; natt < desc->natts; natt++)
    5237                 :             :     {
    5238         [ +  + ]:         560 :         if (free[natt])
    5239                 :          61 :             pfree(DatumGetPointer(attrs[natt]));
    5240                 :             :     }
    5241                 :         255 :     pfree(attrs);
    5242                 :         255 :     pfree(free);
    5243                 :         255 :     pfree(isnull);
    5244                 :             : 
    5245                 :         255 :     MemoryContextSwitchTo(oldcontext);
    5246                 :             : 
    5247                 :             :     /* subtract the old change size */
    5248                 :         255 :     ReorderBufferChangeMemoryUpdate(rb, change, NULL, false, old_size);
    5249                 :             :     /* now add the change back, with the correct size */
    5250                 :         255 :     ReorderBufferChangeMemoryUpdate(rb, change, NULL, true,
    5251                 :             :                                     ReorderBufferChangeSize(change));
    5252                 :             : }
    5253                 :             : 
    5254                 :             : /*
    5255                 :             :  * Free all resources allocated for toast reconstruction.
    5256                 :             :  */
    5257                 :             : static void
    5258                 :      362881 : ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn)
    5259                 :             : {
    5260                 :             :     HASH_SEQ_STATUS hstat;
    5261                 :             :     ReorderBufferToastEnt *ent;
    5262                 :             : 
    5263         [ +  + ]:      362881 :     if (txn->toast_hash == NULL)
    5264                 :      362837 :         return;
    5265                 :             : 
    5266                 :             :     /* sequentially walk over the hash and free everything */
    5267                 :          44 :     hash_seq_init(&hstat, txn->toast_hash);
    5268         [ +  + ]:         106 :     while ((ent = (ReorderBufferToastEnt *) hash_seq_search(&hstat)) != NULL)
    5269                 :             :     {
    5270                 :             :         dlist_mutable_iter it;
    5271                 :             : 
    5272         [ +  + ]:          62 :         if (ent->reconstructed != NULL)
    5273                 :          61 :             pfree(ent->reconstructed);
    5274                 :             : 
    5275   [ +  -  +  + ]:        1909 :         dlist_foreach_modify(it, &ent->chunks)
    5276                 :             :         {
    5277                 :        1847 :             ReorderBufferChange *change =
    5278                 :        1847 :                 dlist_container(ReorderBufferChange, node, it.cur);
    5279                 :             : 
    5280                 :        1847 :             dlist_delete(&change->node);
    5281                 :        1847 :             ReorderBufferFreeChange(rb, change, true);
    5282                 :             :         }
    5283                 :             :     }
    5284                 :             : 
    5285                 :          44 :     hash_destroy(txn->toast_hash);
    5286                 :          44 :     txn->toast_hash = NULL;
    5287                 :             : }
    5288                 :             : 
    5289                 :             : 
    5290                 :             : /* ---------------------------------------
    5291                 :             :  * Visibility support for logical decoding
    5292                 :             :  *
    5293                 :             :  *
    5294                 :             :  * Lookup actual cmin/cmax values when using decoding snapshot. We can't
    5295                 :             :  * always rely on stored cmin/cmax values because of two scenarios:
    5296                 :             :  *
    5297                 :             :  * * A tuple got changed multiple times during a single transaction and thus
    5298                 :             :  *   has got a combo CID. Combo CIDs are only valid for the duration of a
    5299                 :             :  *   single transaction.
    5300                 :             :  * * A tuple with a cmin but no cmax (and thus no combo CID) got
    5301                 :             :  *   deleted/updated in another transaction than the one which created it
    5302                 :             :  *   which we are looking at right now. As only one of cmin, cmax or combo CID
    5303                 :             :  *   is actually stored in the heap we don't have access to the value we
    5304                 :             :  *   need anymore.
    5305                 :             :  *
    5306                 :             :  * To resolve those problems we have a per-transaction hash of (cmin,
    5307                 :             :  * cmax) tuples keyed by (relfilelocator, ctid) which contains the actual
    5308                 :             :  * (cmin, cmax) values. That also takes care of combo CIDs by simply
    5309                 :             :  * not caring about them at all. As we have the real cmin/cmax values
    5310                 :             :  * combo CIDs aren't interesting.
    5311                 :             :  *
    5312                 :             :  * As we only care about catalog tuples here the overhead of this
    5313                 :             :  * hashtable should be acceptable.
    5314                 :             :  *
    5315                 :             :  * Heap rewrites complicate this a bit, check rewriteheap.c for
    5316                 :             :  * details.
    5317                 :             :  * -------------------------------------------------------------------------
    5318                 :             :  */
    5319                 :             : 
    5320                 :             : /* struct for sorting mapping files by LSN efficiently */
    5321                 :             : typedef struct RewriteMappingFile
    5322                 :             : {
    5323                 :             :     XLogRecPtr  lsn;
    5324                 :             :     char        fname[MAXPGPATH];
    5325                 :             : } RewriteMappingFile;
    5326                 :             : 
    5327                 :             : #ifdef NOT_USED
    5328                 :             : static void
    5329                 :             : DisplayMapping(HTAB *tuplecid_data)
    5330                 :             : {
    5331                 :             :     HASH_SEQ_STATUS hstat;
    5332                 :             :     ReorderBufferTupleCidEnt *ent;
    5333                 :             : 
    5334                 :             :     hash_seq_init(&hstat, tuplecid_data);
    5335                 :             :     while ((ent = (ReorderBufferTupleCidEnt *) hash_seq_search(&hstat)) != NULL)
    5336                 :             :     {
    5337                 :             :         elog(DEBUG3, "mapping: node: %u/%u/%u tid: %u/%u cmin: %u, cmax: %u",
    5338                 :             :              ent->key.rlocator.dbOid,
    5339                 :             :              ent->key.rlocator.spcOid,
    5340                 :             :              ent->key.rlocator.relNumber,
    5341                 :             :              ItemPointerGetBlockNumber(&ent->key.tid),
    5342                 :             :              ItemPointerGetOffsetNumber(&ent->key.tid),
    5343                 :             :              ent->cmin,
    5344                 :             :              ent->cmax
    5345                 :             :             );
    5346                 :             :     }
    5347                 :             : }
    5348                 :             : #endif
    5349                 :             : 
    5350                 :             : /*
    5351                 :             :  * Apply a single mapping file to tuplecid_data.
    5352                 :             :  *
    5353                 :             :  * The mapping file has to have been verified to be a) committed b) for our
    5354                 :             :  * transaction c) applied in LSN order.
    5355                 :             :  */
    5356                 :             : static void
    5357                 :          27 : ApplyLogicalMappingFile(HTAB *tuplecid_data, const char *fname)
    5358                 :             : {
    5359                 :             :     char        path[MAXPGPATH];
    5360                 :             :     int         fd;
    5361                 :             :     ssize_t     readBytes;
    5362                 :             :     LogicalRewriteMappingData map;
    5363                 :             : 
    5364                 :          27 :     sprintf(path, "%s/%s", PG_LOGICAL_MAPPINGS_DIR, fname);
    5365                 :          27 :     fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
    5366         [ -  + ]:          27 :     if (fd < 0)
    5367         [ #  # ]:           0 :         ereport(ERROR,
    5368                 :             :                 (errcode_for_file_access(),
    5369                 :             :                  errmsg("could not open file \"%s\": %m", path)));
    5370                 :             : 
    5371                 :             :     while (true)
    5372                 :         209 :     {
    5373                 :             :         ReorderBufferTupleCidKey key;
    5374                 :             :         ReorderBufferTupleCidEnt *ent;
    5375                 :             :         ReorderBufferTupleCidEnt *new_ent;
    5376                 :             :         bool        found;
    5377                 :             : 
    5378                 :             :         /* be careful about padding */
    5379                 :         236 :         memset(&key, 0, sizeof(ReorderBufferTupleCidKey));
    5380                 :             : 
    5381                 :             :         /* read all mappings till the end of the file */
    5382                 :         236 :         pgstat_report_wait_start(WAIT_EVENT_REORDER_LOGICAL_MAPPING_READ);
    5383                 :         236 :         readBytes = read(fd, &map, sizeof(LogicalRewriteMappingData));
    5384                 :         236 :         pgstat_report_wait_end();
    5385                 :             : 
    5386         [ -  + ]:         236 :         if (readBytes < 0)
    5387         [ #  # ]:           0 :             ereport(ERROR,
    5388                 :             :                     (errcode_for_file_access(),
    5389                 :             :                      errmsg("could not read file \"%s\": %m",
    5390                 :             :                             path)));
    5391         [ +  + ]:         236 :         else if (readBytes == 0)    /* EOF */
    5392                 :          27 :             break;
    5393         [ -  + ]:         209 :         else if (readBytes != sizeof(LogicalRewriteMappingData))
    5394         [ #  # ]:           0 :             ereport(ERROR,
    5395                 :             :                     (errcode_for_file_access(),
    5396                 :             :                      errmsg("could not read from file \"%s\": read %zd of %zu",
    5397                 :             :                             path, readBytes,
    5398                 :             :                             sizeof(LogicalRewriteMappingData))));
    5399                 :             : 
    5400                 :         209 :         key.rlocator = map.old_locator;
    5401                 :         209 :         ItemPointerCopy(&map.old_tid,
    5402                 :             :                         &key.tid);
    5403                 :             : 
    5404                 :             : 
    5405                 :             :         ent = (ReorderBufferTupleCidEnt *)
    5406                 :         209 :             hash_search(tuplecid_data, &key, HASH_FIND, NULL);
    5407                 :             : 
    5408                 :             :         /* no existing mapping, no need to update */
    5409         [ -  + ]:         209 :         if (!ent)
    5410                 :           0 :             continue;
    5411                 :             : 
    5412                 :         209 :         key.rlocator = map.new_locator;
    5413                 :         209 :         ItemPointerCopy(&map.new_tid,
    5414                 :             :                         &key.tid);
    5415                 :             : 
    5416                 :             :         new_ent = (ReorderBufferTupleCidEnt *)
    5417                 :         209 :             hash_search(tuplecid_data, &key, HASH_ENTER, &found);
    5418                 :             : 
    5419         [ +  + ]:         209 :         if (found)
    5420                 :             :         {
    5421                 :             :             /*
    5422                 :             :              * Make sure the existing mapping makes sense. We sometime update
    5423                 :             :              * old records that did not yet have a cmax (e.g. pg_class' own
    5424                 :             :              * entry while rewriting it) during rewrites, so allow that.
    5425                 :             :              */
    5426                 :             :             Assert(ent->cmin == InvalidCommandId || ent->cmin == new_ent->cmin);
    5427                 :             :             Assert(ent->cmax == InvalidCommandId || ent->cmax == new_ent->cmax);
    5428                 :             :         }
    5429                 :             :         else
    5430                 :             :         {
    5431                 :             :             /* update mapping */
    5432                 :         203 :             new_ent->cmin = ent->cmin;
    5433                 :         203 :             new_ent->cmax = ent->cmax;
    5434                 :         203 :             new_ent->combocid = ent->combocid;
    5435                 :             :         }
    5436                 :             :     }
    5437                 :             : 
    5438         [ -  + ]:          27 :     if (CloseTransientFile(fd) != 0)
    5439         [ #  # ]:           0 :         ereport(ERROR,
    5440                 :             :                 (errcode_for_file_access(),
    5441                 :             :                  errmsg("could not close file \"%s\": %m", path)));
    5442                 :          27 : }
    5443                 :             : 
    5444                 :             : 
    5445                 :             : /*
    5446                 :             :  * Check whether the TransactionId 'xid' is in the pre-sorted array 'xip'.
    5447                 :             :  */
    5448                 :             : static bool
    5449                 :         348 : TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
    5450                 :             : {
    5451                 :         348 :     return bsearch(&xid, xip, num,
    5452                 :         348 :                    sizeof(TransactionId), xidComparator) != NULL;
    5453                 :             : }
    5454                 :             : 
    5455                 :             : /*
    5456                 :             :  * list_sort() comparator for sorting RewriteMappingFiles in LSN order.
    5457                 :             :  */
    5458                 :             : static int
    5459                 :          37 : file_sort_by_lsn(const ListCell *a_p, const ListCell *b_p)
    5460                 :             : {
    5461                 :          37 :     RewriteMappingFile *a = (RewriteMappingFile *) lfirst(a_p);
    5462                 :          37 :     RewriteMappingFile *b = (RewriteMappingFile *) lfirst(b_p);
    5463                 :             : 
    5464                 :          37 :     return pg_cmp_u64(a->lsn, b->lsn);
    5465                 :             : }
    5466                 :             : 
    5467                 :             : /*
    5468                 :             :  * Apply any existing logical remapping files if there are any targeted at our
    5469                 :             :  * transaction for relid.
    5470                 :             :  */
    5471                 :             : static void
    5472                 :          11 : UpdateLogicalMappings(HTAB *tuplecid_data, Oid relid, Snapshot snapshot)
    5473                 :             : {
    5474                 :             :     DIR        *mapping_dir;
    5475                 :             :     struct dirent *mapping_de;
    5476                 :          11 :     List       *files = NIL;
    5477                 :             :     ListCell   *file;
    5478         [ +  - ]:          11 :     Oid         dboid = IsSharedRelation(relid) ? InvalidOid : MyDatabaseId;
    5479                 :             : 
    5480                 :          11 :     mapping_dir = AllocateDir(PG_LOGICAL_MAPPINGS_DIR);
    5481         [ +  + ]:         573 :     while ((mapping_de = ReadDir(mapping_dir, PG_LOGICAL_MAPPINGS_DIR)) != NULL)
    5482                 :             :     {
    5483                 :             :         Oid         f_dboid;
    5484                 :             :         Oid         f_relid;
    5485                 :             :         TransactionId f_mapped_xid;
    5486                 :             :         TransactionId f_create_xid;
    5487                 :             :         XLogRecPtr  f_lsn;
    5488                 :             :         uint32      f_hi,
    5489                 :             :                     f_lo;
    5490                 :             :         RewriteMappingFile *f;
    5491                 :             : 
    5492         [ +  + ]:         562 :         if (strcmp(mapping_de->d_name, ".") == 0 ||
    5493         [ +  + ]:         551 :             strcmp(mapping_de->d_name, "..") == 0)
    5494                 :         535 :             continue;
    5495                 :             : 
    5496                 :             :         /* Ignore files that aren't ours */
    5497         [ -  + ]:         540 :         if (strncmp(mapping_de->d_name, "map-", 4) != 0)
    5498                 :           0 :             continue;
    5499                 :             : 
    5500         [ -  + ]:         540 :         if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT,
    5501                 :             :                    &f_dboid, &f_relid, &f_hi, &f_lo,
    5502                 :             :                    &f_mapped_xid, &f_create_xid) != 6)
    5503         [ #  # ]:           0 :             elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name);
    5504                 :             : 
    5505                 :         540 :         f_lsn = ((uint64) f_hi) << 32 | f_lo;
    5506                 :             : 
    5507                 :             :         /* mapping for another database */
    5508         [ -  + ]:         540 :         if (f_dboid != dboid)
    5509                 :           0 :             continue;
    5510                 :             : 
    5511                 :             :         /* mapping for another relation */
    5512         [ +  + ]:         540 :         if (f_relid != relid)
    5513                 :          60 :             continue;
    5514                 :             : 
    5515                 :             :         /* did the creating transaction abort? */
    5516         [ +  + ]:         480 :         if (!TransactionIdDidCommit(f_create_xid))
    5517                 :         132 :             continue;
    5518                 :             : 
    5519                 :             :         /* not for our transaction */
    5520         [ +  + ]:         348 :         if (!TransactionIdInArray(f_mapped_xid, snapshot->subxip, snapshot->subxcnt))
    5521                 :         321 :             continue;
    5522                 :             : 
    5523                 :             :         /* ok, relevant, queue for apply */
    5524                 :          27 :         f = palloc_object(RewriteMappingFile);
    5525                 :          27 :         f->lsn = f_lsn;
    5526                 :          27 :         strcpy(f->fname, mapping_de->d_name);
    5527                 :          27 :         files = lappend(files, f);
    5528                 :             :     }
    5529                 :          11 :     FreeDir(mapping_dir);
    5530                 :             : 
    5531                 :             :     /* sort files so we apply them in LSN order */
    5532                 :          11 :     list_sort(files, file_sort_by_lsn);
    5533                 :             : 
    5534   [ +  +  +  +  :          38 :     foreach(file, files)
                   +  + ]
    5535                 :             :     {
    5536                 :          27 :         RewriteMappingFile *f = (RewriteMappingFile *) lfirst(file);
    5537                 :             : 
    5538         [ -  + ]:          27 :         elog(DEBUG1, "applying mapping: \"%s\" in %u", f->fname,
    5539                 :             :              snapshot->subxip[0]);
    5540                 :          27 :         ApplyLogicalMappingFile(tuplecid_data, f->fname);
    5541                 :          27 :         pfree(f);
    5542                 :             :     }
    5543                 :          11 : }
    5544                 :             : 
    5545                 :             : /*
    5546                 :             :  * Lookup cmin/cmax of a tuple, during logical decoding where we can't rely on
    5547                 :             :  * combo CIDs.
    5548                 :             :  */
    5549                 :             : bool
    5550                 :         843 : ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
    5551                 :             :                               Snapshot snapshot,
    5552                 :             :                               HeapTuple htup, Buffer buffer,
    5553                 :             :                               CommandId *cmin, CommandId *cmax)
    5554                 :             : {
    5555                 :             :     ReorderBufferTupleCidKey key;
    5556                 :             :     ReorderBufferTupleCidEnt *ent;
    5557                 :             :     ForkNumber  forkno;
    5558                 :             :     BlockNumber blockno;
    5559                 :         843 :     bool        updated_mapping = false;
    5560                 :             : 
    5561                 :             :     /*
    5562                 :             :      * Return unresolved if tuplecid_data is not valid.  That's because when
    5563                 :             :      * streaming in-progress transactions we may run into tuples with the CID
    5564                 :             :      * before actually decoding them.  Think e.g. about INSERT followed by
    5565                 :             :      * TRUNCATE, where the TRUNCATE may not be decoded yet when applying the
    5566                 :             :      * INSERT.  So in such cases, we assume the CID is from the future
    5567                 :             :      * command.
    5568                 :             :      */
    5569         [ +  + ]:         843 :     if (tuplecid_data == NULL)
    5570                 :          11 :         return false;
    5571                 :             : 
    5572                 :             :     /* be careful about padding */
    5573                 :         832 :     memset(&key, 0, sizeof(key));
    5574                 :             : 
    5575                 :             :     Assert(!BufferIsLocal(buffer));
    5576                 :             : 
    5577                 :             :     /*
    5578                 :             :      * get relfilelocator from the buffer, no convenient way to access it
    5579                 :             :      * other than that.
    5580                 :             :      */
    5581                 :         832 :     BufferGetTag(buffer, &key.rlocator, &forkno, &blockno);
    5582                 :             : 
    5583                 :             :     /* tuples can only be in the main fork */
    5584                 :             :     Assert(forkno == MAIN_FORKNUM);
    5585                 :             :     Assert(blockno == ItemPointerGetBlockNumber(&htup->t_self));
    5586                 :             : 
    5587                 :         832 :     ItemPointerCopy(&htup->t_self,
    5588                 :             :                     &key.tid);
    5589                 :             : 
    5590                 :         843 : restart:
    5591                 :             :     ent = (ReorderBufferTupleCidEnt *)
    5592                 :         843 :         hash_search(tuplecid_data, &key, HASH_FIND, NULL);
    5593                 :             : 
    5594                 :             :     /*
    5595                 :             :      * failed to find a mapping, check whether the table was rewritten and
    5596                 :             :      * apply mapping if so, but only do that once - there can be no new
    5597                 :             :      * mappings while we are in here since we have to hold a lock on the
    5598                 :             :      * relation.
    5599                 :             :      */
    5600   [ +  +  +  + ]:         843 :     if (ent == NULL && !updated_mapping)
    5601                 :             :     {
    5602                 :          11 :         UpdateLogicalMappings(tuplecid_data, htup->t_tableOid, snapshot);
    5603                 :             :         /* now check but don't update for a mapping again */
    5604                 :          11 :         updated_mapping = true;
    5605                 :          11 :         goto restart;
    5606                 :             :     }
    5607         [ +  + ]:         832 :     else if (ent == NULL)
    5608                 :           5 :         return false;
    5609                 :             : 
    5610         [ +  - ]:         827 :     if (cmin)
    5611                 :         827 :         *cmin = ent->cmin;
    5612         [ +  - ]:         827 :     if (cmax)
    5613                 :         827 :         *cmax = ent->cmax;
    5614                 :         827 :     return true;
    5615                 :             : }
    5616                 :             : 
    5617                 :             : /*
    5618                 :             :  * Count invalidation messages of specified transaction.
    5619                 :             :  *
    5620                 :             :  * Returns number of messages, and msgs is set to the pointer of the linked
    5621                 :             :  * list for the messages.
    5622                 :             :  */
    5623                 :             : uint32
    5624                 :          32 : ReorderBufferGetInvalidations(ReorderBuffer *rb, TransactionId xid,
    5625                 :             :                               SharedInvalidationMessage **msgs)
    5626                 :             : {
    5627                 :             :     ReorderBufferTXN *txn;
    5628                 :             : 
    5629                 :          32 :     txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
    5630                 :             :                                 false);
    5631                 :             : 
    5632         [ -  + ]:          32 :     if (txn == NULL)
    5633                 :           0 :         return 0;
    5634                 :             : 
    5635                 :          32 :     *msgs = txn->invalidations;
    5636                 :             : 
    5637                 :          32 :     return txn->ninvalidations;
    5638                 :             : }
        

Generated by: LCOV version 2.0-1