Coverage Report

Created: 2026-08-13 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/replication/logical/reorderbuffer.c
Line
Count
Source
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
0
  ((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
0
#define IsSpecInsert(action) \
199
0
( \
200
0
  ((action) == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT) \
201
0
)
202
0
#define IsSpecConfirmOrAbort(action) \
203
0
( \
204
0
  (((action) == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM) || \
205
0
  ((action) == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT)) \
206
0
)
207
0
#define IsInsertOrUpdate(action) \
208
0
( \
209
0
  (((action) == REORDER_BUFFER_CHANGE_INSERT) || \
210
0
  ((action) == REORDER_BUFFER_CHANGE_UPDATE) || \
211
0
  ((action) == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT)) \
212
0
)
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
ReorderBufferAllocate(void)
326
0
{
327
0
  ReorderBuffer *buffer;
328
0
  HASHCTL   hash_ctl;
329
0
  MemoryContext new_ctx;
330
331
0
  Assert(MyReplicationSlot != NULL);
332
333
  /* allocate memory in own context, to have better accountability */
334
0
  new_ctx = AllocSetContextCreate(CurrentMemoryContext,
335
0
                  "ReorderBuffer",
336
0
                  ALLOCSET_DEFAULT_SIZES);
337
338
0
  buffer =
339
0
    (ReorderBuffer *) MemoryContextAlloc(new_ctx, sizeof(ReorderBuffer));
340
341
0
  memset(&hash_ctl, 0, sizeof(hash_ctl));
342
343
0
  buffer->context = new_ctx;
344
345
0
  buffer->change_context = SlabContextCreate(new_ctx,
346
0
                         "Change",
347
0
                         SLAB_DEFAULT_BLOCK_SIZE,
348
0
                         sizeof(ReorderBufferChange));
349
350
0
  buffer->txn_context = SlabContextCreate(new_ctx,
351
0
                      "TXN",
352
0
                      SLAB_DEFAULT_BLOCK_SIZE,
353
0
                      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
0
  buffer->tup_context = GenerationContextCreate(new_ctx,
366
0
                          "Tuples",
367
0
                          SLAB_DEFAULT_BLOCK_SIZE,
368
0
                          SLAB_DEFAULT_BLOCK_SIZE,
369
0
                          SLAB_DEFAULT_BLOCK_SIZE);
370
371
0
  hash_ctl.keysize = sizeof(TransactionId);
372
0
  hash_ctl.entrysize = sizeof(ReorderBufferTXNByIdEnt);
373
0
  hash_ctl.hcxt = buffer->context;
374
375
0
  buffer->by_txn = hash_create("ReorderBufferByXid", 1000, &hash_ctl,
376
0
                 HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
377
378
0
  buffer->by_txn_last_xid = InvalidTransactionId;
379
0
  buffer->by_txn_last_txn = NULL;
380
381
0
  buffer->outbuf = NULL;
382
0
  buffer->outbufsize = 0;
383
0
  buffer->size = 0;
384
385
  /* txn_heap is ordered by transaction size */
386
0
  buffer->txn_heap = pairingheap_allocate(ReorderBufferTXNSizeCompare, NULL);
387
388
0
  buffer->spillTxns = 0;
389
0
  buffer->spillCount = 0;
390
0
  buffer->spillBytes = 0;
391
0
  buffer->streamTxns = 0;
392
0
  buffer->streamCount = 0;
393
0
  buffer->streamBytes = 0;
394
0
  buffer->memExceededCount = 0;
395
0
  buffer->totalTxns = 0;
396
0
  buffer->totalBytes = 0;
397
398
0
  buffer->current_restart_decoding_lsn = InvalidXLogRecPtr;
399
400
0
  dlist_init(&buffer->toplevel_by_lsn);
401
0
  dlist_init(&buffer->txns_by_base_snapshot_lsn);
402
0
  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
0
  ReorderBufferCleanupSerializedTXNs(NameStr(MyReplicationSlot->data.name));
410
411
0
  return buffer;
412
0
}
413
414
/*
415
 * Free a ReorderBuffer
416
 */
417
void
418
ReorderBufferFree(ReorderBuffer *rb)
419
0
{
420
0
  MemoryContext context = rb->context;
421
422
  /*
423
   * We free separately allocated data by entirely scrapping reorderbuffer's
424
   * memory context.
425
   */
426
0
  MemoryContextDelete(context);
427
428
  /* Free disk space used by unconsumed reorder buffers */
429
0
  ReorderBufferCleanupSerializedTXNs(NameStr(MyReplicationSlot->data.name));
430
0
}
431
432
/*
433
 * Allocate a new ReorderBufferTXN.
434
 */
435
static ReorderBufferTXN *
436
ReorderBufferAllocTXN(ReorderBuffer *rb)
437
0
{
438
0
  ReorderBufferTXN *txn;
439
440
0
  txn = (ReorderBufferTXN *)
441
0
    MemoryContextAlloc(rb->txn_context, sizeof(ReorderBufferTXN));
442
443
0
  memset(txn, 0, sizeof(ReorderBufferTXN));
444
445
0
  dlist_init(&txn->changes);
446
0
  dlist_init(&txn->tuplecids);
447
0
  dlist_init(&txn->subtxns);
448
449
  /* InvalidCommandId is not zero, so set it explicitly */
450
0
  txn->command_id = InvalidCommandId;
451
0
  txn->output_plugin_private = NULL;
452
453
0
  return txn;
454
0
}
455
456
/*
457
 * Free a ReorderBufferTXN.
458
 */
459
static void
460
ReorderBufferFreeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
461
0
{
462
  /* clean the lookup cache if we were cached (quite likely) */
463
0
  if (rb->by_txn_last_xid == txn->xid)
464
0
  {
465
0
    rb->by_txn_last_xid = InvalidTransactionId;
466
0
    rb->by_txn_last_txn = NULL;
467
0
  }
468
469
  /* free data that's contained */
470
471
0
  if (txn->gid != NULL)
472
0
  {
473
0
    pfree(txn->gid);
474
0
    txn->gid = NULL;
475
0
  }
476
477
0
  if (txn->tuplecid_hash != NULL)
478
0
  {
479
0
    hash_destroy(txn->tuplecid_hash);
480
0
    txn->tuplecid_hash = NULL;
481
0
  }
482
483
0
  if (txn->invalidations)
484
0
  {
485
0
    pfree(txn->invalidations);
486
0
    txn->invalidations = NULL;
487
0
  }
488
489
0
  if (txn->invalidations_distributed)
490
0
  {
491
0
    pfree(txn->invalidations_distributed);
492
0
    txn->invalidations_distributed = NULL;
493
0
  }
494
495
  /* Reset the toast hash */
496
0
  ReorderBufferToastReset(rb, txn);
497
498
  /* All changes must be deallocated */
499
0
  Assert(txn->size == 0);
500
501
0
  pfree(txn);
502
0
}
503
504
/*
505
 * Allocate a ReorderBufferChange.
506
 */
507
ReorderBufferChange *
508
ReorderBufferAllocChange(ReorderBuffer *rb)
509
0
{
510
0
  ReorderBufferChange *change;
511
512
0
  change = (ReorderBufferChange *)
513
0
    MemoryContextAlloc(rb->change_context, sizeof(ReorderBufferChange));
514
515
0
  memset(change, 0, sizeof(ReorderBufferChange));
516
0
  return change;
517
0
}
518
519
/*
520
 * Free a ReorderBufferChange and update memory accounting, if requested.
521
 */
522
void
523
ReorderBufferFreeChange(ReorderBuffer *rb, ReorderBufferChange *change,
524
            bool upd_mem)
525
0
{
526
  /* update memory accounting info */
527
0
  if (upd_mem)
528
0
    ReorderBufferChangeMemoryUpdate(rb, change, NULL, false,
529
0
                    ReorderBufferChangeSize(change));
530
531
  /* free contained data */
532
0
  switch (change->action)
533
0
  {
534
0
    case REORDER_BUFFER_CHANGE_INSERT:
535
0
    case REORDER_BUFFER_CHANGE_UPDATE:
536
0
    case REORDER_BUFFER_CHANGE_DELETE:
537
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
538
0
      if (change->data.tp.newtuple)
539
0
      {
540
0
        ReorderBufferFreeTupleBuf(change->data.tp.newtuple);
541
0
        change->data.tp.newtuple = NULL;
542
0
      }
543
544
0
      if (change->data.tp.oldtuple)
545
0
      {
546
0
        ReorderBufferFreeTupleBuf(change->data.tp.oldtuple);
547
0
        change->data.tp.oldtuple = NULL;
548
0
      }
549
0
      break;
550
0
    case REORDER_BUFFER_CHANGE_MESSAGE:
551
0
      if (change->data.msg.prefix != NULL)
552
0
        pfree(change->data.msg.prefix);
553
0
      change->data.msg.prefix = NULL;
554
0
      if (change->data.msg.message != NULL)
555
0
        pfree(change->data.msg.message);
556
0
      change->data.msg.message = NULL;
557
0
      break;
558
0
    case REORDER_BUFFER_CHANGE_INVALIDATION:
559
0
      if (change->data.inval.invalidations)
560
0
        pfree(change->data.inval.invalidations);
561
0
      change->data.inval.invalidations = NULL;
562
0
      break;
563
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
564
0
      if (change->data.snapshot)
565
0
      {
566
0
        ReorderBufferFreeSnap(rb, change->data.snapshot);
567
0
        change->data.snapshot = NULL;
568
0
      }
569
0
      break;
570
      /* no data in addition to the struct itself */
571
0
    case REORDER_BUFFER_CHANGE_TRUNCATE:
572
0
      if (change->data.truncate.relids != NULL)
573
0
      {
574
0
        ReorderBufferFreeRelids(rb, change->data.truncate.relids);
575
0
        change->data.truncate.relids = NULL;
576
0
      }
577
0
      break;
578
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
579
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT:
580
0
    case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
581
0
    case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
582
0
      break;
583
0
  }
584
585
0
  pfree(change);
586
0
}
587
588
/*
589
 * Allocate a HeapTuple fitting a tuple of size tuple_len (excluding header
590
 * overhead).
591
 */
592
HeapTuple
593
ReorderBufferAllocTupleBuf(ReorderBuffer *rb, Size tuple_len)
594
0
{
595
0
  HeapTuple tuple;
596
0
  Size    alloc_len;
597
598
0
  alloc_len = tuple_len + SizeofHeapTupleHeader;
599
600
0
  tuple = (HeapTuple) MemoryContextAlloc(rb->tup_context,
601
0
                       HEAPTUPLESIZE + alloc_len);
602
0
  tuple->t_data = (HeapTupleHeader) ((char *) tuple + HEAPTUPLESIZE);
603
604
0
  return tuple;
605
0
}
606
607
/*
608
 * Free a HeapTuple returned by ReorderBufferAllocTupleBuf().
609
 */
610
void
611
ReorderBufferFreeTupleBuf(HeapTuple tuple)
612
0
{
613
0
  pfree(tuple);
614
0
}
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
ReorderBufferAllocRelids(ReorderBuffer *rb, int nrelids)
627
0
{
628
0
  Oid      *relids;
629
0
  Size    alloc_len;
630
631
0
  alloc_len = sizeof(Oid) * nrelids;
632
633
0
  relids = (Oid *) MemoryContextAlloc(rb->context, alloc_len);
634
635
0
  return relids;
636
0
}
637
638
/*
639
 * Free an array of relids.
640
 */
641
void
642
ReorderBufferFreeRelids(ReorderBuffer *rb, Oid *relids)
643
0
{
644
0
  pfree(relids);
645
0
}
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
ReorderBufferTXNByXid(ReorderBuffer *rb, TransactionId xid, bool create,
655
            bool *is_new, XLogRecPtr lsn, bool create_as_top)
656
0
{
657
0
  ReorderBufferTXN *txn;
658
0
  ReorderBufferTXNByIdEnt *ent;
659
0
  bool    found;
660
661
0
  Assert(TransactionIdIsValid(xid));
662
663
  /*
664
   * Check the one-entry lookup cache first
665
   */
666
0
  if (TransactionIdIsValid(rb->by_txn_last_xid) &&
667
0
    rb->by_txn_last_xid == xid)
668
0
  {
669
0
    txn = rb->by_txn_last_txn;
670
671
0
    if (txn != NULL)
672
0
    {
673
      /* found it, and it's valid */
674
0
      if (is_new)
675
0
        *is_new = false;
676
0
      return txn;
677
0
    }
678
679
    /*
680
     * cached as non-existent, and asked not to create? Then nothing else
681
     * to do.
682
     */
683
0
    if (!create)
684
0
      return NULL;
685
    /* otherwise fall through to create it */
686
0
  }
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
0
  ent = (ReorderBufferTXNByIdEnt *)
695
0
    hash_search(rb->by_txn,
696
0
          &xid,
697
0
          create ? HASH_ENTER : HASH_FIND,
698
0
          &found);
699
0
  if (found)
700
0
    txn = ent->txn;
701
0
  else if (create)
702
0
  {
703
    /* initialize the new entry, if creation was requested */
704
0
    Assert(ent != NULL);
705
0
    Assert(XLogRecPtrIsValid(lsn));
706
707
0
    ent->txn = ReorderBufferAllocTXN(rb);
708
0
    ent->txn->xid = xid;
709
0
    txn = ent->txn;
710
0
    txn->first_lsn = lsn;
711
0
    txn->restart_decoding_lsn = rb->current_restart_decoding_lsn;
712
713
0
    if (create_as_top)
714
0
    {
715
0
      dlist_push_tail(&rb->toplevel_by_lsn, &txn->node);
716
0
      AssertTXNLsnOrder(rb);
717
0
    }
718
0
  }
719
0
  else
720
0
    txn = NULL;       /* not found and not asked to create */
721
722
  /* update cache */
723
0
  rb->by_txn_last_xid = xid;
724
0
  rb->by_txn_last_txn = txn;
725
726
0
  if (is_new)
727
0
    *is_new = !found;
728
729
0
  Assert(!create || txn != NULL);
730
0
  return txn;
731
0
}
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
ReorderBufferProcessPartialChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
743
                  ReorderBufferChange *change,
744
                  bool toast_insert)
745
0
{
746
0
  ReorderBufferTXN *toptxn;
747
748
  /*
749
   * The partial changes need to be processed only while streaming
750
   * in-progress transactions.
751
   */
752
0
  if (!ReorderBufferCanStream(rb))
753
0
    return;
754
755
  /* Get the top transaction. */
756
0
  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
0
  if (toast_insert)
773
0
    toptxn->txn_flags |= RBTXN_HAS_PARTIAL_CHANGE;
774
0
  else if (rbtxn_has_partial_change(toptxn) &&
775
0
       IsInsertOrUpdate(change->action) &&
776
0
       change->data.tp.clear_toast_afterwards)
777
0
    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
0
  if (IsSpecInsert(change->action))
785
0
    toptxn->txn_flags |= RBTXN_HAS_PARTIAL_CHANGE;
786
0
  else if (rbtxn_has_partial_change(toptxn) &&
787
0
       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
0
  if (ReorderBufferCanStartStreaming(rb) &&
800
0
    !(rbtxn_has_partial_change(toptxn)) &&
801
0
    rbtxn_is_serialized(txn) &&
802
0
    rbtxn_has_streamable_change(toptxn))
803
0
    ReorderBufferStreamTXN(rb, toptxn);
804
0
}
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
ReorderBufferQueueChange(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn,
812
             ReorderBufferChange *change, bool toast_insert)
813
0
{
814
0
  ReorderBufferTXN *txn;
815
816
0
  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
0
  if (rbtxn_is_aborted(txn))
824
0
  {
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
0
    ReorderBufferFreeChange(rb, change, false);
830
0
    return;
831
0
  }
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
0
  if (change->action == REORDER_BUFFER_CHANGE_INSERT ||
839
0
    change->action == REORDER_BUFFER_CHANGE_UPDATE ||
840
0
    change->action == REORDER_BUFFER_CHANGE_DELETE ||
841
0
    change->action == REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT ||
842
0
    change->action == REORDER_BUFFER_CHANGE_TRUNCATE ||
843
0
    change->action == REORDER_BUFFER_CHANGE_MESSAGE)
844
0
  {
845
0
    ReorderBufferTXN *toptxn = rbtxn_get_toptxn(txn);
846
847
0
    toptxn->txn_flags |= RBTXN_HAS_STREAMABLE_CHANGE;
848
0
  }
849
850
0
  change->lsn = lsn;
851
0
  change->txn = txn;
852
853
0
  Assert(XLogRecPtrIsValid(lsn));
854
0
  dlist_push_tail(&txn->changes, &change->node);
855
0
  txn->nentries++;
856
0
  txn->nentries_mem++;
857
858
  /* update memory accounting information */
859
0
  ReorderBufferChangeMemoryUpdate(rb, change, NULL, true,
860
0
                  ReorderBufferChangeSize(change));
861
862
  /* process partial change */
863
0
  ReorderBufferProcessPartialChange(rb, txn, change, toast_insert);
864
865
  /* check the memory limits and evict something if needed */
866
0
  ReorderBufferCheckMemoryLimit(rb);
867
0
}
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
ReorderBufferQueueMessage(ReorderBuffer *rb, TransactionId xid,
875
              Snapshot snap, XLogRecPtr lsn,
876
              bool transactional, const char *prefix,
877
              Size message_size, const char *message)
878
0
{
879
0
  if (transactional)
880
0
  {
881
0
    MemoryContext oldcontext;
882
0
    ReorderBufferChange *change;
883
884
0
    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
0
    Assert(!snap);
892
893
0
    oldcontext = MemoryContextSwitchTo(rb->context);
894
895
0
    change = ReorderBufferAllocChange(rb);
896
0
    change->action = REORDER_BUFFER_CHANGE_MESSAGE;
897
0
    change->data.msg.prefix = pstrdup(prefix);
898
0
    change->data.msg.message_size = message_size;
899
0
    change->data.msg.message = palloc(message_size);
900
0
    memcpy(change->data.msg.message, message, message_size);
901
902
0
    ReorderBufferQueueChange(rb, xid, lsn, change, false);
903
904
0
    MemoryContextSwitchTo(oldcontext);
905
0
  }
906
0
  else
907
0
  {
908
0
    ReorderBufferTXN *txn = NULL;
909
0
    volatile Snapshot snapshot_now = snap;
910
911
    /* Non-transactional changes require a valid snapshot. */
912
0
    Assert(snapshot_now);
913
914
0
    if (xid != InvalidTransactionId)
915
0
      txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
916
917
    /* setup snapshot to allow catalog access */
918
0
    SetupHistoricSnapshot(snapshot_now, NULL);
919
0
    PG_TRY();
920
0
    {
921
0
      rb->message(rb, txn, lsn, false, prefix, message_size, message);
922
923
0
      TeardownHistoricSnapshot(false);
924
0
    }
925
0
    PG_CATCH();
926
0
    {
927
0
      TeardownHistoricSnapshot(true);
928
0
      PG_RE_THROW();
929
0
    }
930
0
    PG_END_TRY();
931
0
  }
932
0
}
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
AssertTXNLsnOrder(ReorderBuffer *rb)
944
0
{
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
0
}
1007
1008
/*
1009
 * AssertChangeLsnOrder
1010
 *
1011
 * Check ordering of changes in the (sub)transaction.
1012
 */
1013
static void
1014
AssertChangeLsnOrder(ReorderBufferTXN *txn)
1015
0
{
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
0
}
1039
1040
/*
1041
 * ReorderBufferGetOldestTXN
1042
 *    Return oldest transaction in reorderbuffer
1043
 */
1044
ReorderBufferTXN *
1045
ReorderBufferGetOldestTXN(ReorderBuffer *rb)
1046
0
{
1047
0
  ReorderBufferTXN *txn;
1048
1049
0
  AssertTXNLsnOrder(rb);
1050
1051
0
  if (dlist_is_empty(&rb->toplevel_by_lsn))
1052
0
    return NULL;
1053
1054
0
  txn = dlist_head_element(ReorderBufferTXN, node, &rb->toplevel_by_lsn);
1055
1056
0
  Assert(!rbtxn_is_known_subxact(txn));
1057
0
  Assert(XLogRecPtrIsValid(txn->first_lsn));
1058
0
  return txn;
1059
0
}
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
ReorderBufferGetOldestXmin(ReorderBuffer *rb)
1074
0
{
1075
0
  ReorderBufferTXN *txn;
1076
1077
0
  AssertTXNLsnOrder(rb);
1078
1079
0
  if (dlist_is_empty(&rb->txns_by_base_snapshot_lsn))
1080
0
    return InvalidTransactionId;
1081
1082
0
  txn = dlist_head_element(ReorderBufferTXN, base_snapshot_node,
1083
0
               &rb->txns_by_base_snapshot_lsn);
1084
0
  return txn->base_snapshot->xmin;
1085
0
}
1086
1087
void
1088
ReorderBufferSetRestartPoint(ReorderBuffer *rb, XLogRecPtr ptr)
1089
0
{
1090
0
  rb->current_restart_decoding_lsn = ptr;
1091
0
}
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
ReorderBufferAssignChild(ReorderBuffer *rb, TransactionId xid,
1101
             TransactionId subxid, XLogRecPtr lsn)
1102
0
{
1103
0
  ReorderBufferTXN *txn;
1104
0
  ReorderBufferTXN *subtxn;
1105
0
  bool    new_top;
1106
0
  bool    new_sub;
1107
1108
0
  txn = ReorderBufferTXNByXid(rb, xid, true, &new_top, lsn, true);
1109
0
  subtxn = ReorderBufferTXNByXid(rb, subxid, true, &new_sub, lsn, false);
1110
1111
0
  if (!new_sub)
1112
0
  {
1113
0
    if (rbtxn_is_known_subxact(subtxn))
1114
0
    {
1115
      /* already associated, nothing to do */
1116
0
      return;
1117
0
    }
1118
0
    else
1119
0
    {
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
0
    }
1127
0
  }
1128
1129
0
  subtxn->txn_flags |= RBTXN_IS_SUBXACT;
1130
0
  subtxn->toplevel_xid = xid;
1131
0
  Assert(subtxn->nsubtxns == 0);
1132
1133
  /* set the reference to top-level transaction */
1134
0
  subtxn->toptxn = txn;
1135
1136
  /* add to subtransaction list */
1137
0
  dlist_push_tail(&txn->subtxns, &subtxn->node);
1138
0
  txn->nsubtxns++;
1139
1140
  /* Possibly transfer the subtxn's snapshot to its top-level txn. */
1141
0
  ReorderBufferTransferSnapToParent(txn, subtxn);
1142
1143
  /* Verify LSN-ordering invariant */
1144
0
  AssertTXNLsnOrder(rb);
1145
0
}
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
ReorderBufferTransferSnapToParent(ReorderBufferTXN *txn,
1167
                  ReorderBufferTXN *subtxn)
1168
0
{
1169
0
  Assert(subtxn->toplevel_xid == txn->xid);
1170
1171
0
  if (subtxn->base_snapshot != NULL)
1172
0
  {
1173
0
    if (txn->base_snapshot == NULL ||
1174
0
      subtxn->base_snapshot_lsn < txn->base_snapshot_lsn)
1175
0
    {
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
0
      {
1182
0
        SnapBuildSnapDecRefcount(txn->base_snapshot);
1183
0
        dlist_delete(&txn->base_snapshot_node);
1184
0
      }
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
0
                &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
0
    }
1204
0
    else
1205
0
    {
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
0
    }
1212
0
  }
1213
0
}
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
ReorderBufferCommitChild(ReorderBuffer *rb, TransactionId xid,
1221
             TransactionId subxid, XLogRecPtr commit_lsn,
1222
             XLogRecPtr end_lsn)
1223
0
{
1224
0
  ReorderBufferTXN *subtxn;
1225
1226
0
  subtxn = ReorderBufferTXNByXid(rb, subxid, false, NULL,
1227
0
                   InvalidXLogRecPtr, false);
1228
1229
  /*
1230
   * No need to do anything if that subtxn didn't contain any changes
1231
   */
1232
0
  if (!subtxn)
1233
0
    return;
1234
1235
0
  subtxn->final_lsn = commit_lsn;
1236
0
  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
0
  ReorderBufferAssignChild(rb, xid, subxid, InvalidXLogRecPtr);
1243
0
}
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
ReorderBufferIterCompare(Datum a, Datum b, void *arg)
1263
0
{
1264
0
  ReorderBufferIterTXNState *state = (ReorderBufferIterTXNState *) arg;
1265
0
  XLogRecPtr  pos_a = state->entries[DatumGetInt32(a)].lsn;
1266
0
  XLogRecPtr  pos_b = state->entries[DatumGetInt32(b)].lsn;
1267
1268
0
  if (pos_a < pos_b)
1269
0
    return 1;
1270
0
  else if (pos_a == pos_b)
1271
0
    return 0;
1272
0
  return -1;
1273
0
}
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
ReorderBufferIterTXNInit(ReorderBuffer *rb, ReorderBufferTXN *txn,
1286
             ReorderBufferIterTXNState *volatile *iter_state)
1287
0
{
1288
0
  Size    nr_txns = 0;
1289
0
  ReorderBufferIterTXNState *state;
1290
0
  dlist_iter  cur_txn_i;
1291
0
  Size    off;
1292
1293
0
  *iter_state = NULL;
1294
1295
  /* Check ordering of changes in the toplevel transaction. */
1296
0
  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
0
  if (txn->nentries > 0)
1304
0
    nr_txns++;
1305
1306
0
  dlist_foreach(cur_txn_i, &txn->subtxns)
1307
0
  {
1308
0
    ReorderBufferTXN *cur_txn;
1309
1310
0
    cur_txn = dlist_container(ReorderBufferTXN, node, cur_txn_i.cur);
1311
1312
    /* Check ordering of changes in this subtransaction. */
1313
0
    AssertChangeLsnOrder(cur_txn);
1314
1315
0
    if (cur_txn->nentries > 0)
1316
0
      nr_txns++;
1317
0
  }
1318
1319
  /* allocate iteration state */
1320
0
  state = (ReorderBufferIterTXNState *)
1321
0
    MemoryContextAllocZero(rb->context,
1322
0
                 sizeof(ReorderBufferIterTXNState) +
1323
0
                 sizeof(ReorderBufferIterTXNEntry) * nr_txns);
1324
1325
0
  state->nr_txns = nr_txns;
1326
0
  dlist_init(&state->old_change);
1327
1328
0
  for (off = 0; off < state->nr_txns; off++)
1329
0
  {
1330
0
    state->entries[off].file.vfd = -1;
1331
0
    state->entries[off].segno = 0;
1332
0
  }
1333
1334
  /* allocate heap */
1335
0
  state->heap = binaryheap_allocate(state->nr_txns,
1336
0
                    ReorderBufferIterCompare,
1337
0
                    state);
1338
1339
  /* Now that the state fields are initialized, it is safe to return it. */
1340
0
  *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
0
  off = 0;
1348
1349
  /* add toplevel transaction if it contains changes */
1350
0
  if (txn->nentries > 0)
1351
0
  {
1352
0
    ReorderBufferChange *cur_change;
1353
1354
0
    if (rbtxn_is_serialized(txn))
1355
0
    {
1356
      /* serialize remaining changes */
1357
0
      ReorderBufferSerializeTXN(rb, txn);
1358
0
      ReorderBufferRestoreChanges(rb, txn, &state->entries[off].file,
1359
0
                    &state->entries[off].segno);
1360
0
    }
1361
1362
0
    cur_change = dlist_head_element(ReorderBufferChange, node,
1363
0
                    &txn->changes);
1364
1365
0
    state->entries[off].lsn = cur_change->lsn;
1366
0
    state->entries[off].change = cur_change;
1367
0
    state->entries[off].txn = txn;
1368
1369
0
    binaryheap_add_unordered(state->heap, Int32GetDatum(off++));
1370
0
  }
1371
1372
  /* add subtransactions if they contain changes */
1373
0
  dlist_foreach(cur_txn_i, &txn->subtxns)
1374
0
  {
1375
0
    ReorderBufferTXN *cur_txn;
1376
1377
0
    cur_txn = dlist_container(ReorderBufferTXN, node, cur_txn_i.cur);
1378
1379
0
    if (cur_txn->nentries > 0)
1380
0
    {
1381
0
      ReorderBufferChange *cur_change;
1382
1383
0
      if (rbtxn_is_serialized(cur_txn))
1384
0
      {
1385
        /* serialize remaining changes */
1386
0
        ReorderBufferSerializeTXN(rb, cur_txn);
1387
0
        ReorderBufferRestoreChanges(rb, cur_txn,
1388
0
                      &state->entries[off].file,
1389
0
                      &state->entries[off].segno);
1390
0
      }
1391
0
      cur_change = dlist_head_element(ReorderBufferChange, node,
1392
0
                      &cur_txn->changes);
1393
1394
0
      state->entries[off].lsn = cur_change->lsn;
1395
0
      state->entries[off].change = cur_change;
1396
0
      state->entries[off].txn = cur_txn;
1397
1398
0
      binaryheap_add_unordered(state->heap, Int32GetDatum(off++));
1399
0
    }
1400
0
  }
1401
1402
  /* assemble a valid binary heap */
1403
0
  binaryheap_build(state->heap);
1404
0
}
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
ReorderBufferIterTXNNext(ReorderBuffer *rb, ReorderBufferIterTXNState *state)
1414
{
1415
  ReorderBufferChange *change;
1416
  ReorderBufferIterTXNEntry *entry;
1417
  int32   off;
1418
1419
  /* nothing there anymore */
1420
  if (binaryheap_empty(state->heap))
1421
    return NULL;
1422
1423
  off = DatumGetInt32(binaryheap_first(state->heap));
1424
  entry = &state->entries[off];
1425
1426
  /* free memory we might have "leaked" in the previous *Next call */
1427
  if (!dlist_is_empty(&state->old_change))
1428
  {
1429
    change = dlist_container(ReorderBufferChange, node,
1430
                 dlist_pop_head_node(&state->old_change));
1431
    ReorderBufferFreeChange(rb, change, true);
1432
    Assert(dlist_is_empty(&state->old_change));
1433
  }
1434
1435
  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
  if (dlist_has_next(&entry->txn->changes, &entry->change->node))
1444
  {
1445
    dlist_node *next = dlist_next_node(&entry->txn->changes, &change->node);
1446
    ReorderBufferChange *next_change =
1447
      dlist_container(ReorderBufferChange, node, next);
1448
1449
    /* txn stays the same */
1450
    state->entries[off].lsn = next_change->lsn;
1451
    state->entries[off].change = next_change;
1452
1453
    binaryheap_replace_first(state->heap, Int32GetDatum(off));
1454
    return change;
1455
  }
1456
1457
  /* try to load changes from disk */
1458
  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
    dlist_delete(&change->node);
1465
    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
    rb->totalBytes += entry->txn->size;
1473
    if (ReorderBufferRestoreChanges(rb, entry->txn, &entry->file,
1474
                    &state->entries[off].segno))
1475
    {
1476
      /* successfully restored changes from disk */
1477
      ReorderBufferChange *next_change =
1478
        dlist_head_element(ReorderBufferChange, node,
1479
                   &entry->txn->changes);
1480
1481
      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
      state->entries[off].lsn = next_change->lsn;
1488
      state->entries[off].change = next_change;
1489
      binaryheap_replace_first(state->heap, Int32GetDatum(off));
1490
1491
      return change;
1492
    }
1493
  }
1494
1495
  /* ok, no changes there anymore, remove */
1496
  binaryheap_remove_first(state->heap);
1497
1498
  return change;
1499
}
1500
1501
/*
1502
 * Deallocate the iterator
1503
 */
1504
static void
1505
ReorderBufferIterTXNFinish(ReorderBuffer *rb,
1506
               ReorderBufferIterTXNState *state)
1507
0
{
1508
0
  Size    off;
1509
1510
0
  for (off = 0; off < state->nr_txns; off++)
1511
0
  {
1512
0
    if (state->entries[off].file.vfd != -1)
1513
0
      FileClose(state->entries[off].file.vfd);
1514
0
  }
1515
1516
  /* free memory we might have "leaked" in the last *Next call */
1517
0
  if (!dlist_is_empty(&state->old_change))
1518
0
  {
1519
0
    ReorderBufferChange *change;
1520
1521
0
    change = dlist_container(ReorderBufferChange, node,
1522
0
                 dlist_pop_head_node(&state->old_change));
1523
0
    ReorderBufferFreeChange(rb, change, true);
1524
0
    Assert(dlist_is_empty(&state->old_change));
1525
0
  }
1526
1527
0
  binaryheap_free(state->heap);
1528
0
  pfree(state);
1529
0
}
1530
1531
/*
1532
 * Cleanup the contents of a transaction, usually after the transaction
1533
 * committed or aborted.
1534
 */
1535
static void
1536
ReorderBufferCleanupTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
1537
0
{
1538
0
  bool    found;
1539
0
  dlist_mutable_iter iter;
1540
0
  Size    mem_freed = 0;
1541
1542
  /* cleanup subtransactions & their changes */
1543
0
  dlist_foreach_modify(iter, &txn->subtxns)
1544
0
  {
1545
0
    ReorderBufferTXN *subtxn;
1546
1547
0
    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
0
    Assert(rbtxn_is_known_subxact(subtxn));
1555
0
    Assert(subtxn->nsubtxns == 0);
1556
1557
0
    ReorderBufferCleanupTXN(rb, subtxn);
1558
0
  }
1559
1560
  /* cleanup changes in the txn */
1561
0
  dlist_foreach_modify(iter, &txn->changes)
1562
0
  {
1563
0
    ReorderBufferChange *change;
1564
1565
0
    change = dlist_container(ReorderBufferChange, node, iter.cur);
1566
1567
    /* Check we're not mixing changes from different transactions. */
1568
0
    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
0
    mem_freed += ReorderBufferChangeSize(change);
1577
1578
0
    ReorderBufferFreeChange(rb, change, false);
1579
0
  }
1580
1581
  /* Update the memory counter */
1582
0
  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
0
  dlist_foreach_modify(iter, &txn->tuplecids)
1589
0
  {
1590
0
    ReorderBufferChange *change;
1591
1592
0
    change = dlist_container(ReorderBufferChange, node, iter.cur);
1593
1594
    /* Check we're not mixing changes from different transactions. */
1595
0
    Assert(change->txn == txn);
1596
0
    Assert(change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
1597
1598
0
    ReorderBufferFreeChange(rb, change, true);
1599
0
  }
1600
1601
  /*
1602
   * Cleanup the base snapshot, if set.
1603
   */
1604
0
  if (txn->base_snapshot != NULL)
1605
0
  {
1606
0
    SnapBuildSnapDecRefcount(txn->base_snapshot);
1607
0
    dlist_delete(&txn->base_snapshot_node);
1608
0
  }
1609
1610
  /*
1611
   * Cleanup the snapshot for the last streamed run.
1612
   */
1613
0
  if (txn->snapshot_now != NULL)
1614
0
  {
1615
0
    Assert(rbtxn_is_streamed(txn));
1616
0
    ReorderBufferFreeSnap(rb, txn->snapshot_now);
1617
0
  }
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
0
  dlist_delete(&txn->node);
1629
0
  if (rbtxn_has_catalog_changes(txn))
1630
0
    dclist_delete_from(&rb->catchange_txns, &txn->catchange_node);
1631
1632
  /* now remove reference from buffer */
1633
0
  hash_search(rb->by_txn, &txn->xid, HASH_REMOVE, &found);
1634
0
  Assert(found);
1635
1636
  /* remove entries spilled to disk */
1637
0
  if (rbtxn_is_serialized(txn))
1638
0
    ReorderBufferRestoreCleanup(rb, txn);
1639
1640
  /* deallocate */
1641
0
  ReorderBufferFreeTXN(rb, txn);
1642
0
}
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
ReorderBufferTruncateTXN(ReorderBuffer *rb, ReorderBufferTXN *txn, bool txn_prepared)
1658
0
{
1659
0
  dlist_mutable_iter iter;
1660
0
  Size    mem_freed = 0;
1661
1662
  /* cleanup subtransactions & their changes */
1663
0
  dlist_foreach_modify(iter, &txn->subtxns)
1664
0
  {
1665
0
    ReorderBufferTXN *subtxn;
1666
1667
0
    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
0
    Assert(rbtxn_is_known_subxact(subtxn));
1675
0
    Assert(subtxn->nsubtxns == 0);
1676
1677
0
    ReorderBufferMaybeMarkTXNStreamed(rb, subtxn);
1678
0
    ReorderBufferTruncateTXN(rb, subtxn, txn_prepared);
1679
0
  }
1680
1681
  /* cleanup changes in the txn */
1682
0
  dlist_foreach_modify(iter, &txn->changes)
1683
0
  {
1684
0
    ReorderBufferChange *change;
1685
1686
0
    change = dlist_container(ReorderBufferChange, node, iter.cur);
1687
1688
    /* Check we're not mixing changes from different transactions. */
1689
0
    Assert(change->txn == txn);
1690
1691
    /* remove the change from its containing list */
1692
0
    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
0
    mem_freed += ReorderBufferChangeSize(change);
1701
1702
0
    ReorderBufferFreeChange(rb, change, false);
1703
0
  }
1704
1705
  /* Update the memory counter */
1706
0
  ReorderBufferChangeMemoryUpdate(rb, NULL, txn, false, mem_freed);
1707
1708
0
  if (txn_prepared)
1709
0
  {
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
0
    dlist_foreach_modify(iter, &txn->tuplecids)
1716
0
    {
1717
0
      ReorderBufferChange *change;
1718
1719
0
      change = dlist_container(ReorderBufferChange, node, iter.cur);
1720
1721
      /* Check we're not mixing changes from different transactions. */
1722
0
      Assert(change->txn == txn);
1723
0
      Assert(change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
1724
1725
      /* Remove the change from its containing list. */
1726
0
      dlist_delete(&change->node);
1727
1728
0
      ReorderBufferFreeChange(rb, change, true);
1729
0
    }
1730
0
  }
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
0
  if (txn->tuplecid_hash != NULL)
1738
0
  {
1739
0
    hash_destroy(txn->tuplecid_hash);
1740
0
    txn->tuplecid_hash = NULL;
1741
0
  }
1742
1743
  /* If this txn is serialized then clean the disk space. */
1744
0
  if (rbtxn_is_serialized(txn))
1745
0
  {
1746
0
    ReorderBufferRestoreCleanup(rb, txn);
1747
0
    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
0
    txn->txn_flags |= RBTXN_IS_SERIALIZED_CLEAR;
1755
0
  }
1756
1757
  /* also reset the number of entries in the transaction */
1758
0
  txn->nentries_mem = 0;
1759
0
  txn->nentries = 0;
1760
0
}
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
ReorderBufferCheckAndTruncateAbortedTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
1776
0
{
1777
  /* Quick return for regression tests */
1778
0
  if (unlikely(debug_logical_replication_streaming == DEBUG_LOGICAL_REP_STREAMING_IMMEDIATE))
1779
0
    return false;
1780
1781
  /*
1782
   * Quick return if the transaction status is already known.
1783
   */
1784
1785
0
  if (rbtxn_is_committed(txn))
1786
0
    return false;
1787
0
  if (rbtxn_is_aborted(txn))
1788
0
  {
1789
    /* Already-aborted transactions should not have any changes */
1790
0
    Assert(txn->size == 0);
1791
1792
0
    return true;
1793
0
  }
1794
1795
  /* Otherwise, check the transaction status using CLOG lookup */
1796
1797
0
  if (TransactionIdIsInProgress(txn->xid))
1798
0
    return false;
1799
1800
0
  if (TransactionIdDidCommit(txn->xid))
1801
0
  {
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
0
    Assert(!rbtxn_is_aborted(txn));
1807
0
    txn->txn_flags |= RBTXN_IS_COMMITTED;
1808
0
    return false;
1809
0
  }
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
0
  ReorderBufferTruncateTXN(rb, txn, rbtxn_is_prepared(txn));
1817
0
  ReorderBufferToastReset(rb, txn);
1818
1819
  /* All changes should be discarded */
1820
0
  Assert(txn->size == 0);
1821
1822
  /*
1823
   * Mark the transaction as aborted so we can ignore future changes of this
1824
   * transaction.
1825
   */
1826
0
  Assert(!rbtxn_is_committed(txn));
1827
0
  txn->txn_flags |= RBTXN_IS_ABORTED;
1828
1829
0
  return true;
1830
0
}
1831
1832
/*
1833
 * Build a hash with a (relfilelocator, ctid) -> (cmin, cmax) mapping for use by
1834
 * HeapTupleSatisfiesHistoricMVCC.
1835
 */
1836
static void
1837
ReorderBufferBuildTupleCidHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
1838
0
{
1839
0
  dlist_iter  iter;
1840
0
  HASHCTL   hash_ctl;
1841
1842
0
  if (!rbtxn_has_catalog_changes(txn) || dlist_is_empty(&txn->tuplecids))
1843
0
    return;
1844
1845
0
  hash_ctl.keysize = sizeof(ReorderBufferTupleCidKey);
1846
0
  hash_ctl.entrysize = sizeof(ReorderBufferTupleCidEnt);
1847
0
  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
0
  txn->tuplecid_hash =
1854
0
    hash_create("ReorderBufferTupleCid", txn->ntuplecids, &hash_ctl,
1855
0
          HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
1856
1857
0
  dlist_foreach(iter, &txn->tuplecids)
1858
0
  {
1859
0
    ReorderBufferTupleCidKey key;
1860
0
    ReorderBufferTupleCidEnt *ent;
1861
0
    bool    found;
1862
0
    ReorderBufferChange *change;
1863
1864
0
    change = dlist_container(ReorderBufferChange, node, iter.cur);
1865
1866
0
    Assert(change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID);
1867
1868
    /* be careful about padding */
1869
0
    memset(&key, 0, sizeof(ReorderBufferTupleCidKey));
1870
1871
0
    key.rlocator = change->data.tuplecid.locator;
1872
1873
0
    ItemPointerCopy(&change->data.tuplecid.tid,
1874
0
            &key.tid);
1875
1876
0
    ent = (ReorderBufferTupleCidEnt *)
1877
0
      hash_search(txn->tuplecid_hash, &key, HASH_ENTER, &found);
1878
0
    if (!found)
1879
0
    {
1880
0
      ent->cmin = change->data.tuplecid.cmin;
1881
0
      ent->cmax = change->data.tuplecid.cmax;
1882
0
      ent->combocid = change->data.tuplecid.combocid;
1883
0
    }
1884
0
    else
1885
0
    {
1886
      /*
1887
       * Maybe we already saw this tuple before in this transaction, but
1888
       * if so it must have the same cmin.
1889
       */
1890
0
      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
0
      Assert((ent->cmax == InvalidCommandId) ||
1897
0
           ((change->data.tuplecid.cmax != InvalidCommandId) &&
1898
0
          (change->data.tuplecid.cmax > ent->cmax)));
1899
0
      ent->cmax = change->data.tuplecid.cmax;
1900
0
    }
1901
0
  }
1902
0
}
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
ReorderBufferCopySnap(ReorderBuffer *rb, Snapshot orig_snap,
1911
            ReorderBufferTXN *txn, CommandId cid)
1912
0
{
1913
0
  Snapshot  snap;
1914
0
  dlist_iter  iter;
1915
0
  int     i = 0;
1916
0
  Size    size;
1917
1918
0
  size = sizeof(SnapshotData) +
1919
0
    sizeof(TransactionId) * orig_snap->xcnt +
1920
0
    sizeof(TransactionId) * (txn->nsubtxns + 1);
1921
1922
0
  snap = MemoryContextAllocZero(rb->context, size);
1923
0
  memcpy(snap, orig_snap, sizeof(SnapshotData));
1924
1925
0
  snap->copied = true;
1926
0
  snap->active_count = 1;   /* mark as active so nobody frees it */
1927
0
  snap->regd_count = 0;
1928
0
  snap->xip = (TransactionId *) (snap + 1);
1929
1930
0
  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
0
  snap->subxip = snap->xip + snap->xcnt;
1938
0
  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
0
  snap->subxcnt = 1;
1946
1947
0
  dlist_foreach(iter, &txn->subtxns)
1948
0
  {
1949
0
    ReorderBufferTXN *sub_txn;
1950
1951
0
    sub_txn = dlist_container(ReorderBufferTXN, node, iter.cur);
1952
0
    snap->subxip[i++] = sub_txn->xid;
1953
0
    snap->subxcnt++;
1954
0
  }
1955
1956
  /* sort so we can bsearch() later */
1957
0
  qsort(snap->subxip, snap->subxcnt, sizeof(TransactionId), xidComparator);
1958
1959
  /* store the specified current CommandId */
1960
0
  snap->curcid = cid;
1961
1962
0
  return snap;
1963
0
}
1964
1965
/*
1966
 * Free a previously ReorderBufferCopySnap'ed snapshot
1967
 */
1968
static void
1969
ReorderBufferFreeSnap(ReorderBuffer *rb, Snapshot snap)
1970
0
{
1971
0
  if (snap->copied)
1972
0
    pfree(snap);
1973
0
  else
1974
0
    SnapBuildSnapDecRefcount(snap);
1975
0
}
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
ReorderBufferStreamCommit(ReorderBuffer *rb, ReorderBufferTXN *txn)
1985
0
{
1986
  /* we should only call this for previously streamed transactions */
1987
0
  Assert(rbtxn_is_streamed(txn));
1988
1989
0
  ReorderBufferStreamTXN(rb, txn);
1990
1991
0
  if (rbtxn_is_prepared(txn))
1992
0
  {
1993
    /*
1994
     * Note, we send stream prepare even if a concurrent abort is
1995
     * detected. See DecodePrepare for more information.
1996
     */
1997
0
    Assert(!rbtxn_sent_prepare(txn));
1998
0
    rb->stream_prepare(rb, txn, txn->final_lsn);
1999
0
    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
0
    ReorderBufferTruncateTXN(rb, txn, true);
2007
    /* Reset the CheckXidAlive */
2008
0
    CheckXidAlive = InvalidTransactionId;
2009
0
  }
2010
0
  else
2011
0
  {
2012
0
    rb->stream_commit(rb, txn, txn->final_lsn);
2013
0
    ReorderBufferCleanupTXN(rb, txn);
2014
0
  }
2015
0
}
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
SetupCheckXidLive(TransactionId xid)
2051
0
{
2052
  /*
2053
   * If the input transaction id is already set as a CheckXidAlive then
2054
   * nothing to do.
2055
   */
2056
0
  if (TransactionIdEquals(CheckXidAlive, xid))
2057
0
    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
0
  if (!TransactionIdDidCommit(xid))
2064
0
    CheckXidAlive = xid;
2065
0
  else
2066
0
    CheckXidAlive = InvalidTransactionId;
2067
0
}
2068
2069
/*
2070
 * Helper function for ReorderBufferProcessTXN for applying change.
2071
 */
2072
static inline void
2073
ReorderBufferApplyChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
2074
             Relation relation, ReorderBufferChange *change,
2075
             bool streaming)
2076
0
{
2077
0
  if (streaming)
2078
0
    rb->stream_change(rb, txn, relation, change);
2079
0
  else
2080
0
    rb->apply_change(rb, txn, relation, change);
2081
0
}
2082
2083
/*
2084
 * Helper function for ReorderBufferProcessTXN for applying the truncate.
2085
 */
2086
static inline void
2087
ReorderBufferApplyTruncate(ReorderBuffer *rb, ReorderBufferTXN *txn,
2088
               int nrelations, Relation *relations,
2089
               ReorderBufferChange *change, bool streaming)
2090
0
{
2091
0
  if (streaming)
2092
0
    rb->stream_truncate(rb, txn, nrelations, relations, change);
2093
0
  else
2094
0
    rb->apply_truncate(rb, txn, nrelations, relations, change);
2095
0
}
2096
2097
/*
2098
 * Helper function for ReorderBufferProcessTXN for applying the message.
2099
 */
2100
static inline void
2101
ReorderBufferApplyMessage(ReorderBuffer *rb, ReorderBufferTXN *txn,
2102
              ReorderBufferChange *change, bool streaming)
2103
0
{
2104
0
  if (streaming)
2105
0
    rb->stream_message(rb, txn, change->lsn, true,
2106
0
               change->data.msg.prefix,
2107
0
               change->data.msg.message_size,
2108
0
               change->data.msg.message);
2109
0
  else
2110
0
    rb->message(rb, txn, change->lsn, true,
2111
0
          change->data.msg.prefix,
2112
0
          change->data.msg.message_size,
2113
0
          change->data.msg.message);
2114
0
}
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
ReorderBufferSaveTXNSnapshot(ReorderBuffer *rb, ReorderBufferTXN *txn,
2122
               Snapshot snapshot_now, CommandId command_id)
2123
0
{
2124
0
  txn->command_id = command_id;
2125
2126
  /* Avoid copying if it's already copied. */
2127
0
  if (snapshot_now->copied)
2128
0
    txn->snapshot_now = snapshot_now;
2129
0
  else
2130
0
    txn->snapshot_now = ReorderBufferCopySnap(rb, snapshot_now,
2131
0
                          txn, command_id);
2132
0
}
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
ReorderBufferMaybeMarkTXNStreamed(ReorderBuffer *rb, ReorderBufferTXN *txn)
2140
0
{
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
0
  if (rbtxn_is_toptxn(txn) || (txn->nentries_mem != 0))
2155
0
    txn->txn_flags |= RBTXN_IS_STREAMED;
2156
0
}
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
ReorderBufferResetTXN(ReorderBuffer *rb, ReorderBufferTXN *txn,
2167
            Snapshot snapshot_now,
2168
            CommandId command_id,
2169
            XLogRecPtr last_lsn)
2170
0
{
2171
  /* Discard the changes that we just streamed */
2172
0
  ReorderBufferTruncateTXN(rb, txn, rbtxn_is_prepared(txn));
2173
2174
  /* Free all resources allocated for toast reconstruction */
2175
0
  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
0
  if (rbtxn_is_streamed(txn))
2182
0
  {
2183
0
    rb->stream_stop(rb, txn, last_lsn);
2184
0
    ReorderBufferSaveTXNSnapshot(rb, txn, snapshot_now, command_id);
2185
0
  }
2186
2187
  /* All changes must be deallocated */
2188
0
  Assert(txn->size == 0);
2189
0
}
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
ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn,
2205
            XLogRecPtr commit_lsn,
2206
            volatile Snapshot snapshot_now,
2207
            volatile CommandId command_id,
2208
            bool streaming)
2209
0
{
2210
0
  bool    using_subtxn;
2211
0
  MemoryContext ccxt = CurrentMemoryContext;
2212
0
  ResourceOwner cowner = CurrentResourceOwner;
2213
0
  ReorderBufferIterTXNState *volatile iterstate = NULL;
2214
0
  volatile XLogRecPtr prev_lsn = InvalidXLogRecPtr;
2215
0
  ReorderBufferChange *volatile specinsert = NULL;
2216
0
  volatile bool stream_started = false;
2217
0
  ReorderBufferTXN *volatile curtxn = NULL;
2218
2219
  /* build data to be able to lookup the CommandIds of catalog tuples */
2220
0
  ReorderBufferBuildTupleCidHash(rb, txn);
2221
2222
  /* setup the initial snapshot */
2223
0
  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
0
  using_subtxn = IsTransactionOrTransactionBlock();
2236
2237
0
  PG_TRY();
2238
0
  {
2239
0
    ReorderBufferChange *change;
2240
0
    int     changes_count = 0;  /* used to accumulate the number of
2241
                     * changes */
2242
2243
0
    if (using_subtxn)
2244
0
      BeginInternalSubTransaction(streaming ? "stream" : "replay");
2245
0
    else
2246
0
      StartTransactionCommand();
2247
2248
    /*
2249
     * We only need to send begin/begin-prepare for non-streamed
2250
     * transactions.
2251
     */
2252
0
    if (!streaming)
2253
0
    {
2254
0
      if (rbtxn_is_prepared(txn))
2255
0
        rb->begin_prepare(rb, txn);
2256
0
      else
2257
0
        rb->begin(rb, txn);
2258
0
    }
2259
2260
0
    ReorderBufferIterTXNInit(rb, txn, &iterstate);
2261
0
    while ((change = ReorderBufferIterTXNNext(rb, iterstate)) != NULL)
2262
0
    {
2263
0
      Relation  relation = NULL;
2264
0
      Oid     reloid;
2265
2266
0
      CHECK_FOR_INTERRUPTS();
2267
2268
      /*
2269
       * We can't call start stream callback before processing first
2270
       * change.
2271
       */
2272
0
      if (!XLogRecPtrIsValid(prev_lsn))
2273
0
      {
2274
0
        if (streaming)
2275
0
        {
2276
0
          txn->origin_id = change->origin_id;
2277
0
          rb->stream_start(rb, txn, change->lsn);
2278
0
          stream_started = true;
2279
0
        }
2280
0
      }
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
0
      Assert(!XLogRecPtrIsValid(prev_lsn) || prev_lsn <= change->lsn);
2288
2289
0
      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
0
      if (streaming || rbtxn_is_prepared(change->txn))
2297
0
      {
2298
0
        curtxn = change->txn;
2299
0
        SetupCheckXidLive(curtxn->xid);
2300
0
      }
2301
2302
0
      switch (change->action)
2303
0
      {
2304
0
        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
0
          if (specinsert == NULL)
2312
0
            elog(ERROR, "invalid ordering of speculative insertion changes");
2313
0
          Assert(specinsert->data.tp.oldtuple == NULL);
2314
0
          change = specinsert;
2315
0
          change->action = REORDER_BUFFER_CHANGE_INSERT;
2316
2317
          /* intentionally fall through */
2318
0
          pg_fallthrough;
2319
0
        case REORDER_BUFFER_CHANGE_INSERT:
2320
0
        case REORDER_BUFFER_CHANGE_UPDATE:
2321
0
        case REORDER_BUFFER_CHANGE_DELETE:
2322
0
          Assert(snapshot_now);
2323
2324
0
          reloid = RelidByRelfilenumber(change->data.tp.rlocator.spcOid,
2325
0
                          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
0
          if (reloid == InvalidOid &&
2340
0
            change->data.tp.newtuple == NULL &&
2341
0
            change->data.tp.oldtuple == NULL)
2342
0
            goto change_done;
2343
0
          else if (reloid == InvalidOid)
2344
0
            elog(ERROR, "could not map filenumber \"%s\" to relation OID",
2345
0
               relpathperm(change->data.tp.rlocator,
2346
0
                     MAIN_FORKNUM).str);
2347
2348
0
          relation = RelationIdGetRelation(reloid);
2349
2350
0
          if (!RelationIsValid(relation))
2351
0
            elog(ERROR, "could not open relation with OID %u (for filenumber \"%s\")",
2352
0
               reloid,
2353
0
               relpathperm(change->data.tp.rlocator,
2354
0
                     MAIN_FORKNUM).str);
2355
2356
0
          if (!RelationIsLogicallyLogged(relation))
2357
0
            goto change_done;
2358
2359
          /*
2360
           * Ignore temporary heaps created during DDL unless the
2361
           * plugin has asked for them.
2362
           */
2363
0
          if (relation->rd_rel->relrewrite && !rb->output_rewrites)
2364
0
            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
0
          if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
2373
0
            goto change_done;
2374
2375
          /* user-triggered change */
2376
0
          if (!IsToastRelation(relation))
2377
0
          {
2378
0
            ReorderBufferToastReplace(rb, txn, relation, change);
2379
0
            ReorderBufferApplyChange(rb, txn, relation, change,
2380
0
                         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
0
            if (change->data.tp.clear_toast_afterwards)
2388
0
              ReorderBufferToastReset(rb, txn);
2389
0
          }
2390
          /* we're not interested in toast deletions */
2391
0
          else if (change->action == REORDER_BUFFER_CHANGE_INSERT)
2392
0
          {
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
0
            Assert(change->data.tp.newtuple != NULL);
2402
2403
0
            dlist_delete(&change->node);
2404
0
            ReorderBufferToastAppendChunk(rb, txn, relation,
2405
0
                            change);
2406
0
          }
2407
2408
0
      change_done:
2409
2410
          /*
2411
           * If speculative insertion was confirmed, the record
2412
           * isn't needed anymore.
2413
           */
2414
0
          if (specinsert != NULL)
2415
0
          {
2416
0
            ReorderBufferFreeChange(rb, specinsert, true);
2417
0
            specinsert = NULL;
2418
0
          }
2419
2420
0
          if (RelationIsValid(relation))
2421
0
          {
2422
0
            RelationClose(relation);
2423
0
            relation = NULL;
2424
0
          }
2425
0
          break;
2426
2427
0
        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
0
          Assert(specinsert == NULL);
2444
2445
          /* and memorize the pending insertion */
2446
0
          dlist_delete(&change->node);
2447
0
          specinsert = change;
2448
0
          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
0
          {
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
0
            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
0
          }
2474
0
          break;
2475
2476
0
        case REORDER_BUFFER_CHANGE_TRUNCATE:
2477
0
          {
2478
0
            int     i;
2479
0
            int     nrelids = change->data.truncate.nrelids;
2480
0
            int     nrelations = 0;
2481
0
            Relation   *relations;
2482
2483
0
            relations = palloc0_array(Relation, nrelids);
2484
0
            for (i = 0; i < nrelids; i++)
2485
0
            {
2486
0
              Oid     relid = change->data.truncate.relids[i];
2487
0
              Relation  rel;
2488
2489
0
              rel = RelationIdGetRelation(relid);
2490
2491
0
              if (!RelationIsValid(rel))
2492
0
                elog(ERROR, "could not open relation with OID %u", relid);
2493
2494
0
              if (!RelationIsLogicallyLogged(rel))
2495
0
                continue;
2496
2497
0
              relations[nrelations++] = rel;
2498
0
            }
2499
2500
            /* Apply the truncate. */
2501
0
            ReorderBufferApplyTruncate(rb, txn, nrelations,
2502
0
                           relations, change,
2503
0
                           streaming);
2504
2505
0
            for (i = 0; i < nrelations; i++)
2506
0
              RelationClose(relations[i]);
2507
2508
0
            break;
2509
0
          }
2510
2511
0
        case REORDER_BUFFER_CHANGE_MESSAGE:
2512
0
          ReorderBufferApplyMessage(rb, txn, change, streaming);
2513
0
          break;
2514
2515
0
        case REORDER_BUFFER_CHANGE_INVALIDATION:
2516
          /* Execute the invalidation messages locally */
2517
0
          ReorderBufferExecuteInvalidations(change->data.inval.ninvalidations,
2518
0
                            change->data.inval.invalidations);
2519
0
          break;
2520
2521
0
        case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
2522
          /* get rid of the old */
2523
0
          TeardownHistoricSnapshot(false);
2524
2525
0
          if (snapshot_now->copied)
2526
0
          {
2527
0
            ReorderBufferFreeSnap(rb, snapshot_now);
2528
0
            snapshot_now =
2529
0
              ReorderBufferCopySnap(rb, change->data.snapshot,
2530
0
                          txn, command_id);
2531
0
          }
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
0
          else if (change->data.snapshot->copied)
2539
0
          {
2540
0
            snapshot_now =
2541
0
              ReorderBufferCopySnap(rb, change->data.snapshot,
2542
0
                          txn, command_id);
2543
0
          }
2544
0
          else
2545
0
          {
2546
0
            snapshot_now = change->data.snapshot;
2547
0
          }
2548
2549
          /* and continue with the new one */
2550
0
          SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash);
2551
0
          break;
2552
2553
0
        case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
2554
0
          Assert(change->data.command_id != InvalidCommandId);
2555
2556
0
          if (command_id < change->data.command_id)
2557
0
          {
2558
0
            command_id = change->data.command_id;
2559
2560
0
            if (!snapshot_now->copied)
2561
0
            {
2562
              /* we don't use the global one anymore */
2563
0
              snapshot_now = ReorderBufferCopySnap(rb, snapshot_now,
2564
0
                                 txn, command_id);
2565
0
            }
2566
2567
0
            snapshot_now->curcid = command_id;
2568
2569
0
            TeardownHistoricSnapshot(false);
2570
0
            SetupHistoricSnapshot(snapshot_now, txn->tuplecid_hash);
2571
0
          }
2572
2573
0
          break;
2574
2575
0
        case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
2576
0
          elog(ERROR, "tuplecid value in changequeue");
2577
0
          break;
2578
0
      }
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
0
#define CHANGES_THRESHOLD 100
2591
2592
0
      if (++changes_count >= CHANGES_THRESHOLD)
2593
0
      {
2594
0
        rb->update_progress_txn(rb, txn, prev_lsn);
2595
0
        changes_count = 0;
2596
0
      }
2597
0
    }
2598
2599
    /* speculative insertion record must be freed by now */
2600
0
    Assert(!specinsert);
2601
2602
    /* clean up the iterator */
2603
0
    ReorderBufferIterTXNFinish(rb, iterstate);
2604
0
    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
0
    if (!rbtxn_is_streamed(txn))
2616
0
      rb->totalTxns++;
2617
2618
0
    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
0
    if (streaming)
2625
0
    {
2626
0
      if (stream_started)
2627
0
      {
2628
0
        rb->stream_stop(rb, txn, prev_lsn);
2629
0
        stream_started = false;
2630
0
      }
2631
0
    }
2632
0
    else
2633
0
    {
2634
      /*
2635
       * Call either PREPARE (for two-phase transactions) or COMMIT (for
2636
       * regular ones).
2637
       */
2638
0
      if (rbtxn_is_prepared(txn))
2639
0
      {
2640
0
        Assert(!rbtxn_sent_prepare(txn));
2641
0
        rb->prepare(rb, txn, commit_lsn);
2642
0
        txn->txn_flags |= RBTXN_SENT_PREPARE;
2643
0
      }
2644
0
      else
2645
0
        rb->commit(rb, txn, commit_lsn);
2646
0
    }
2647
2648
    /* this is just a sanity check against bad output plugin behaviour */
2649
0
    if (GetCurrentTransactionIdIfAny() != InvalidTransactionId)
2650
0
      elog(ERROR, "output plugin used XID %u",
2651
0
         GetCurrentTransactionId());
2652
2653
    /*
2654
     * Remember the command ID and snapshot for the next set of changes in
2655
     * streaming mode.
2656
     */
2657
0
    if (streaming)
2658
0
      ReorderBufferSaveTXNSnapshot(rb, txn, snapshot_now, command_id);
2659
0
    else if (snapshot_now->copied)
2660
0
      ReorderBufferFreeSnap(rb, snapshot_now);
2661
2662
    /* cleanup */
2663
0
    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
0
    AbortCurrentTransaction();
2672
2673
    /* make sure there's no cache pollution */
2674
0
    if (rbtxn_distr_inval_overflowed(txn))
2675
0
    {
2676
0
      Assert(txn->ninvalidations_distributed == 0);
2677
0
      InvalidateSystemCaches();
2678
0
    }
2679
0
    else
2680
0
    {
2681
0
      ReorderBufferExecuteInvalidations(txn->ninvalidations, txn->invalidations);
2682
0
      ReorderBufferExecuteInvalidations(txn->ninvalidations_distributed,
2683
0
                        txn->invalidations_distributed);
2684
0
    }
2685
2686
0
    if (using_subtxn)
2687
0
    {
2688
0
      RollbackAndReleaseCurrentSubTransaction();
2689
0
      MemoryContextSwitchTo(ccxt);
2690
0
      CurrentResourceOwner = cowner;
2691
0
    }
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
0
    if (streaming || rbtxn_is_prepared(txn))
2708
0
    {
2709
0
      if (streaming)
2710
0
        ReorderBufferMaybeMarkTXNStreamed(rb, txn);
2711
2712
0
      ReorderBufferTruncateTXN(rb, txn, rbtxn_is_prepared(txn));
2713
      /* Reset the CheckXidAlive */
2714
0
      CheckXidAlive = InvalidTransactionId;
2715
0
    }
2716
0
    else
2717
0
      ReorderBufferCleanupTXN(rb, txn);
2718
0
  }
2719
0
  PG_CATCH();
2720
0
  {
2721
0
    MemoryContext ecxt = MemoryContextSwitchTo(ccxt);
2722
0
    ErrorData  *errdata = CopyErrorData();
2723
2724
    /* TODO: Encapsulate cleanup from the PG_TRY and PG_CATCH blocks */
2725
0
    if (iterstate)
2726
0
      ReorderBufferIterTXNFinish(rb, iterstate);
2727
2728
0
    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
0
    AbortCurrentTransaction();
2735
2736
    /* make sure there's no cache pollution */
2737
0
    if (rbtxn_distr_inval_overflowed(txn))
2738
0
    {
2739
0
      Assert(txn->ninvalidations_distributed == 0);
2740
0
      InvalidateSystemCaches();
2741
0
    }
2742
0
    else
2743
0
    {
2744
0
      ReorderBufferExecuteInvalidations(txn->ninvalidations, txn->invalidations);
2745
0
      ReorderBufferExecuteInvalidations(txn->ninvalidations_distributed,
2746
0
                        txn->invalidations_distributed);
2747
0
    }
2748
2749
0
    if (using_subtxn)
2750
0
    {
2751
0
      RollbackAndReleaseCurrentSubTransaction();
2752
0
      MemoryContextSwitchTo(ccxt);
2753
0
      CurrentResourceOwner = cowner;
2754
0
    }
2755
2756
    /* Free the specinsert change before freeing the ReorderBufferTXN */
2757
0
    if (specinsert != NULL)
2758
0
    {
2759
0
      ReorderBufferFreeChange(rb, specinsert, true);
2760
0
      specinsert = NULL;
2761
0
    }
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
0
    if (errdata->sqlerrcode == ERRCODE_TRANSACTION_ROLLBACK &&
2776
0
      (stream_started || rbtxn_is_prepared(txn)))
2777
0
    {
2778
      /* curtxn must be set for streaming or prepared transactions */
2779
0
      Assert(curtxn);
2780
2781
      /* Cleanup the temporary error state. */
2782
0
      FlushErrorState();
2783
0
      FreeErrorData(errdata);
2784
0
      errdata = NULL;
2785
2786
      /* Remember the transaction is aborted. */
2787
0
      Assert(!rbtxn_is_committed(curtxn));
2788
0
      curtxn->txn_flags |= RBTXN_IS_ABORTED;
2789
2790
      /* Mark the transaction is streamed if appropriate */
2791
0
      if (stream_started)
2792
0
        ReorderBufferMaybeMarkTXNStreamed(rb, txn);
2793
2794
      /* Reset the TXN so that it is allowed to stream remaining data. */
2795
0
      ReorderBufferResetTXN(rb, txn, snapshot_now,
2796
0
                  command_id, prev_lsn);
2797
0
    }
2798
0
    else
2799
0
    {
2800
0
      ReorderBufferCleanupTXN(rb, txn);
2801
0
      MemoryContextSwitchTo(ecxt);
2802
0
      PG_RE_THROW();
2803
0
    }
2804
0
  }
2805
0
  PG_END_TRY();
2806
0
}
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
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
0
{
2825
0
  Snapshot  snapshot_now;
2826
0
  CommandId command_id = FirstCommandId;
2827
2828
0
  txn->final_lsn = commit_lsn;
2829
0
  txn->end_lsn = end_lsn;
2830
0
  txn->commit_time = commit_time;
2831
0
  txn->origin_id = origin_id;
2832
0
  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
0
  if (rbtxn_is_streamed(txn))
2843
0
  {
2844
0
    ReorderBufferStreamCommit(rb, txn);
2845
0
    return;
2846
0
  }
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
0
  if (txn->base_snapshot == NULL)
2855
0
  {
2856
0
    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
0
    if (!rbtxn_is_prepared(txn))
2863
0
      ReorderBufferCleanupTXN(rb, txn);
2864
0
    return;
2865
0
  }
2866
2867
0
  snapshot_now = txn->base_snapshot;
2868
2869
  /* Process and send the changes to output plugin. */
2870
0
  ReorderBufferProcessTXN(rb, txn, commit_lsn, snapshot_now,
2871
0
              command_id, false);
2872
0
}
2873
2874
/*
2875
 * Commit a transaction.
2876
 *
2877
 * See comments for ReorderBufferReplay().
2878
 */
2879
void
2880
ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
2881
          XLogRecPtr commit_lsn, XLogRecPtr end_lsn,
2882
          TimestampTz commit_time,
2883
          ReplOriginId origin_id, XLogRecPtr origin_lsn)
2884
0
{
2885
0
  ReorderBufferTXN *txn;
2886
2887
0
  txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
2888
0
                false);
2889
2890
  /* unknown transaction, nothing to replay */
2891
0
  if (txn == NULL)
2892
0
    return;
2893
2894
0
  ReorderBufferReplay(txn, rb, xid, commit_lsn, end_lsn, commit_time,
2895
0
            origin_id, origin_lsn);
2896
0
}
2897
2898
/*
2899
 * Record the prepare information for a transaction. Also, mark the transaction
2900
 * as a prepared transaction.
2901
 */
2902
bool
2903
ReorderBufferRememberPrepareInfo(ReorderBuffer *rb, TransactionId xid,
2904
                 XLogRecPtr prepare_lsn, XLogRecPtr end_lsn,
2905
                 TimestampTz prepare_time,
2906
                 ReplOriginId origin_id, XLogRecPtr origin_lsn)
2907
0
{
2908
0
  ReorderBufferTXN *txn;
2909
2910
0
  txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr, false);
2911
2912
  /* unknown transaction, nothing to do */
2913
0
  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
0
  txn->final_lsn = prepare_lsn;
2921
0
  txn->end_lsn = end_lsn;
2922
0
  txn->prepare_time = prepare_time;
2923
0
  txn->origin_id = origin_id;
2924
0
  txn->origin_lsn = origin_lsn;
2925
2926
  /* Mark this transaction as a prepared transaction */
2927
0
  Assert((txn->txn_flags & RBTXN_PREPARE_STATUS_MASK) == 0);
2928
0
  txn->txn_flags |= RBTXN_IS_PREPARED;
2929
2930
0
  return true;
2931
0
}
2932
2933
/* Remember that we have skipped prepare */
2934
void
2935
ReorderBufferSkipPrepare(ReorderBuffer *rb, TransactionId xid)
2936
0
{
2937
0
  ReorderBufferTXN *txn;
2938
2939
0
  txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr, false);
2940
2941
  /* unknown transaction, nothing to do */
2942
0
  if (txn == NULL)
2943
0
    return;
2944
2945
  /* txn must have been marked as a prepared transaction */
2946
0
  Assert((txn->txn_flags & RBTXN_PREPARE_STATUS_MASK) == RBTXN_IS_PREPARED);
2947
0
  txn->txn_flags |= RBTXN_SKIPPED_PREPARE;
2948
0
}
2949
2950
/*
2951
 * Prepare a two-phase transaction.
2952
 *
2953
 * See comments for ReorderBufferReplay().
2954
 */
2955
void
2956
ReorderBufferPrepare(ReorderBuffer *rb, TransactionId xid,
2957
           char *gid)
2958
0
{
2959
0
  ReorderBufferTXN *txn;
2960
2961
0
  txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
2962
0
                false);
2963
2964
  /* unknown transaction, nothing to replay */
2965
0
  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
0
  Assert((txn->txn_flags & RBTXN_PREPARE_STATUS_MASK) == RBTXN_IS_PREPARED);
2974
0
  Assert(XLogRecPtrIsValid(txn->final_lsn));
2975
2976
0
  txn->gid = pstrdup(gid);
2977
2978
0
  ReorderBufferReplay(txn, rb, xid, txn->final_lsn, txn->end_lsn,
2979
0
            txn->prepare_time, txn->origin_id, txn->origin_lsn);
2980
2981
  /*
2982
   * Send a prepare if not already done so. The "not already sent" case can
2983
   * occur if we have detected a concurrent abort while replaying the
2984
   * non-streaming transaction; we still send the prepare so that later when
2985
   * rollback prepared is decoded and sent, the downstream should be able to
2986
   * rollback such a xact. See comments atop DecodePrepare.
2987
   *
2988
   * Skip this for a transaction that made no changes to the database (i.e.
2989
   * has no base snapshot), as we haven't sent any changes for it. Such a
2990
   * transaction is cleaned up without invoking the commit/rollback prepared
2991
   * callbacks in ReorderBufferFinishPrepared().
2992
   */
2993
0
  if (!rbtxn_sent_prepare(txn) && txn->base_snapshot != NULL)
2994
0
  {
2995
0
    rb->prepare(rb, txn, txn->final_lsn);
2996
0
    txn->txn_flags |= RBTXN_SENT_PREPARE;
2997
0
  }
2998
0
}
2999
3000
/*
3001
 * This is used to handle COMMIT/ROLLBACK PREPARED.
3002
 */
3003
void
3004
ReorderBufferFinishPrepared(ReorderBuffer *rb, TransactionId xid,
3005
              XLogRecPtr commit_lsn, XLogRecPtr end_lsn,
3006
              XLogRecPtr two_phase_at,
3007
              TimestampTz commit_time, ReplOriginId origin_id,
3008
              XLogRecPtr origin_lsn, char *gid, bool is_commit)
3009
0
{
3010
0
  ReorderBufferTXN *txn;
3011
0
  XLogRecPtr  prepare_end_lsn;
3012
0
  TimestampTz prepare_time;
3013
3014
0
  txn = ReorderBufferTXNByXid(rb, xid, false, NULL, commit_lsn, false);
3015
3016
  /* unknown transaction, nothing to do */
3017
0
  if (txn == NULL)
3018
0
    return;
3019
3020
  /*
3021
   * By this time the txn has the prepare record information, remember it to
3022
   * be later used for rollback.
3023
   */
3024
0
  prepare_end_lsn = txn->end_lsn;
3025
0
  prepare_time = txn->prepare_time;
3026
3027
  /* add the gid in the txn */
3028
0
  txn->gid = pstrdup(gid);
3029
3030
  /*
3031
   * It is possible that this transaction is not decoded at prepare time
3032
   * either because by that time we didn't have a consistent snapshot, or
3033
   * two_phase was not enabled, or it was decoded earlier but we have
3034
   * restarted. We only need to send the prepare if it was not decoded
3035
   * earlier. We don't need to decode the xact for aborts if it is not done
3036
   * already.
3037
   */
3038
0
  if ((txn->final_lsn < two_phase_at) && is_commit)
3039
0
  {
3040
    /*
3041
     * txn must have been marked as a prepared transaction and skipped but
3042
     * not sent a prepare. Also, the prepare info must have been updated
3043
     * in txn even if we skip prepare.
3044
     */
3045
0
    Assert((txn->txn_flags & RBTXN_PREPARE_STATUS_MASK) ==
3046
0
         (RBTXN_IS_PREPARED | RBTXN_SKIPPED_PREPARE));
3047
0
    Assert(XLogRecPtrIsValid(txn->final_lsn));
3048
3049
    /*
3050
     * By this time the txn has the prepare record information and it is
3051
     * important to use that so that downstream gets the accurate
3052
     * information. If instead, we have passed commit information here
3053
     * then downstream can behave as it has already replayed commit
3054
     * prepared after the restart.
3055
     */
3056
0
    ReorderBufferReplay(txn, rb, xid, txn->final_lsn, txn->end_lsn,
3057
0
              txn->prepare_time, txn->origin_id, txn->origin_lsn);
3058
0
  }
3059
3060
  /*
3061
   * If this transaction has no snapshot, it didn't make any changes to the
3062
   * database, so there's nothing to decode.  Note that
3063
   * ReorderBufferCommitChild will have transferred any snapshots from
3064
   * subtransactions if there were any.
3065
   */
3066
0
  if (txn->base_snapshot == NULL)
3067
0
  {
3068
0
    Assert(txn->ninvalidations == 0);
3069
0
    Assert(!rbtxn_sent_prepare(txn));
3070
3071
    /*
3072
     * Removing this txn before a commit might result in the computation
3073
     * of an incorrect restart_lsn. See SnapBuildProcessRunningXacts.
3074
     */
3075
0
    ReorderBufferCleanupTXN(rb, txn);
3076
0
    return;
3077
0
  }
3078
3079
0
  txn->final_lsn = commit_lsn;
3080
0
  txn->end_lsn = end_lsn;
3081
0
  txn->commit_time = commit_time;
3082
0
  txn->origin_id = origin_id;
3083
0
  txn->origin_lsn = origin_lsn;
3084
3085
0
  if (is_commit)
3086
0
    rb->commit_prepared(rb, txn, commit_lsn);
3087
0
  else
3088
0
    rb->rollback_prepared(rb, txn, prepare_end_lsn, prepare_time);
3089
3090
  /* cleanup: make sure there's no cache pollution */
3091
0
  ReorderBufferExecuteInvalidations(txn->ninvalidations,
3092
0
                    txn->invalidations);
3093
0
  ReorderBufferCleanupTXN(rb, txn);
3094
0
}
3095
3096
/*
3097
 * Abort a transaction that possibly has previous changes. Needs to be first
3098
 * called for subtransactions and then for the toplevel xid.
3099
 *
3100
 * NB: Transactions handled here have to have actively aborted (i.e. have
3101
 * produced an abort record). Implicitly aborted transactions are handled via
3102
 * ReorderBufferAbortOld(); transactions we're just not interested in, but
3103
 * which have committed are handled in ReorderBufferForget().
3104
 *
3105
 * This function purges this transaction and its contents from memory and
3106
 * disk.
3107
 */
3108
void
3109
ReorderBufferAbort(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn,
3110
           TimestampTz abort_time)
3111
0
{
3112
0
  ReorderBufferTXN *txn;
3113
3114
0
  txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
3115
0
                false);
3116
3117
  /* unknown, nothing to remove */
3118
0
  if (txn == NULL)
3119
0
    return;
3120
3121
0
  txn->abort_time = abort_time;
3122
3123
  /* For streamed transactions notify the remote node about the abort. */
3124
0
  if (rbtxn_is_streamed(txn))
3125
0
  {
3126
0
    rb->stream_abort(rb, txn, lsn);
3127
3128
    /*
3129
     * We might have decoded changes for this transaction that could load
3130
     * the cache as per the current transaction's view (consider DDL's
3131
     * happened in this transaction). We don't want the decoding of future
3132
     * transactions to use those cache entries so execute only the inval
3133
     * messages in this transaction.
3134
     */
3135
0
    if (txn->ninvalidations > 0)
3136
0
      ReorderBufferImmediateInvalidation(rb, txn->ninvalidations,
3137
0
                         txn->invalidations);
3138
0
  }
3139
3140
  /* cosmetic... */
3141
0
  txn->final_lsn = lsn;
3142
3143
  /* remove potential on-disk data, and deallocate */
3144
0
  ReorderBufferCleanupTXN(rb, txn);
3145
0
}
3146
3147
/*
3148
 * Abort all transactions that aren't actually running anymore because the
3149
 * server restarted.
3150
 *
3151
 * NB: These really have to be transactions that have aborted due to a server
3152
 * crash/immediate restart, as we don't deal with invalidations here.
3153
 */
3154
void
3155
ReorderBufferAbortOld(ReorderBuffer *rb, TransactionId oldestRunningXid)
3156
0
{
3157
0
  dlist_mutable_iter it;
3158
3159
  /*
3160
   * Iterate through all (potential) toplevel TXNs and abort all that are
3161
   * older than what possibly can be running. Once we've found the first
3162
   * that is alive we stop, there might be some that acquired an xid earlier
3163
   * but started writing later, but it's unlikely and they will be cleaned
3164
   * up in a later call to this function.
3165
   */
3166
0
  dlist_foreach_modify(it, &rb->toplevel_by_lsn)
3167
0
  {
3168
0
    ReorderBufferTXN *txn;
3169
3170
0
    txn = dlist_container(ReorderBufferTXN, node, it.cur);
3171
3172
0
    if (TransactionIdPrecedes(txn->xid, oldestRunningXid))
3173
0
    {
3174
0
      elog(DEBUG2, "aborting old transaction %u", txn->xid);
3175
3176
      /* Notify the remote node about the crash/immediate restart. */
3177
0
      if (rbtxn_is_streamed(txn))
3178
0
        rb->stream_abort(rb, txn, InvalidXLogRecPtr);
3179
3180
      /* remove potential on-disk data, and deallocate this tx */
3181
0
      ReorderBufferCleanupTXN(rb, txn);
3182
0
    }
3183
0
    else
3184
0
      return;
3185
0
  }
3186
0
}
3187
3188
/*
3189
 * Forget the contents of a transaction if we aren't interested in its
3190
 * contents. Needs to be first called for subtransactions and then for the
3191
 * toplevel xid.
3192
 *
3193
 * This is significantly different to ReorderBufferAbort() because
3194
 * transactions that have committed need to be treated differently from aborted
3195
 * ones since they may have modified the catalog.
3196
 *
3197
 * Note that this is only allowed to be called in the moment a transaction
3198
 * commit has just been read, not earlier; otherwise later records referring
3199
 * to this xid might re-create the transaction incompletely.
3200
 */
3201
void
3202
ReorderBufferForget(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn)
3203
0
{
3204
0
  ReorderBufferTXN *txn;
3205
3206
0
  txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
3207
0
                false);
3208
3209
  /* unknown, nothing to forget */
3210
0
  if (txn == NULL)
3211
0
    return;
3212
3213
  /* this transaction mustn't be streamed */
3214
0
  Assert(!rbtxn_is_streamed(txn));
3215
3216
  /* cosmetic... */
3217
0
  txn->final_lsn = lsn;
3218
3219
  /*
3220
   * Process only cache invalidation messages in this transaction if there
3221
   * are any. Even if we're not interested in the transaction's contents, it
3222
   * could have manipulated the catalog and we need to update the caches
3223
   * according to that.
3224
   */
3225
0
  if (txn->base_snapshot != NULL && txn->ninvalidations > 0)
3226
0
    ReorderBufferImmediateInvalidation(rb, txn->ninvalidations,
3227
0
                       txn->invalidations);
3228
0
  else
3229
0
    Assert(txn->ninvalidations == 0);
3230
3231
  /* remove potential on-disk data, and deallocate */
3232
0
  ReorderBufferCleanupTXN(rb, txn);
3233
0
}
3234
3235
/*
3236
 * Invalidate cache for those transactions that need to be skipped just in case
3237
 * catalogs were manipulated as part of the transaction.
3238
 *
3239
 * Note that this is a special-purpose function for prepared transactions where
3240
 * we don't want to clean up the TXN even when we decide to skip it. See
3241
 * DecodePrepare.
3242
 */
3243
void
3244
ReorderBufferInvalidate(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn)
3245
0
{
3246
0
  ReorderBufferTXN *txn;
3247
3248
0
  txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
3249
0
                false);
3250
3251
  /* unknown, nothing to do */
3252
0
  if (txn == NULL)
3253
0
    return;
3254
3255
  /*
3256
   * Process cache invalidation messages if there are any. Even if we're not
3257
   * interested in the transaction's contents, it could have manipulated the
3258
   * catalog and we need to update the caches according to that.
3259
   */
3260
0
  if (txn->base_snapshot != NULL && txn->ninvalidations > 0)
3261
0
    ReorderBufferImmediateInvalidation(rb, txn->ninvalidations,
3262
0
                       txn->invalidations);
3263
0
  else
3264
0
    Assert(txn->ninvalidations == 0);
3265
0
}
3266
3267
3268
/*
3269
 * Execute invalidations happening outside the context of a decoded
3270
 * transaction. That currently happens either for xid-less commits
3271
 * (cf. RecordTransactionCommit()) or for invalidations in uninteresting
3272
 * transactions (via ReorderBufferForget()).
3273
 */
3274
void
3275
ReorderBufferImmediateInvalidation(ReorderBuffer *rb, uint32 ninvalidations,
3276
                   SharedInvalidationMessage *invalidations)
3277
0
{
3278
0
  bool    use_subtxn = IsTransactionOrTransactionBlock();
3279
0
  MemoryContext ccxt = CurrentMemoryContext;
3280
0
  ResourceOwner cowner = CurrentResourceOwner;
3281
3282
0
  if (use_subtxn)
3283
0
    BeginInternalSubTransaction("replay");
3284
3285
  /*
3286
   * Force invalidations to happen outside of a valid transaction - that way
3287
   * entries will just be marked as invalid without accessing the catalog.
3288
   * That's advantageous because we don't need to setup the full state
3289
   * necessary for catalog access.
3290
   */
3291
0
  if (use_subtxn)
3292
0
    AbortCurrentTransaction();
3293
3294
0
  for (uint32 i = 0; i < ninvalidations; i++)
3295
0
    LocalExecuteInvalidationMessage(&invalidations[i]);
3296
3297
0
  if (use_subtxn)
3298
0
  {
3299
0
    RollbackAndReleaseCurrentSubTransaction();
3300
0
    MemoryContextSwitchTo(ccxt);
3301
0
    CurrentResourceOwner = cowner;
3302
0
  }
3303
0
}
3304
3305
/*
3306
 * Tell reorderbuffer about an xid seen in the WAL stream. Has to be called at
3307
 * least once for every xid in XLogRecord->xl_xid (other places in records
3308
 * may, but do not have to be passed through here).
3309
 *
3310
 * Reorderbuffer keeps some data structures about transactions in LSN order,
3311
 * for efficiency. To do that it has to know about when transactions are seen
3312
 * first in the WAL. As many types of records are not actually interesting for
3313
 * logical decoding, they do not necessarily pass through here.
3314
 */
3315
void
3316
ReorderBufferProcessXid(ReorderBuffer *rb, TransactionId xid, XLogRecPtr lsn)
3317
0
{
3318
  /* many records won't have an xid assigned, centralize check here */
3319
0
  if (xid != InvalidTransactionId)
3320
0
    ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
3321
0
}
3322
3323
/*
3324
 * Add a new snapshot to this transaction that may only used after lsn 'lsn'
3325
 * because the previous snapshot doesn't describe the catalog correctly for
3326
 * following rows.
3327
 */
3328
void
3329
ReorderBufferAddSnapshot(ReorderBuffer *rb, TransactionId xid,
3330
             XLogRecPtr lsn, Snapshot snap)
3331
0
{
3332
0
  ReorderBufferChange *change = ReorderBufferAllocChange(rb);
3333
3334
0
  change->data.snapshot = snap;
3335
0
  change->action = REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT;
3336
3337
0
  ReorderBufferQueueChange(rb, xid, lsn, change, false);
3338
0
}
3339
3340
/*
3341
 * Set up the transaction's base snapshot.
3342
 *
3343
 * If we know that xid is a subtransaction, set the base snapshot on the
3344
 * top-level transaction instead.
3345
 */
3346
void
3347
ReorderBufferSetBaseSnapshot(ReorderBuffer *rb, TransactionId xid,
3348
               XLogRecPtr lsn, Snapshot snap)
3349
0
{
3350
0
  ReorderBufferTXN *txn;
3351
0
  bool    is_new;
3352
3353
0
  Assert(snap != NULL);
3354
3355
  /*
3356
   * Fetch the transaction to operate on.  If we know it's a subtransaction,
3357
   * operate on its top-level transaction instead.
3358
   */
3359
0
  txn = ReorderBufferTXNByXid(rb, xid, true, &is_new, lsn, true);
3360
0
  if (rbtxn_is_known_subxact(txn))
3361
0
    txn = ReorderBufferTXNByXid(rb, txn->toplevel_xid, false,
3362
0
                  NULL, InvalidXLogRecPtr, false);
3363
0
  Assert(txn->base_snapshot == NULL);
3364
3365
0
  txn->base_snapshot = snap;
3366
0
  txn->base_snapshot_lsn = lsn;
3367
0
  dlist_push_tail(&rb->txns_by_base_snapshot_lsn, &txn->base_snapshot_node);
3368
3369
0
  AssertTXNLsnOrder(rb);
3370
0
}
3371
3372
/*
3373
 * Access the catalog with this CommandId at this point in the changestream.
3374
 *
3375
 * May only be called for command ids > 1
3376
 */
3377
void
3378
ReorderBufferAddNewCommandId(ReorderBuffer *rb, TransactionId xid,
3379
               XLogRecPtr lsn, CommandId cid)
3380
0
{
3381
0
  ReorderBufferChange *change = ReorderBufferAllocChange(rb);
3382
3383
0
  change->data.command_id = cid;
3384
0
  change->action = REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID;
3385
3386
0
  ReorderBufferQueueChange(rb, xid, lsn, change, false);
3387
0
}
3388
3389
/*
3390
 * Update memory counters to account for the new or removed change.
3391
 *
3392
 * We update two counters - in the reorder buffer, and in the transaction
3393
 * containing the change. The reorder buffer counter allows us to quickly
3394
 * decide if we reached the memory limit, the transaction counter allows
3395
 * us to quickly pick the largest transaction for eviction.
3396
 *
3397
 * Either txn or change must be non-NULL at least. We update the memory
3398
 * counter of txn if it's non-NULL, otherwise change->txn.
3399
 *
3400
 * When streaming is enabled, we need to update the toplevel transaction
3401
 * counters instead - we don't really care about subtransactions as we
3402
 * can't stream them individually anyway, and we only pick toplevel
3403
 * transactions for eviction. So only toplevel transactions matter.
3404
 */
3405
static void
3406
ReorderBufferChangeMemoryUpdate(ReorderBuffer *rb,
3407
                ReorderBufferChange *change,
3408
                ReorderBufferTXN *txn,
3409
                bool addition, Size sz)
3410
0
{
3411
0
  ReorderBufferTXN *toptxn;
3412
3413
0
  Assert(txn || change);
3414
3415
  /*
3416
   * Ignore tuple CID changes, because those are not evicted when reaching
3417
   * memory limit. So we just don't count them, because it might easily
3418
   * trigger a pointless attempt to spill.
3419
   */
3420
0
  if (change && change->action == REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID)
3421
0
    return;
3422
3423
0
  if (sz == 0)
3424
0
    return;
3425
3426
0
  if (txn == NULL)
3427
0
    txn = change->txn;
3428
0
  Assert(txn != NULL);
3429
3430
  /*
3431
   * Update the total size in top level as well. This is later used to
3432
   * compute the decoding stats.
3433
   */
3434
0
  toptxn = rbtxn_get_toptxn(txn);
3435
3436
0
  if (addition)
3437
0
  {
3438
0
    Size    oldsize = txn->size;
3439
3440
0
    txn->size += sz;
3441
0
    rb->size += sz;
3442
3443
    /* Update the total size in the top transaction. */
3444
0
    toptxn->total_size += sz;
3445
3446
    /* Update the max-heap */
3447
0
    if (oldsize != 0)
3448
0
      pairingheap_remove(rb->txn_heap, &txn->txn_node);
3449
0
    pairingheap_add(rb->txn_heap, &txn->txn_node);
3450
0
  }
3451
0
  else
3452
0
  {
3453
0
    Assert((rb->size >= sz) && (txn->size >= sz));
3454
0
    txn->size -= sz;
3455
0
    rb->size -= sz;
3456
3457
    /* Update the total size in the top transaction. */
3458
0
    toptxn->total_size -= sz;
3459
3460
    /* Update the max-heap */
3461
0
    pairingheap_remove(rb->txn_heap, &txn->txn_node);
3462
0
    if (txn->size != 0)
3463
0
      pairingheap_add(rb->txn_heap, &txn->txn_node);
3464
0
  }
3465
3466
0
  Assert(txn->size <= rb->size);
3467
0
}
3468
3469
/*
3470
 * Add new (relfilelocator, tid) -> (cmin, cmax) mappings.
3471
 *
3472
 * We do not include this change type in memory accounting, because we
3473
 * keep CIDs in a separate list and do not evict them when reaching
3474
 * the memory limit.
3475
 */
3476
void
3477
ReorderBufferAddNewTupleCids(ReorderBuffer *rb, TransactionId xid,
3478
               XLogRecPtr lsn, RelFileLocator locator,
3479
               ItemPointerData tid, CommandId cmin,
3480
               CommandId cmax, CommandId combocid)
3481
0
{
3482
0
  ReorderBufferChange *change = ReorderBufferAllocChange(rb);
3483
0
  ReorderBufferTXN *txn;
3484
3485
0
  txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
3486
3487
0
  change->data.tuplecid.locator = locator;
3488
0
  change->data.tuplecid.tid = tid;
3489
0
  change->data.tuplecid.cmin = cmin;
3490
0
  change->data.tuplecid.cmax = cmax;
3491
0
  change->data.tuplecid.combocid = combocid;
3492
0
  change->lsn = lsn;
3493
0
  change->txn = txn;
3494
0
  change->action = REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID;
3495
3496
0
  dlist_push_tail(&txn->tuplecids, &change->node);
3497
0
  txn->ntuplecids++;
3498
0
}
3499
3500
/*
3501
 * Add new invalidation messages to the reorder buffer queue.
3502
 */
3503
static void
3504
ReorderBufferQueueInvalidations(ReorderBuffer *rb, TransactionId xid,
3505
                XLogRecPtr lsn, Size nmsgs,
3506
                SharedInvalidationMessage *msgs)
3507
0
{
3508
0
  ReorderBufferChange *change;
3509
3510
0
  change = ReorderBufferAllocChange(rb);
3511
0
  change->action = REORDER_BUFFER_CHANGE_INVALIDATION;
3512
0
  change->data.inval.ninvalidations = nmsgs;
3513
0
  change->data.inval.invalidations = palloc_array(SharedInvalidationMessage, nmsgs);
3514
0
  memcpy(change->data.inval.invalidations, msgs,
3515
0
       sizeof(SharedInvalidationMessage) * nmsgs);
3516
3517
0
  ReorderBufferQueueChange(rb, xid, lsn, change, false);
3518
0
}
3519
3520
/*
3521
 * A helper function for ReorderBufferAddInvalidations() and
3522
 * ReorderBufferAddDistributedInvalidations() to accumulate the invalidation
3523
 * messages to the **invals_out.
3524
 */
3525
static void
3526
ReorderBufferAccumulateInvalidations(SharedInvalidationMessage **invals_out,
3527
                   uint32 *ninvals_out,
3528
                   SharedInvalidationMessage *msgs_new,
3529
                   Size nmsgs_new)
3530
0
{
3531
0
  if (*ninvals_out == 0)
3532
0
  {
3533
0
    *ninvals_out = nmsgs_new;
3534
0
    *invals_out = palloc_array(SharedInvalidationMessage, nmsgs_new);
3535
0
    memcpy(*invals_out, msgs_new, sizeof(SharedInvalidationMessage) * nmsgs_new);
3536
0
  }
3537
0
  else
3538
0
  {
3539
    /* Enlarge the array of inval messages */
3540
0
    *invals_out =
3541
0
      repalloc_array(*invals_out, SharedInvalidationMessage,
3542
0
               (*ninvals_out + nmsgs_new));
3543
0
    memcpy(*invals_out + *ninvals_out, msgs_new,
3544
0
         nmsgs_new * sizeof(SharedInvalidationMessage));
3545
0
    *ninvals_out += nmsgs_new;
3546
0
  }
3547
0
}
3548
3549
/*
3550
 * Accumulate the invalidations for executing them later.
3551
 *
3552
 * This needs to be called for each XLOG_XACT_INVALIDATIONS message and
3553
 * accumulates all the invalidation messages in the toplevel transaction, if
3554
 * available, otherwise in the current transaction, as well as in the form of
3555
 * change in reorder buffer.  We require to record it in form of the change
3556
 * so that we can execute only the required invalidations instead of executing
3557
 * all the invalidations on each CommandId increment.  We also need to
3558
 * accumulate these in the txn buffer because in some cases where we skip
3559
 * processing the transaction (see ReorderBufferForget), we need to execute
3560
 * all the invalidations together.
3561
 */
3562
void
3563
ReorderBufferAddInvalidations(ReorderBuffer *rb, TransactionId xid,
3564
                XLogRecPtr lsn, Size nmsgs,
3565
                SharedInvalidationMessage *msgs)
3566
0
{
3567
0
  ReorderBufferTXN *txn;
3568
0
  MemoryContext oldcontext;
3569
3570
0
  txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
3571
3572
0
  oldcontext = MemoryContextSwitchTo(rb->context);
3573
3574
  /*
3575
   * Collect all the invalidations under the top transaction, if available,
3576
   * so that we can execute them all together.  See comments atop this
3577
   * function.
3578
   */
3579
0
  txn = rbtxn_get_toptxn(txn);
3580
3581
0
  Assert(nmsgs > 0);
3582
3583
0
  ReorderBufferAccumulateInvalidations(&txn->invalidations,
3584
0
                     &txn->ninvalidations,
3585
0
                     msgs, nmsgs);
3586
3587
0
  ReorderBufferQueueInvalidations(rb, xid, lsn, nmsgs, msgs);
3588
3589
0
  MemoryContextSwitchTo(oldcontext);
3590
0
}
3591
3592
/*
3593
 * Accumulate the invalidations distributed by other committed transactions
3594
 * for executing them later.
3595
 *
3596
 * This function is similar to ReorderBufferAddInvalidations() but stores
3597
 * the given inval messages to the txn->invalidations_distributed with the
3598
 * overflow check.
3599
 *
3600
 * This needs to be called by committed transactions to distribute their
3601
 * inval messages to in-progress transactions.
3602
 */
3603
void
3604
ReorderBufferAddDistributedInvalidations(ReorderBuffer *rb, TransactionId xid,
3605
                     XLogRecPtr lsn, Size nmsgs,
3606
                     SharedInvalidationMessage *msgs)
3607
0
{
3608
0
  ReorderBufferTXN *txn;
3609
0
  MemoryContext oldcontext;
3610
3611
0
  txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
3612
3613
0
  oldcontext = MemoryContextSwitchTo(rb->context);
3614
3615
  /*
3616
   * Collect all the invalidations under the top transaction, if available,
3617
   * so that we can execute them all together.  See comments
3618
   * ReorderBufferAddInvalidations.
3619
   */
3620
0
  txn = rbtxn_get_toptxn(txn);
3621
3622
0
  Assert(nmsgs > 0);
3623
3624
0
  if (!rbtxn_distr_inval_overflowed(txn))
3625
0
  {
3626
    /*
3627
     * Check the transaction has enough space for storing distributed
3628
     * invalidation messages.
3629
     */
3630
0
    if (txn->ninvalidations_distributed + nmsgs >= MAX_DISTR_INVAL_MSG_PER_TXN)
3631
0
    {
3632
      /*
3633
       * Mark the invalidation message as overflowed and free up the
3634
       * messages accumulated so far.
3635
       */
3636
0
      txn->txn_flags |= RBTXN_DISTR_INVAL_OVERFLOWED;
3637
3638
0
      if (txn->invalidations_distributed)
3639
0
      {
3640
0
        pfree(txn->invalidations_distributed);
3641
0
        txn->invalidations_distributed = NULL;
3642
0
        txn->ninvalidations_distributed = 0;
3643
0
      }
3644
0
    }
3645
0
    else
3646
0
      ReorderBufferAccumulateInvalidations(&txn->invalidations_distributed,
3647
0
                         &txn->ninvalidations_distributed,
3648
0
                         msgs, nmsgs);
3649
0
  }
3650
3651
  /* Queue the invalidation messages into the transaction */
3652
0
  ReorderBufferQueueInvalidations(rb, xid, lsn, nmsgs, msgs);
3653
3654
0
  MemoryContextSwitchTo(oldcontext);
3655
0
}
3656
3657
/*
3658
 * Apply all invalidations we know. Possibly we only need parts at this point
3659
 * in the changestream but we don't know which those are.
3660
 */
3661
static void
3662
ReorderBufferExecuteInvalidations(uint32 nmsgs, SharedInvalidationMessage *msgs)
3663
0
{
3664
0
  for (uint32 i = 0; i < nmsgs; i++)
3665
0
    LocalExecuteInvalidationMessage(&msgs[i]);
3666
0
}
3667
3668
/*
3669
 * Mark a transaction as containing catalog changes
3670
 */
3671
void
3672
ReorderBufferXidSetCatalogChanges(ReorderBuffer *rb, TransactionId xid,
3673
                  XLogRecPtr lsn)
3674
0
{
3675
0
  ReorderBufferTXN *txn;
3676
3677
0
  txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
3678
3679
0
  if (!rbtxn_has_catalog_changes(txn))
3680
0
  {
3681
0
    txn->txn_flags |= RBTXN_HAS_CATALOG_CHANGES;
3682
0
    dclist_push_tail(&rb->catchange_txns, &txn->catchange_node);
3683
0
  }
3684
3685
  /*
3686
   * Mark top-level transaction as having catalog changes too if one of its
3687
   * children has so that the ReorderBufferBuildTupleCidHash can
3688
   * conveniently check just top-level transaction and decide whether to
3689
   * build the hash table or not.
3690
   */
3691
0
  if (rbtxn_is_subtxn(txn))
3692
0
  {
3693
0
    ReorderBufferTXN *toptxn = rbtxn_get_toptxn(txn);
3694
3695
0
    if (!rbtxn_has_catalog_changes(toptxn))
3696
0
    {
3697
0
      toptxn->txn_flags |= RBTXN_HAS_CATALOG_CHANGES;
3698
0
      dclist_push_tail(&rb->catchange_txns, &toptxn->catchange_node);
3699
0
    }
3700
0
  }
3701
0
}
3702
3703
/*
3704
 * Return palloc'ed array of the transactions that have changed catalogs.
3705
 * The returned array is sorted in xidComparator order.
3706
 *
3707
 * The caller must free the returned array when done with it.
3708
 */
3709
TransactionId *
3710
ReorderBufferGetCatalogChangesXacts(ReorderBuffer *rb)
3711
0
{
3712
0
  dlist_iter  iter;
3713
0
  TransactionId *xids = NULL;
3714
0
  size_t    xcnt = 0;
3715
3716
  /* Quick return if the list is empty */
3717
0
  if (dclist_count(&rb->catchange_txns) == 0)
3718
0
    return NULL;
3719
3720
  /* Initialize XID array */
3721
0
  xids = palloc_array(TransactionId, dclist_count(&rb->catchange_txns));
3722
0
  dclist_foreach(iter, &rb->catchange_txns)
3723
0
  {
3724
0
    ReorderBufferTXN *txn = dclist_container(ReorderBufferTXN,
3725
0
                         catchange_node,
3726
0
                         iter.cur);
3727
3728
0
    Assert(rbtxn_has_catalog_changes(txn));
3729
3730
0
    xids[xcnt++] = txn->xid;
3731
0
  }
3732
3733
0
  qsort(xids, xcnt, sizeof(TransactionId), xidComparator);
3734
3735
0
  Assert(xcnt == dclist_count(&rb->catchange_txns));
3736
0
  return xids;
3737
0
}
3738
3739
/*
3740
 * Query whether a transaction is already *known* to contain catalog
3741
 * changes. This can be wrong until directly before the commit!
3742
 */
3743
bool
3744
ReorderBufferXidHasCatalogChanges(ReorderBuffer *rb, TransactionId xid)
3745
0
{
3746
0
  ReorderBufferTXN *txn;
3747
3748
0
  txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
3749
0
                false);
3750
0
  if (txn == NULL)
3751
0
    return false;
3752
3753
0
  return rbtxn_has_catalog_changes(txn);
3754
0
}
3755
3756
/*
3757
 * ReorderBufferXidHasBaseSnapshot
3758
 *    Have we already set the base snapshot for the given txn/subtxn?
3759
 */
3760
bool
3761
ReorderBufferXidHasBaseSnapshot(ReorderBuffer *rb, TransactionId xid)
3762
0
{
3763
0
  ReorderBufferTXN *txn;
3764
3765
0
  txn = ReorderBufferTXNByXid(rb, xid, false,
3766
0
                NULL, InvalidXLogRecPtr, false);
3767
3768
  /* transaction isn't known yet, ergo no snapshot */
3769
0
  if (txn == NULL)
3770
0
    return false;
3771
3772
  /* a known subtxn? operate on top-level txn instead */
3773
0
  if (rbtxn_is_known_subxact(txn))
3774
0
    txn = ReorderBufferTXNByXid(rb, txn->toplevel_xid, false,
3775
0
                  NULL, InvalidXLogRecPtr, false);
3776
3777
0
  return txn->base_snapshot != NULL;
3778
0
}
3779
3780
3781
/*
3782
 * ---------------------------------------
3783
 * Disk serialization support
3784
 * ---------------------------------------
3785
 */
3786
3787
/*
3788
 * Ensure the IO buffer is >= sz.
3789
 */
3790
static void
3791
ReorderBufferSerializeReserve(ReorderBuffer *rb, Size sz)
3792
0
{
3793
0
  if (!rb->outbufsize)
3794
0
  {
3795
0
    rb->outbuf = MemoryContextAlloc(rb->context, sz);
3796
0
    rb->outbufsize = sz;
3797
0
  }
3798
0
  else if (rb->outbufsize < sz)
3799
0
  {
3800
0
    rb->outbuf = repalloc(rb->outbuf, sz);
3801
0
    rb->outbufsize = sz;
3802
0
  }
3803
0
}
3804
3805
3806
/* Compare two transactions by size */
3807
static int
3808
ReorderBufferTXNSizeCompare(const pairingheap_node *a, const pairingheap_node *b, void *arg)
3809
0
{
3810
0
  const ReorderBufferTXN *ta = pairingheap_const_container(ReorderBufferTXN, txn_node, a);
3811
0
  const ReorderBufferTXN *tb = pairingheap_const_container(ReorderBufferTXN, txn_node, b);
3812
3813
0
  if (ta->size < tb->size)
3814
0
    return -1;
3815
0
  if (ta->size > tb->size)
3816
0
    return 1;
3817
0
  return 0;
3818
0
}
3819
3820
/*
3821
 * Find the largest transaction (toplevel or subxact) to evict (spill to disk).
3822
 */
3823
static ReorderBufferTXN *
3824
ReorderBufferLargestTXN(ReorderBuffer *rb)
3825
0
{
3826
0
  ReorderBufferTXN *largest;
3827
3828
  /* Get the largest transaction from the max-heap */
3829
0
  largest = pairingheap_container(ReorderBufferTXN, txn_node,
3830
0
                  pairingheap_first(rb->txn_heap));
3831
3832
0
  Assert(largest);
3833
0
  Assert(largest->size > 0);
3834
0
  Assert(largest->size <= rb->size);
3835
3836
0
  return largest;
3837
0
}
3838
3839
/*
3840
 * Find the largest streamable (and non-aborted) toplevel transaction to evict
3841
 * (by streaming).
3842
 *
3843
 * This can be seen as an optimized version of ReorderBufferLargestTXN, which
3844
 * should give us the same transaction (because we don't update memory account
3845
 * for subtransaction with streaming, so it's always 0). But we can simply
3846
 * iterate over the limited number of toplevel transactions that have a base
3847
 * snapshot. There is no use of selecting a transaction that doesn't have base
3848
 * snapshot because we don't decode such transactions.  Also, we do not select
3849
 * the transaction which doesn't have any streamable change.
3850
 *
3851
 * Note that, we skip transactions that contain incomplete changes. There
3852
 * is a scope of optimization here such that we can select the largest
3853
 * transaction which has incomplete changes.  But that will make the code and
3854
 * design quite complex and that might not be worth the benefit.  If we plan to
3855
 * stream the transactions that contain incomplete changes then we need to
3856
 * find a way to partially stream/truncate the transaction changes in-memory
3857
 * and build a mechanism to partially truncate the spilled files.
3858
 * Additionally, whenever we partially stream the transaction we need to
3859
 * maintain the last streamed lsn and next time we need to restore from that
3860
 * segment and the offset in WAL.  As we stream the changes from the top
3861
 * transaction and restore them subtransaction wise, we need to even remember
3862
 * the subxact from where we streamed the last change.
3863
 */
3864
static ReorderBufferTXN *
3865
ReorderBufferLargestStreamableTopTXN(ReorderBuffer *rb)
3866
0
{
3867
0
  dlist_iter  iter;
3868
0
  Size    largest_size = 0;
3869
0
  ReorderBufferTXN *largest = NULL;
3870
3871
  /* Find the largest top-level transaction having a base snapshot. */
3872
0
  dlist_foreach(iter, &rb->txns_by_base_snapshot_lsn)
3873
0
  {
3874
0
    ReorderBufferTXN *txn;
3875
3876
0
    txn = dlist_container(ReorderBufferTXN, base_snapshot_node, iter.cur);
3877
3878
    /* must not be a subtxn */
3879
0
    Assert(!rbtxn_is_known_subxact(txn));
3880
    /* base_snapshot must be set */
3881
0
    Assert(txn->base_snapshot != NULL);
3882
3883
    /* Don't consider these kinds of transactions for eviction. */
3884
0
    if (rbtxn_has_partial_change(txn) ||
3885
0
      !rbtxn_has_streamable_change(txn) ||
3886
0
      rbtxn_is_aborted(txn))
3887
0
      continue;
3888
3889
    /* Find the largest of the eviction candidates. */
3890
0
    if ((largest == NULL || txn->total_size > largest_size) &&
3891
0
      (txn->total_size > 0))
3892
0
    {
3893
0
      largest = txn;
3894
0
      largest_size = txn->total_size;
3895
0
    }
3896
0
  }
3897
3898
0
  return largest;
3899
0
}
3900
3901
/*
3902
 * Check whether the logical_decoding_work_mem limit was reached, and if yes
3903
 * pick the largest (sub)transaction at-a-time to evict and spill its changes to
3904
 * disk or send to the output plugin until we reach under the memory limit.
3905
 *
3906
 * If debug_logical_replication_streaming is set to "immediate", stream or
3907
 * serialize the changes immediately.
3908
 *
3909
 * XXX At this point we select the transactions until we reach under the memory
3910
 * limit, but we might also adapt a more elaborate eviction strategy - for example
3911
 * evicting enough transactions to free certain fraction (e.g. 50%) of the memory
3912
 * limit.
3913
 */
3914
static void
3915
ReorderBufferCheckMemoryLimit(ReorderBuffer *rb)
3916
0
{
3917
0
  ReorderBufferTXN *txn;
3918
0
  bool    update_stats = true;
3919
3920
0
  if (rb->size >= logical_decoding_work_mem * (Size) 1024)
3921
0
  {
3922
    /*
3923
     * Update the statistics as the memory usage has reached the limit. We
3924
     * report the statistics update later in this function since we can
3925
     * update the slot statistics altogether while streaming or
3926
     * serializing transactions in most cases.
3927
     */
3928
0
    rb->memExceededCount += 1;
3929
0
  }
3930
0
  else if (debug_logical_replication_streaming == DEBUG_LOGICAL_REP_STREAMING_BUFFERED)
3931
0
  {
3932
    /*
3933
     * Bail out if debug_logical_replication_streaming is buffered and we
3934
     * haven't exceeded the memory limit.
3935
     */
3936
0
    return;
3937
0
  }
3938
3939
  /*
3940
   * If debug_logical_replication_streaming is immediate, loop until there's
3941
   * no change. Otherwise, loop until we reach under the memory limit. One
3942
   * might think that just by evicting the largest (sub)transaction we will
3943
   * come under the memory limit based on assumption that the selected
3944
   * transaction is at least as large as the most recent change (which
3945
   * caused us to go over the memory limit). However, that is not true
3946
   * because a user can reduce the logical_decoding_work_mem to a smaller
3947
   * value before the most recent change.
3948
   */
3949
0
  while (rb->size >= logical_decoding_work_mem * (Size) 1024 ||
3950
0
       (debug_logical_replication_streaming == DEBUG_LOGICAL_REP_STREAMING_IMMEDIATE &&
3951
0
      rb->size > 0))
3952
0
  {
3953
    /*
3954
     * Pick the largest non-aborted transaction and evict it from memory
3955
     * by streaming, if possible.  Otherwise, spill to disk.
3956
     */
3957
0
    if (ReorderBufferCanStartStreaming(rb) &&
3958
0
      (txn = ReorderBufferLargestStreamableTopTXN(rb)) != NULL)
3959
0
    {
3960
      /* we know there has to be one, because the size is not zero */
3961
0
      Assert(txn && rbtxn_is_toptxn(txn));
3962
0
      Assert(txn->total_size > 0);
3963
0
      Assert(rb->size >= txn->total_size);
3964
3965
      /* skip the transaction if aborted */
3966
0
      if (ReorderBufferCheckAndTruncateAbortedTXN(rb, txn))
3967
0
        continue;
3968
3969
0
      ReorderBufferStreamTXN(rb, txn);
3970
0
    }
3971
0
    else
3972
0
    {
3973
      /*
3974
       * Pick the largest transaction (or subtransaction) and evict it
3975
       * from memory by serializing it to disk.
3976
       */
3977
0
      txn = ReorderBufferLargestTXN(rb);
3978
3979
      /* we know there has to be one, because the size is not zero */
3980
0
      Assert(txn);
3981
0
      Assert(txn->size > 0);
3982
0
      Assert(rb->size >= txn->size);
3983
3984
      /* skip the transaction if aborted */
3985
0
      if (ReorderBufferCheckAndTruncateAbortedTXN(rb, txn))
3986
0
        continue;
3987
3988
0
      ReorderBufferSerializeTXN(rb, txn);
3989
0
    }
3990
3991
    /*
3992
     * After eviction, the transaction should have no entries in memory,
3993
     * and should use 0 bytes for changes.
3994
     */
3995
0
    Assert(txn->size == 0);
3996
0
    Assert(txn->nentries_mem == 0);
3997
3998
    /*
3999
     * We've reported the memExceededCount update while streaming or
4000
     * serializing the transaction.
4001
     */
4002
0
    update_stats = false;
4003
0
  }
4004
4005
0
  if (update_stats)
4006
0
    UpdateDecodingStats((LogicalDecodingContext *) rb->private_data);
4007
4008
  /* We must be under the memory limit now. */
4009
0
  Assert(rb->size < logical_decoding_work_mem * (Size) 1024);
4010
0
}
4011
4012
/*
4013
 * Spill data of a large transaction (and its subtransactions) to disk.
4014
 */
4015
static void
4016
ReorderBufferSerializeTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
4017
0
{
4018
0
  dlist_iter  subtxn_i;
4019
0
  dlist_mutable_iter change_i;
4020
0
  int     fd = -1;
4021
0
  XLogSegNo curOpenSegNo = 0;
4022
0
  Size    spilled = 0;
4023
0
  Size    size = txn->size;
4024
4025
0
  elog(DEBUG2, "spill %u changes in XID %u to disk",
4026
0
     (uint32) txn->nentries_mem, txn->xid);
4027
4028
  /* do the same to all child TXs */
4029
0
  dlist_foreach(subtxn_i, &txn->subtxns)
4030
0
  {
4031
0
    ReorderBufferTXN *subtxn;
4032
4033
0
    subtxn = dlist_container(ReorderBufferTXN, node, subtxn_i.cur);
4034
0
    ReorderBufferSerializeTXN(rb, subtxn);
4035
0
  }
4036
4037
  /* serialize changestream */
4038
0
  dlist_foreach_modify(change_i, &txn->changes)
4039
0
  {
4040
0
    ReorderBufferChange *change;
4041
4042
0
    change = dlist_container(ReorderBufferChange, node, change_i.cur);
4043
4044
    /*
4045
     * store in segment in which it belongs by start lsn, don't split over
4046
     * multiple segments tho
4047
     */
4048
0
    if (fd == -1 ||
4049
0
      !XLByteInSeg(change->lsn, curOpenSegNo, wal_segment_size))
4050
0
    {
4051
0
      char    path[MAXPGPATH];
4052
4053
0
      if (fd != -1)
4054
0
        CloseTransientFile(fd);
4055
4056
0
      XLByteToSeg(change->lsn, curOpenSegNo, wal_segment_size);
4057
4058
      /*
4059
       * No need to care about TLIs here, only used during a single run,
4060
       * so each LSN only maps to a specific WAL record.
4061
       */
4062
0
      ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid,
4063
0
                    curOpenSegNo);
4064
4065
      /* open segment, create it if necessary */
4066
0
      fd = OpenTransientFile(path,
4067
0
                   O_CREAT | O_WRONLY | O_APPEND | PG_BINARY);
4068
4069
0
      if (fd < 0)
4070
0
        ereport(ERROR,
4071
0
            (errcode_for_file_access(),
4072
0
             errmsg("could not open file \"%s\": %m", path)));
4073
0
    }
4074
4075
0
    ReorderBufferSerializeChange(rb, txn, fd, change);
4076
0
    dlist_delete(&change->node);
4077
0
    ReorderBufferFreeChange(rb, change, false);
4078
4079
0
    spilled++;
4080
0
  }
4081
4082
  /* Update the memory counter */
4083
0
  ReorderBufferChangeMemoryUpdate(rb, NULL, txn, false, size);
4084
4085
  /* update the statistics iff we have spilled anything */
4086
0
  if (spilled)
4087
0
  {
4088
0
    rb->spillCount += 1;
4089
0
    rb->spillBytes += size;
4090
4091
    /* don't consider already serialized transactions */
4092
0
    rb->spillTxns += (rbtxn_is_serialized(txn) || rbtxn_is_serialized_clear(txn)) ? 0 : 1;
4093
4094
    /* update the decoding stats */
4095
0
    UpdateDecodingStats((LogicalDecodingContext *) rb->private_data);
4096
0
  }
4097
4098
0
  Assert(spilled == txn->nentries_mem);
4099
0
  Assert(dlist_is_empty(&txn->changes));
4100
0
  txn->nentries_mem = 0;
4101
0
  txn->txn_flags |= RBTXN_IS_SERIALIZED;
4102
4103
0
  if (fd != -1)
4104
0
    CloseTransientFile(fd);
4105
0
}
4106
4107
/*
4108
 * Serialize individual change to disk.
4109
 */
4110
static void
4111
ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
4112
               int fd, ReorderBufferChange *change)
4113
0
{
4114
0
  ReorderBufferDiskChange *ondisk;
4115
0
  Size    sz = sizeof(ReorderBufferDiskChange);
4116
4117
0
  ReorderBufferSerializeReserve(rb, sz);
4118
4119
0
  ondisk = (ReorderBufferDiskChange *) rb->outbuf;
4120
0
  memcpy(&ondisk->change, change, sizeof(ReorderBufferChange));
4121
4122
0
  switch (change->action)
4123
0
  {
4124
      /* fall through these, they're all similar enough */
4125
0
    case REORDER_BUFFER_CHANGE_INSERT:
4126
0
    case REORDER_BUFFER_CHANGE_UPDATE:
4127
0
    case REORDER_BUFFER_CHANGE_DELETE:
4128
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
4129
0
      {
4130
0
        char     *data;
4131
0
        HeapTuple oldtup,
4132
0
              newtup;
4133
0
        Size    oldlen = 0;
4134
0
        Size    newlen = 0;
4135
4136
0
        oldtup = change->data.tp.oldtuple;
4137
0
        newtup = change->data.tp.newtuple;
4138
4139
0
        if (oldtup)
4140
0
        {
4141
0
          sz += sizeof(HeapTupleData);
4142
0
          oldlen = oldtup->t_len;
4143
0
          sz += oldlen;
4144
0
        }
4145
4146
0
        if (newtup)
4147
0
        {
4148
0
          sz += sizeof(HeapTupleData);
4149
0
          newlen = newtup->t_len;
4150
0
          sz += newlen;
4151
0
        }
4152
4153
        /* make sure we have enough space */
4154
0
        ReorderBufferSerializeReserve(rb, sz);
4155
4156
0
        data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
4157
        /* might have been reallocated above */
4158
0
        ondisk = (ReorderBufferDiskChange *) rb->outbuf;
4159
4160
0
        if (oldlen)
4161
0
        {
4162
0
          memcpy(data, oldtup, sizeof(HeapTupleData));
4163
0
          data += sizeof(HeapTupleData);
4164
4165
0
          memcpy(data, oldtup->t_data, oldlen);
4166
0
          data += oldlen;
4167
0
        }
4168
4169
0
        if (newlen)
4170
0
        {
4171
0
          memcpy(data, newtup, sizeof(HeapTupleData));
4172
0
          data += sizeof(HeapTupleData);
4173
4174
0
          memcpy(data, newtup->t_data, newlen);
4175
0
          data += newlen;
4176
0
        }
4177
0
        break;
4178
0
      }
4179
0
    case REORDER_BUFFER_CHANGE_MESSAGE:
4180
0
      {
4181
0
        char     *data;
4182
0
        Size    prefix_size = strlen(change->data.msg.prefix) + 1;
4183
4184
0
        sz += prefix_size + change->data.msg.message_size +
4185
0
          sizeof(Size) + sizeof(Size);
4186
0
        ReorderBufferSerializeReserve(rb, sz);
4187
4188
0
        data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
4189
4190
        /* might have been reallocated above */
4191
0
        ondisk = (ReorderBufferDiskChange *) rb->outbuf;
4192
4193
        /* write the prefix including the size */
4194
0
        memcpy(data, &prefix_size, sizeof(Size));
4195
0
        data += sizeof(Size);
4196
0
        memcpy(data, change->data.msg.prefix,
4197
0
             prefix_size);
4198
0
        data += prefix_size;
4199
4200
        /* write the message including the size */
4201
0
        memcpy(data, &change->data.msg.message_size, sizeof(Size));
4202
0
        data += sizeof(Size);
4203
0
        memcpy(data, change->data.msg.message,
4204
0
             change->data.msg.message_size);
4205
0
        data += change->data.msg.message_size;
4206
4207
0
        break;
4208
0
      }
4209
0
    case REORDER_BUFFER_CHANGE_INVALIDATION:
4210
0
      {
4211
0
        char     *data;
4212
0
        Size    inval_size = sizeof(SharedInvalidationMessage) *
4213
0
          change->data.inval.ninvalidations;
4214
4215
0
        sz += inval_size;
4216
4217
0
        ReorderBufferSerializeReserve(rb, sz);
4218
0
        data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
4219
4220
        /* might have been reallocated above */
4221
0
        ondisk = (ReorderBufferDiskChange *) rb->outbuf;
4222
0
        memcpy(data, change->data.inval.invalidations, inval_size);
4223
0
        data += inval_size;
4224
4225
0
        break;
4226
0
      }
4227
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
4228
0
      {
4229
0
        Snapshot  snap;
4230
0
        char     *data;
4231
4232
0
        snap = change->data.snapshot;
4233
4234
0
        sz += sizeof(SnapshotData) +
4235
0
          sizeof(TransactionId) * snap->xcnt +
4236
0
          sizeof(TransactionId) * snap->subxcnt;
4237
4238
        /* make sure we have enough space */
4239
0
        ReorderBufferSerializeReserve(rb, sz);
4240
0
        data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
4241
        /* might have been reallocated above */
4242
0
        ondisk = (ReorderBufferDiskChange *) rb->outbuf;
4243
4244
0
        memcpy(data, snap, sizeof(SnapshotData));
4245
0
        data += sizeof(SnapshotData);
4246
4247
0
        if (snap->xcnt)
4248
0
        {
4249
0
          memcpy(data, snap->xip,
4250
0
               sizeof(TransactionId) * snap->xcnt);
4251
0
          data += sizeof(TransactionId) * snap->xcnt;
4252
0
        }
4253
4254
0
        if (snap->subxcnt)
4255
0
        {
4256
0
          memcpy(data, snap->subxip,
4257
0
               sizeof(TransactionId) * snap->subxcnt);
4258
0
          data += sizeof(TransactionId) * snap->subxcnt;
4259
0
        }
4260
0
        break;
4261
0
      }
4262
0
    case REORDER_BUFFER_CHANGE_TRUNCATE:
4263
0
      {
4264
0
        Size    size;
4265
0
        char     *data;
4266
4267
        /* account for the OIDs of truncated relations */
4268
0
        size = sizeof(Oid) * change->data.truncate.nrelids;
4269
0
        sz += size;
4270
4271
        /* make sure we have enough space */
4272
0
        ReorderBufferSerializeReserve(rb, sz);
4273
4274
0
        data = ((char *) rb->outbuf) + sizeof(ReorderBufferDiskChange);
4275
        /* might have been reallocated above */
4276
0
        ondisk = (ReorderBufferDiskChange *) rb->outbuf;
4277
4278
0
        memcpy(data, change->data.truncate.relids, size);
4279
0
        data += size;
4280
4281
0
        break;
4282
0
      }
4283
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
4284
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT:
4285
0
    case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
4286
0
    case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
4287
      /* ReorderBufferChange contains everything important */
4288
0
      break;
4289
0
  }
4290
4291
0
  ondisk->size = sz;
4292
4293
0
  errno = 0;
4294
0
  pgstat_report_wait_start(WAIT_EVENT_REORDER_BUFFER_WRITE);
4295
0
  if (write(fd, rb->outbuf, ondisk->size) != ondisk->size)
4296
0
  {
4297
0
    int     save_errno = errno;
4298
4299
0
    CloseTransientFile(fd);
4300
4301
    /* if write didn't set errno, assume problem is no disk space */
4302
0
    errno = save_errno ? save_errno : ENOSPC;
4303
0
    ereport(ERROR,
4304
0
        (errcode_for_file_access(),
4305
0
         errmsg("could not write to data file for XID %u: %m",
4306
0
            txn->xid)));
4307
0
  }
4308
0
  pgstat_report_wait_end();
4309
4310
  /*
4311
   * Keep the transaction's final_lsn up to date with each change we send to
4312
   * disk, so that ReorderBufferRestoreCleanup works correctly.  (We used to
4313
   * only do this on commit and abort records, but that doesn't work if a
4314
   * system crash leaves a transaction without its abort record).
4315
   *
4316
   * Make sure not to move it backwards.
4317
   */
4318
0
  if (txn->final_lsn < change->lsn)
4319
0
    txn->final_lsn = change->lsn;
4320
4321
0
  Assert(ondisk->change.action == change->action);
4322
0
}
4323
4324
/* Returns true, if the output plugin supports streaming, false, otherwise. */
4325
static inline bool
4326
ReorderBufferCanStream(ReorderBuffer *rb)
4327
0
{
4328
0
  LogicalDecodingContext *ctx = rb->private_data;
4329
4330
0
  return ctx->streaming;
4331
0
}
4332
4333
/* Returns true, if the streaming can be started now, false, otherwise. */
4334
static inline bool
4335
ReorderBufferCanStartStreaming(ReorderBuffer *rb)
4336
0
{
4337
0
  LogicalDecodingContext *ctx = rb->private_data;
4338
0
  SnapBuild  *builder = ctx->snapshot_builder;
4339
4340
  /* We can't start streaming unless a consistent state is reached. */
4341
0
  if (SnapBuildCurrentState(builder) < SNAPBUILD_CONSISTENT)
4342
0
    return false;
4343
4344
  /*
4345
   * We can't start streaming immediately even if the streaming is enabled
4346
   * because we previously decoded this transaction and now just are
4347
   * restarting.
4348
   */
4349
0
  if (ReorderBufferCanStream(rb) &&
4350
0
    !SnapBuildXactNeedsSkip(builder, ctx->reader->ReadRecPtr))
4351
0
    return true;
4352
4353
0
  return false;
4354
0
}
4355
4356
/*
4357
 * Send data of a large transaction (and its subtransactions) to the
4358
 * output plugin, but using the stream API.
4359
 */
4360
static void
4361
ReorderBufferStreamTXN(ReorderBuffer *rb, ReorderBufferTXN *txn)
4362
0
{
4363
0
  Snapshot  snapshot_now;
4364
0
  CommandId command_id;
4365
0
  Size    stream_bytes;
4366
0
  bool    txn_is_streamed;
4367
4368
  /* We can never reach here for a subtransaction. */
4369
0
  Assert(rbtxn_is_toptxn(txn));
4370
4371
  /*
4372
   * We can't make any assumptions about base snapshot here, similar to what
4373
   * ReorderBufferCommit() does. That relies on base_snapshot getting
4374
   * transferred from subxact in ReorderBufferCommitChild(), but that was
4375
   * not yet called as the transaction is in-progress.
4376
   *
4377
   * So just walk the subxacts and use the same logic here. But we only need
4378
   * to do that once, when the transaction is streamed for the first time.
4379
   * After that we need to reuse the snapshot from the previous run.
4380
   *
4381
   * Unlike DecodeCommit which adds xids of all the subtransactions in
4382
   * snapshot's xip array via SnapBuildCommitTxn, we can't do that here but
4383
   * we do add them to subxip array instead via ReorderBufferCopySnap. This
4384
   * allows the catalog changes made in subtransactions decoded till now to
4385
   * be visible.
4386
   */
4387
0
  if (txn->snapshot_now == NULL)
4388
0
  {
4389
0
    dlist_iter  subxact_i;
4390
4391
    /* make sure this transaction is streamed for the first time */
4392
0
    Assert(!rbtxn_is_streamed(txn));
4393
4394
    /* at the beginning we should have invalid command ID */
4395
0
    Assert(txn->command_id == InvalidCommandId);
4396
4397
0
    dlist_foreach(subxact_i, &txn->subtxns)
4398
0
    {
4399
0
      ReorderBufferTXN *subtxn;
4400
4401
0
      subtxn = dlist_container(ReorderBufferTXN, node, subxact_i.cur);
4402
0
      ReorderBufferTransferSnapToParent(txn, subtxn);
4403
0
    }
4404
4405
    /*
4406
     * If this transaction has no snapshot, it didn't make any changes to
4407
     * the database till now, so there's nothing to decode.
4408
     */
4409
0
    if (txn->base_snapshot == NULL)
4410
0
    {
4411
0
      Assert(txn->ninvalidations == 0);
4412
0
      return;
4413
0
    }
4414
4415
0
    command_id = FirstCommandId;
4416
0
    snapshot_now = ReorderBufferCopySnap(rb, txn->base_snapshot,
4417
0
                       txn, command_id);
4418
0
  }
4419
0
  else
4420
0
  {
4421
    /* the transaction must have been already streamed */
4422
0
    Assert(rbtxn_is_streamed(txn));
4423
4424
    /*
4425
     * Nah, we already have snapshot from the previous streaming run. We
4426
     * assume new subxacts can't move the LSN backwards, and so can't beat
4427
     * the LSN condition in the previous branch (so no need to walk
4428
     * through subxacts again). In fact, we must not do that as we may be
4429
     * using snapshot half-way through the subxact.
4430
     */
4431
0
    command_id = txn->command_id;
4432
4433
    /*
4434
     * We can't use txn->snapshot_now directly because after the last
4435
     * streaming run, we might have got some new sub-transactions. So we
4436
     * need to add them to the snapshot.
4437
     */
4438
0
    snapshot_now = ReorderBufferCopySnap(rb, txn->snapshot_now,
4439
0
                       txn, command_id);
4440
4441
    /* Free the previously copied snapshot. */
4442
0
    Assert(txn->snapshot_now->copied);
4443
0
    ReorderBufferFreeSnap(rb, txn->snapshot_now);
4444
0
    txn->snapshot_now = NULL;
4445
0
  }
4446
4447
  /*
4448
   * Remember this information to be used later to update stats. We can't
4449
   * update the stats here as an error while processing the changes would
4450
   * lead to the accumulation of stats even though we haven't streamed all
4451
   * the changes.
4452
   */
4453
0
  txn_is_streamed = rbtxn_is_streamed(txn);
4454
0
  stream_bytes = txn->total_size;
4455
4456
  /* Process and send the changes to output plugin. */
4457
0
  ReorderBufferProcessTXN(rb, txn, InvalidXLogRecPtr, snapshot_now,
4458
0
              command_id, true);
4459
4460
0
  rb->streamCount += 1;
4461
0
  rb->streamBytes += stream_bytes;
4462
4463
  /* Don't consider already streamed transaction. */
4464
0
  rb->streamTxns += (txn_is_streamed) ? 0 : 1;
4465
4466
  /* update the decoding stats */
4467
0
  UpdateDecodingStats((LogicalDecodingContext *) rb->private_data);
4468
4469
0
  Assert(dlist_is_empty(&txn->changes));
4470
0
  Assert(txn->nentries == 0);
4471
0
  Assert(txn->nentries_mem == 0);
4472
0
}
4473
4474
/*
4475
 * Size of a change in memory.
4476
 */
4477
static Size
4478
ReorderBufferChangeSize(ReorderBufferChange *change)
4479
0
{
4480
0
  Size    sz = sizeof(ReorderBufferChange);
4481
4482
0
  switch (change->action)
4483
0
  {
4484
      /* fall through these, they're all similar enough */
4485
0
    case REORDER_BUFFER_CHANGE_INSERT:
4486
0
    case REORDER_BUFFER_CHANGE_UPDATE:
4487
0
    case REORDER_BUFFER_CHANGE_DELETE:
4488
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
4489
0
      {
4490
0
        HeapTuple oldtup,
4491
0
              newtup;
4492
0
        Size    oldlen = 0;
4493
0
        Size    newlen = 0;
4494
4495
0
        oldtup = change->data.tp.oldtuple;
4496
0
        newtup = change->data.tp.newtuple;
4497
4498
0
        if (oldtup)
4499
0
        {
4500
0
          sz += sizeof(HeapTupleData);
4501
0
          oldlen = oldtup->t_len;
4502
0
          sz += oldlen;
4503
0
        }
4504
4505
0
        if (newtup)
4506
0
        {
4507
0
          sz += sizeof(HeapTupleData);
4508
0
          newlen = newtup->t_len;
4509
0
          sz += newlen;
4510
0
        }
4511
4512
0
        break;
4513
0
      }
4514
0
    case REORDER_BUFFER_CHANGE_MESSAGE:
4515
0
      {
4516
0
        Size    prefix_size = strlen(change->data.msg.prefix) + 1;
4517
4518
0
        sz += prefix_size + change->data.msg.message_size +
4519
0
          sizeof(Size) + sizeof(Size);
4520
4521
0
        break;
4522
0
      }
4523
0
    case REORDER_BUFFER_CHANGE_INVALIDATION:
4524
0
      {
4525
0
        sz += sizeof(SharedInvalidationMessage) *
4526
0
          change->data.inval.ninvalidations;
4527
0
        break;
4528
0
      }
4529
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
4530
0
      {
4531
0
        Snapshot  snap;
4532
4533
0
        snap = change->data.snapshot;
4534
4535
0
        sz += sizeof(SnapshotData) +
4536
0
          sizeof(TransactionId) * snap->xcnt +
4537
0
          sizeof(TransactionId) * snap->subxcnt;
4538
4539
0
        break;
4540
0
      }
4541
0
    case REORDER_BUFFER_CHANGE_TRUNCATE:
4542
0
      {
4543
0
        sz += sizeof(Oid) * change->data.truncate.nrelids;
4544
4545
0
        break;
4546
0
      }
4547
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
4548
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT:
4549
0
    case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
4550
0
    case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
4551
      /* ReorderBufferChange contains everything important */
4552
0
      break;
4553
0
  }
4554
4555
0
  return sz;
4556
0
}
4557
4558
4559
/*
4560
 * Restore a number of changes spilled to disk back into memory.
4561
 */
4562
static Size
4563
ReorderBufferRestoreChanges(ReorderBuffer *rb, ReorderBufferTXN *txn,
4564
              TXNEntryFile *file, XLogSegNo *segno)
4565
0
{
4566
0
  Size    restored = 0;
4567
0
  XLogSegNo last_segno;
4568
0
  dlist_mutable_iter cleanup_iter;
4569
0
  File     *fd = &file->vfd;
4570
4571
0
  Assert(XLogRecPtrIsValid(txn->first_lsn));
4572
0
  Assert(XLogRecPtrIsValid(txn->final_lsn));
4573
4574
  /* free current entries, so we have memory for more */
4575
0
  dlist_foreach_modify(cleanup_iter, &txn->changes)
4576
0
  {
4577
0
    ReorderBufferChange *cleanup =
4578
0
      dlist_container(ReorderBufferChange, node, cleanup_iter.cur);
4579
4580
0
    dlist_delete(&cleanup->node);
4581
0
    ReorderBufferFreeChange(rb, cleanup, true);
4582
0
  }
4583
0
  txn->nentries_mem = 0;
4584
0
  Assert(dlist_is_empty(&txn->changes));
4585
4586
0
  XLByteToSeg(txn->final_lsn, last_segno, wal_segment_size);
4587
4588
0
  while (restored < max_changes_in_memory && *segno <= last_segno)
4589
0
  {
4590
0
    ssize_t   readBytes;
4591
0
    ReorderBufferDiskChange *ondisk;
4592
4593
0
    CHECK_FOR_INTERRUPTS();
4594
4595
0
    if (*fd == -1)
4596
0
    {
4597
0
      char    path[MAXPGPATH];
4598
4599
      /* first time in */
4600
0
      if (*segno == 0)
4601
0
        XLByteToSeg(txn->first_lsn, *segno, wal_segment_size);
4602
4603
0
      Assert(*segno != 0 || dlist_is_empty(&txn->changes));
4604
4605
      /*
4606
       * No need to care about TLIs here, only used during a single run,
4607
       * so each LSN only maps to a specific WAL record.
4608
       */
4609
0
      ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid,
4610
0
                    *segno);
4611
4612
0
      *fd = PathNameOpenFile(path, O_RDONLY | PG_BINARY);
4613
4614
      /* No harm in resetting the offset even in case of failure */
4615
0
      file->curOffset = 0;
4616
4617
0
      if (*fd < 0 && errno == ENOENT)
4618
0
      {
4619
0
        *fd = -1;
4620
0
        (*segno)++;
4621
0
        continue;
4622
0
      }
4623
0
      else if (*fd < 0)
4624
0
        ereport(ERROR,
4625
0
            (errcode_for_file_access(),
4626
0
             errmsg("could not open file \"%s\": %m",
4627
0
                path)));
4628
0
    }
4629
4630
    /*
4631
     * Read the statically sized part of a change which has information
4632
     * about the total size. If we couldn't read a record, we're at the
4633
     * end of this file.
4634
     */
4635
0
    ReorderBufferSerializeReserve(rb, sizeof(ReorderBufferDiskChange));
4636
0
    readBytes = FileRead(file->vfd, rb->outbuf,
4637
0
               sizeof(ReorderBufferDiskChange),
4638
0
               file->curOffset, WAIT_EVENT_REORDER_BUFFER_READ);
4639
4640
    /* eof */
4641
0
    if (readBytes == 0)
4642
0
    {
4643
0
      FileClose(*fd);
4644
0
      *fd = -1;
4645
0
      (*segno)++;
4646
0
      continue;
4647
0
    }
4648
0
    else if (readBytes < 0)
4649
0
      ereport(ERROR,
4650
0
          (errcode_for_file_access(),
4651
0
           errmsg("could not read from reorderbuffer spill file: %m")));
4652
0
    else if (readBytes != sizeof(ReorderBufferDiskChange))
4653
0
      ereport(ERROR,
4654
0
          (errcode_for_file_access(),
4655
0
           errmsg("could not read from reorderbuffer spill file: read %zd of %zu",
4656
0
              readBytes,
4657
0
              sizeof(ReorderBufferDiskChange))));
4658
4659
0
    file->curOffset += readBytes;
4660
4661
0
    ondisk = (ReorderBufferDiskChange *) rb->outbuf;
4662
4663
0
    ReorderBufferSerializeReserve(rb,
4664
0
                    sizeof(ReorderBufferDiskChange) + ondisk->size);
4665
0
    ondisk = (ReorderBufferDiskChange *) rb->outbuf;
4666
4667
0
    readBytes = FileRead(file->vfd,
4668
0
               rb->outbuf + sizeof(ReorderBufferDiskChange),
4669
0
               ondisk->size - sizeof(ReorderBufferDiskChange),
4670
0
               file->curOffset,
4671
0
               WAIT_EVENT_REORDER_BUFFER_READ);
4672
4673
0
    if (readBytes < 0)
4674
0
      ereport(ERROR,
4675
0
          (errcode_for_file_access(),
4676
0
           errmsg("could not read from reorderbuffer spill file: %m")));
4677
0
    else if (readBytes != ondisk->size - sizeof(ReorderBufferDiskChange))
4678
0
      ereport(ERROR,
4679
0
          (errcode_for_file_access(),
4680
0
           errmsg("could not read from reorderbuffer spill file: read %zd of %zu",
4681
0
              readBytes,
4682
0
              (ondisk->size - sizeof(ReorderBufferDiskChange)))));
4683
4684
0
    file->curOffset += readBytes;
4685
4686
    /*
4687
     * ok, read a full change from disk, now restore it into proper
4688
     * in-memory format
4689
     */
4690
0
    ReorderBufferRestoreChange(rb, txn, rb->outbuf);
4691
0
    restored++;
4692
0
  }
4693
4694
0
  return restored;
4695
0
}
4696
4697
/*
4698
 * Convert change from its on-disk format to in-memory format and queue it onto
4699
 * the TXN's ->changes list.
4700
 *
4701
 * Note: although "data" is declared char*, at entry it points to a
4702
 * maxalign'd buffer, making it safe in most of this function to assume
4703
 * that the pointed-to data is suitably aligned for direct access.
4704
 */
4705
static void
4706
ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
4707
               char *data)
4708
0
{
4709
0
  ReorderBufferDiskChange *ondisk;
4710
0
  ReorderBufferChange *change;
4711
4712
0
  ondisk = (ReorderBufferDiskChange *) data;
4713
4714
0
  change = ReorderBufferAllocChange(rb);
4715
4716
  /* copy static part */
4717
0
  memcpy(change, &ondisk->change, sizeof(ReorderBufferChange));
4718
4719
0
  data += sizeof(ReorderBufferDiskChange);
4720
4721
  /* restore individual stuff */
4722
0
  switch (change->action)
4723
0
  {
4724
      /* fall through these, they're all similar enough */
4725
0
    case REORDER_BUFFER_CHANGE_INSERT:
4726
0
    case REORDER_BUFFER_CHANGE_UPDATE:
4727
0
    case REORDER_BUFFER_CHANGE_DELETE:
4728
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
4729
0
      if (change->data.tp.oldtuple)
4730
0
      {
4731
0
        uint32    tuplelen = ((HeapTuple) data)->t_len;
4732
4733
0
        change->data.tp.oldtuple =
4734
0
          ReorderBufferAllocTupleBuf(rb, tuplelen - SizeofHeapTupleHeader);
4735
4736
        /* restore ->tuple */
4737
0
        memcpy(change->data.tp.oldtuple, data,
4738
0
             sizeof(HeapTupleData));
4739
0
        data += sizeof(HeapTupleData);
4740
4741
        /* reset t_data pointer into the new tuplebuf */
4742
0
        change->data.tp.oldtuple->t_data =
4743
0
          (HeapTupleHeader) ((char *) change->data.tp.oldtuple + HEAPTUPLESIZE);
4744
4745
        /* restore tuple data itself */
4746
0
        memcpy(change->data.tp.oldtuple->t_data, data, tuplelen);
4747
0
        data += tuplelen;
4748
0
      }
4749
4750
0
      if (change->data.tp.newtuple)
4751
0
      {
4752
        /* here, data might not be suitably aligned! */
4753
0
        uint32    tuplelen;
4754
4755
0
        memcpy(&tuplelen, data + offsetof(HeapTupleData, t_len),
4756
0
             sizeof(uint32));
4757
4758
0
        change->data.tp.newtuple =
4759
0
          ReorderBufferAllocTupleBuf(rb, tuplelen - SizeofHeapTupleHeader);
4760
4761
        /* restore ->tuple */
4762
0
        memcpy(change->data.tp.newtuple, data,
4763
0
             sizeof(HeapTupleData));
4764
0
        data += sizeof(HeapTupleData);
4765
4766
        /* reset t_data pointer into the new tuplebuf */
4767
0
        change->data.tp.newtuple->t_data =
4768
0
          (HeapTupleHeader) ((char *) change->data.tp.newtuple + HEAPTUPLESIZE);
4769
4770
        /* restore tuple data itself */
4771
0
        memcpy(change->data.tp.newtuple->t_data, data, tuplelen);
4772
0
        data += tuplelen;
4773
0
      }
4774
4775
0
      break;
4776
0
    case REORDER_BUFFER_CHANGE_MESSAGE:
4777
0
      {
4778
0
        Size    prefix_size;
4779
4780
        /* read prefix */
4781
0
        memcpy(&prefix_size, data, sizeof(Size));
4782
0
        data += sizeof(Size);
4783
0
        change->data.msg.prefix = MemoryContextAlloc(rb->context,
4784
0
                               prefix_size);
4785
0
        memcpy(change->data.msg.prefix, data, prefix_size);
4786
0
        Assert(change->data.msg.prefix[prefix_size - 1] == '\0');
4787
0
        data += prefix_size;
4788
4789
        /* read the message */
4790
0
        memcpy(&change->data.msg.message_size, data, sizeof(Size));
4791
0
        data += sizeof(Size);
4792
0
        change->data.msg.message = MemoryContextAlloc(rb->context,
4793
0
                                change->data.msg.message_size);
4794
0
        memcpy(change->data.msg.message, data,
4795
0
             change->data.msg.message_size);
4796
0
        data += change->data.msg.message_size;
4797
4798
0
        break;
4799
0
      }
4800
0
    case REORDER_BUFFER_CHANGE_INVALIDATION:
4801
0
      {
4802
0
        Size    inval_size = sizeof(SharedInvalidationMessage) *
4803
0
          change->data.inval.ninvalidations;
4804
4805
0
        change->data.inval.invalidations =
4806
0
          MemoryContextAlloc(rb->context, inval_size);
4807
4808
        /* read the message */
4809
0
        memcpy(change->data.inval.invalidations, data, inval_size);
4810
4811
0
        break;
4812
0
      }
4813
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
4814
0
      {
4815
0
        Snapshot  oldsnap;
4816
0
        Snapshot  newsnap;
4817
0
        Size    size;
4818
4819
0
        oldsnap = (Snapshot) data;
4820
4821
0
        size = sizeof(SnapshotData) +
4822
0
          sizeof(TransactionId) * oldsnap->xcnt +
4823
0
          sizeof(TransactionId) * (oldsnap->subxcnt + 0);
4824
4825
0
        change->data.snapshot = MemoryContextAllocZero(rb->context, size);
4826
4827
0
        newsnap = change->data.snapshot;
4828
4829
0
        memcpy(newsnap, data, size);
4830
0
        newsnap->xip = (TransactionId *)
4831
0
          (((char *) newsnap) + sizeof(SnapshotData));
4832
0
        newsnap->subxip = newsnap->xip + newsnap->xcnt;
4833
0
        newsnap->copied = true;
4834
0
        break;
4835
0
      }
4836
      /* the base struct contains all the data, easy peasy */
4837
0
    case REORDER_BUFFER_CHANGE_TRUNCATE:
4838
0
      {
4839
0
        Oid      *relids;
4840
4841
0
        relids = ReorderBufferAllocRelids(rb, change->data.truncate.nrelids);
4842
0
        memcpy(relids, data, change->data.truncate.nrelids * sizeof(Oid));
4843
0
        change->data.truncate.relids = relids;
4844
4845
0
        break;
4846
0
      }
4847
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
4848
0
    case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_ABORT:
4849
0
    case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
4850
0
    case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
4851
0
      break;
4852
0
  }
4853
4854
0
  dlist_push_tail(&txn->changes, &change->node);
4855
0
  txn->nentries_mem++;
4856
4857
  /*
4858
   * Update memory accounting for the restored change.  We need to do this
4859
   * although we don't check the memory limit when restoring the changes in
4860
   * this branch (we only do that when initially queueing the changes after
4861
   * decoding), because we will release the changes later, and that will
4862
   * update the accounting too (subtracting the size from the counters). And
4863
   * we don't want to underflow there.
4864
   */
4865
0
  ReorderBufferChangeMemoryUpdate(rb, change, NULL, true,
4866
0
                  ReorderBufferChangeSize(change));
4867
0
}
4868
4869
/*
4870
 * Remove all on-disk stored for the passed in transaction.
4871
 */
4872
static void
4873
ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn)
4874
0
{
4875
0
  XLogSegNo first;
4876
0
  XLogSegNo cur;
4877
0
  XLogSegNo last;
4878
4879
0
  Assert(XLogRecPtrIsValid(txn->first_lsn));
4880
0
  Assert(XLogRecPtrIsValid(txn->final_lsn));
4881
4882
0
  XLByteToSeg(txn->first_lsn, first, wal_segment_size);
4883
0
  XLByteToSeg(txn->final_lsn, last, wal_segment_size);
4884
4885
  /* iterate over all possible filenames, and delete them */
4886
0
  for (cur = first; cur <= last; cur++)
4887
0
  {
4888
0
    char    path[MAXPGPATH];
4889
4890
0
    ReorderBufferSerializedPath(path, MyReplicationSlot, txn->xid, cur);
4891
0
    if (unlink(path) != 0 && errno != ENOENT)
4892
0
      ereport(ERROR,
4893
0
          (errcode_for_file_access(),
4894
0
           errmsg("could not remove file \"%s\": %m", path)));
4895
0
  }
4896
0
}
4897
4898
/*
4899
 * Remove any leftover serialized reorder buffers from a slot directory after a
4900
 * prior crash or decoding session exit.
4901
 */
4902
static void
4903
ReorderBufferCleanupSerializedTXNs(const char *slotname)
4904
0
{
4905
0
  DIR      *spill_dir;
4906
0
  struct dirent *spill_de;
4907
0
  struct stat statbuf;
4908
0
  char    path[MAXPGPATH * 2 + sizeof(PG_REPLSLOT_DIR)];
4909
4910
0
  sprintf(path, "%s/%s", PG_REPLSLOT_DIR, slotname);
4911
4912
  /* we're only handling directories here, skip if it's not ours */
4913
0
  if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
4914
0
    return;
4915
4916
0
  spill_dir = AllocateDir(path);
4917
0
  while ((spill_de = ReadDirExtended(spill_dir, path, INFO)) != NULL)
4918
0
  {
4919
    /* only look at names that can be ours */
4920
0
    if (strncmp(spill_de->d_name, "xid", 3) == 0)
4921
0
    {
4922
0
      snprintf(path, sizeof(path),
4923
0
           "%s/%s/%s", PG_REPLSLOT_DIR, slotname,
4924
0
           spill_de->d_name);
4925
4926
0
      if (unlink(path) != 0)
4927
0
        ereport(ERROR,
4928
0
            (errcode_for_file_access(),
4929
0
             errmsg("could not remove file \"%s\" during removal of %s/%s/xid*: %m",
4930
0
                path, PG_REPLSLOT_DIR, slotname)));
4931
0
    }
4932
0
  }
4933
0
  FreeDir(spill_dir);
4934
0
}
4935
4936
/*
4937
 * Given a replication slot, transaction ID and segment number, fill in the
4938
 * corresponding spill file into 'path', which is a caller-owned buffer of size
4939
 * at least MAXPGPATH.
4940
 */
4941
static void
4942
ReorderBufferSerializedPath(char *path, ReplicationSlot *slot, TransactionId xid,
4943
              XLogSegNo segno)
4944
0
{
4945
0
  XLogRecPtr  recptr;
4946
4947
0
  XLogSegNoOffsetToRecPtr(segno, 0, wal_segment_size, recptr);
4948
4949
0
  snprintf(path, MAXPGPATH, "%s/%s/xid-%u-lsn-%X-%X.spill",
4950
0
       PG_REPLSLOT_DIR,
4951
0
       NameStr(MyReplicationSlot->data.name),
4952
0
       xid, LSN_FORMAT_ARGS(recptr));
4953
0
}
4954
4955
/*
4956
 * Delete all data spilled to disk after we've restarted/crashed. It will be
4957
 * recreated when the respective slots are reused.
4958
 */
4959
void
4960
StartupReorderBuffer(void)
4961
0
{
4962
0
  DIR      *logical_dir;
4963
0
  struct dirent *logical_de;
4964
4965
0
  logical_dir = AllocateDir(PG_REPLSLOT_DIR);
4966
0
  while ((logical_de = ReadDir(logical_dir, PG_REPLSLOT_DIR)) != NULL)
4967
0
  {
4968
0
    if (strcmp(logical_de->d_name, ".") == 0 ||
4969
0
      strcmp(logical_de->d_name, "..") == 0)
4970
0
      continue;
4971
4972
    /* if it cannot be a slot, skip the directory */
4973
0
    if (!ReplicationSlotValidateName(logical_de->d_name, true, DEBUG2))
4974
0
      continue;
4975
4976
    /*
4977
     * ok, has to be a surviving logical slot, iterate and delete
4978
     * everything starting with xid-*
4979
     */
4980
0
    ReorderBufferCleanupSerializedTXNs(logical_de->d_name);
4981
0
  }
4982
0
  FreeDir(logical_dir);
4983
0
}
4984
4985
/* ---------------------------------------
4986
 * toast reassembly support
4987
 * ---------------------------------------
4988
 */
4989
4990
/*
4991
 * Initialize per tuple toast reconstruction support.
4992
 */
4993
static void
4994
ReorderBufferToastInitHash(ReorderBuffer *rb, ReorderBufferTXN *txn)
4995
0
{
4996
0
  HASHCTL   hash_ctl;
4997
4998
0
  Assert(txn->toast_hash == NULL);
4999
5000
0
  hash_ctl.keysize = sizeof(Oid);
5001
0
  hash_ctl.entrysize = sizeof(ReorderBufferToastEnt);
5002
0
  hash_ctl.hcxt = rb->context;
5003
0
  txn->toast_hash = hash_create("ReorderBufferToastHash", 5, &hash_ctl,
5004
0
                  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
5005
0
}
5006
5007
/*
5008
 * Per toast-chunk handling for toast reconstruction
5009
 *
5010
 * Appends a toast chunk so we can reconstruct it when the tuple "owning" the
5011
 * toasted Datum comes along.
5012
 */
5013
static void
5014
ReorderBufferToastAppendChunk(ReorderBuffer *rb, ReorderBufferTXN *txn,
5015
                Relation relation, ReorderBufferChange *change)
5016
0
{
5017
0
  ReorderBufferToastEnt *ent;
5018
0
  HeapTuple newtup;
5019
0
  bool    found;
5020
0
  int32   chunksize;
5021
0
  bool    isnull;
5022
0
  Pointer   chunk;
5023
0
  TupleDesc desc = RelationGetDescr(relation);
5024
0
  Oid     chunk_id;
5025
0
  int32   chunk_seq;
5026
5027
0
  if (txn->toast_hash == NULL)
5028
0
    ReorderBufferToastInitHash(rb, txn);
5029
5030
0
  Assert(IsToastRelation(relation));
5031
5032
0
  newtup = change->data.tp.newtuple;
5033
0
  chunk_id = DatumGetObjectId(fastgetattr(newtup, 1, desc, &isnull));
5034
0
  Assert(!isnull);
5035
0
  chunk_seq = DatumGetInt32(fastgetattr(newtup, 2, desc, &isnull));
5036
0
  Assert(!isnull);
5037
5038
0
  ent = (ReorderBufferToastEnt *)
5039
0
    hash_search(txn->toast_hash, &chunk_id, HASH_ENTER, &found);
5040
5041
0
  if (!found)
5042
0
  {
5043
0
    Assert(ent->chunk_id == chunk_id);
5044
0
    ent->num_chunks = 0;
5045
0
    ent->last_chunk_seq = 0;
5046
0
    ent->size = 0;
5047
0
    ent->reconstructed = NULL;
5048
0
    dlist_init(&ent->chunks);
5049
5050
0
    if (chunk_seq != 0)
5051
0
      elog(ERROR, "got sequence entry %d for toast chunk %u instead of seq 0",
5052
0
         chunk_seq, chunk_id);
5053
0
  }
5054
0
  else if (found && chunk_seq != ent->last_chunk_seq + 1)
5055
0
    elog(ERROR, "got sequence entry %d for toast chunk %u instead of seq %d",
5056
0
       chunk_seq, chunk_id, ent->last_chunk_seq + 1);
5057
5058
0
  chunk = DatumGetPointer(fastgetattr(newtup, 3, desc, &isnull));
5059
0
  Assert(!isnull);
5060
5061
  /* calculate size so we can allocate the right size at once later */
5062
0
  if (!VARATT_IS_EXTENDED(chunk))
5063
0
    chunksize = VARSIZE(chunk) - VARHDRSZ;
5064
0
  else if (VARATT_IS_SHORT(chunk))
5065
    /* could happen due to heap_form_tuple doing its thing */
5066
0
    chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
5067
0
  else
5068
0
    elog(ERROR, "unexpected type of toast chunk");
5069
5070
0
  ent->size += chunksize;
5071
0
  ent->last_chunk_seq = chunk_seq;
5072
0
  ent->num_chunks++;
5073
0
  dlist_push_tail(&ent->chunks, &change->node);
5074
0
}
5075
5076
/*
5077
 * Rejigger change->newtuple to point to in-memory toast tuples instead of
5078
 * on-disk toast tuples that may no longer exist (think DROP TABLE or VACUUM).
5079
 *
5080
 * We cannot replace unchanged toast tuples though, so those will still point
5081
 * to on-disk toast data.
5082
 *
5083
 * While updating the existing change with detoasted tuple data, we need to
5084
 * update the memory accounting info, because the change size will differ.
5085
 * Otherwise the accounting may get out of sync, triggering serialization
5086
 * at unexpected times.
5087
 *
5088
 * We simply subtract size of the change before rejiggering the tuple, and
5089
 * then add the new size. This makes it look like the change was removed
5090
 * and then added back, except it only tweaks the accounting info.
5091
 *
5092
 * In particular it can't trigger serialization, which would be pointless
5093
 * anyway as it happens during commit processing right before handing
5094
 * the change to the output plugin.
5095
 */
5096
static void
5097
ReorderBufferToastReplace(ReorderBuffer *rb, ReorderBufferTXN *txn,
5098
              Relation relation, ReorderBufferChange *change)
5099
0
{
5100
0
  TupleDesc desc;
5101
0
  int     natt;
5102
0
  Datum    *attrs;
5103
0
  bool     *isnull;
5104
0
  bool     *free;
5105
0
  HeapTuple tmphtup;
5106
0
  Relation  toast_rel;
5107
0
  TupleDesc toast_desc;
5108
0
  MemoryContext oldcontext;
5109
0
  HeapTuple newtup;
5110
0
  Size    old_size;
5111
5112
  /* no toast tuples changed */
5113
0
  if (txn->toast_hash == NULL)
5114
0
    return;
5115
5116
  /*
5117
   * We're going to modify the size of the change. So, to make sure the
5118
   * accounting is correct we record the current change size and then after
5119
   * re-computing the change we'll subtract the recorded size and then
5120
   * re-add the new change size at the end. We don't immediately subtract
5121
   * the old size because if there is any error before we add the new size,
5122
   * we will release the changes and that will update the accounting info
5123
   * (subtracting the size from the counters). And we don't want to
5124
   * underflow there.
5125
   */
5126
0
  old_size = ReorderBufferChangeSize(change);
5127
5128
0
  oldcontext = MemoryContextSwitchTo(rb->context);
5129
5130
  /* we should only have toast tuples in an INSERT or UPDATE */
5131
0
  Assert(change->data.tp.newtuple);
5132
5133
0
  desc = RelationGetDescr(relation);
5134
5135
0
  toast_rel = RelationIdGetRelation(relation->rd_rel->reltoastrelid);
5136
0
  if (!RelationIsValid(toast_rel))
5137
0
    elog(ERROR, "could not open toast relation with OID %u (base relation \"%s\")",
5138
0
       relation->rd_rel->reltoastrelid, RelationGetRelationName(relation));
5139
5140
0
  toast_desc = RelationGetDescr(toast_rel);
5141
5142
  /* should we allocate from stack instead? */
5143
0
  attrs = palloc0_array(Datum, desc->natts);
5144
0
  isnull = palloc0_array(bool, desc->natts);
5145
0
  free = palloc0_array(bool, desc->natts);
5146
5147
0
  newtup = change->data.tp.newtuple;
5148
5149
0
  heap_deform_tuple(newtup, desc, attrs, isnull);
5150
5151
0
  for (natt = 0; natt < desc->natts; natt++)
5152
0
  {
5153
0
    CompactAttribute *attr = TupleDescCompactAttr(desc, natt);
5154
0
    ReorderBufferToastEnt *ent;
5155
0
    varlena    *varlena_pointer;
5156
5157
    /* va_rawsize is the size of the original datum -- including header */
5158
0
    varatt_external toast_pointer;
5159
0
    varatt_indirect redirect_pointer;
5160
0
    varlena    *new_datum = NULL;
5161
0
    varlena    *reconstructed;
5162
0
    dlist_iter  it;
5163
0
    Size    data_done = 0;
5164
5165
0
    if (attr->attisdropped)
5166
0
      continue;
5167
5168
    /* not a varlena datatype */
5169
0
    if (attr->attlen != -1)
5170
0
      continue;
5171
5172
    /* no data */
5173
0
    if (isnull[natt])
5174
0
      continue;
5175
5176
    /* ok, we know we have a toast datum */
5177
0
    varlena_pointer = (varlena *) DatumGetPointer(attrs[natt]);
5178
5179
    /* no need to do anything if the tuple isn't external */
5180
0
    if (!VARATT_IS_EXTERNAL(varlena_pointer))
5181
0
      continue;
5182
5183
0
    VARATT_EXTERNAL_GET_POINTER(toast_pointer, varlena_pointer);
5184
5185
    /*
5186
     * Check whether the toast tuple changed, replace if so.
5187
     */
5188
0
    ent = (ReorderBufferToastEnt *)
5189
0
      hash_search(txn->toast_hash,
5190
0
            &toast_pointer.va_valueid,
5191
0
            HASH_FIND,
5192
0
            NULL);
5193
0
    if (ent == NULL)
5194
0
      continue;
5195
5196
0
    new_datum =
5197
0
      (varlena *) palloc0(INDIRECT_POINTER_SIZE);
5198
5199
0
    free[natt] = true;
5200
5201
0
    reconstructed = palloc0(toast_pointer.va_rawsize);
5202
5203
0
    ent->reconstructed = reconstructed;
5204
5205
    /* stitch toast tuple back together from its parts */
5206
0
    dlist_foreach(it, &ent->chunks)
5207
0
    {
5208
0
      bool    cisnull;
5209
0
      ReorderBufferChange *cchange;
5210
0
      HeapTuple ctup;
5211
0
      Pointer   chunk;
5212
5213
0
      cchange = dlist_container(ReorderBufferChange, node, it.cur);
5214
0
      ctup = cchange->data.tp.newtuple;
5215
0
      chunk = DatumGetPointer(fastgetattr(ctup, 3, toast_desc, &cisnull));
5216
5217
0
      Assert(!cisnull);
5218
0
      Assert(!VARATT_IS_EXTERNAL(chunk));
5219
0
      Assert(!VARATT_IS_SHORT(chunk));
5220
5221
0
      memcpy(VARDATA(reconstructed) + data_done,
5222
0
           VARDATA(chunk),
5223
0
           VARSIZE(chunk) - VARHDRSZ);
5224
0
      data_done += VARSIZE(chunk) - VARHDRSZ;
5225
0
    }
5226
0
    Assert(data_done == VARATT_EXTERNAL_GET_EXTSIZE(toast_pointer));
5227
5228
    /* make sure its marked as compressed or not */
5229
0
    if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer))
5230
0
      SET_VARSIZE_COMPRESSED(reconstructed, data_done + VARHDRSZ);
5231
0
    else
5232
0
      SET_VARSIZE(reconstructed, data_done + VARHDRSZ);
5233
5234
0
    memset(&redirect_pointer, 0, sizeof(redirect_pointer));
5235
0
    redirect_pointer.pointer = reconstructed;
5236
5237
0
    SET_VARTAG_EXTERNAL(new_datum, VARTAG_INDIRECT);
5238
0
    memcpy(VARDATA_EXTERNAL(new_datum), &redirect_pointer,
5239
0
         sizeof(redirect_pointer));
5240
5241
0
    attrs[natt] = PointerGetDatum(new_datum);
5242
0
  }
5243
5244
  /*
5245
   * Build tuple in separate memory & copy tuple back into the tuplebuf
5246
   * passed to the output plugin. We can't directly heap_fill_tuple() into
5247
   * the tuplebuf because attrs[] will point back into the current content.
5248
   */
5249
0
  tmphtup = heap_form_tuple(desc, attrs, isnull);
5250
0
  Assert(newtup->t_len <= MaxHeapTupleSize);
5251
0
  Assert(newtup->t_data == (HeapTupleHeader) ((char *) newtup + HEAPTUPLESIZE));
5252
5253
0
  memcpy(newtup->t_data, tmphtup->t_data, tmphtup->t_len);
5254
0
  newtup->t_len = tmphtup->t_len;
5255
5256
  /*
5257
   * free resources we won't further need, more persistent stuff will be
5258
   * free'd in ReorderBufferToastReset().
5259
   */
5260
0
  RelationClose(toast_rel);
5261
0
  pfree(tmphtup);
5262
0
  for (natt = 0; natt < desc->natts; natt++)
5263
0
  {
5264
0
    if (free[natt])
5265
0
      pfree(DatumGetPointer(attrs[natt]));
5266
0
  }
5267
0
  pfree(attrs);
5268
0
  pfree(free);
5269
0
  pfree(isnull);
5270
5271
0
  MemoryContextSwitchTo(oldcontext);
5272
5273
  /* subtract the old change size */
5274
0
  ReorderBufferChangeMemoryUpdate(rb, change, NULL, false, old_size);
5275
  /* now add the change back, with the correct size */
5276
0
  ReorderBufferChangeMemoryUpdate(rb, change, NULL, true,
5277
0
                  ReorderBufferChangeSize(change));
5278
0
}
5279
5280
/*
5281
 * Free all resources allocated for toast reconstruction.
5282
 */
5283
static void
5284
ReorderBufferToastReset(ReorderBuffer *rb, ReorderBufferTXN *txn)
5285
0
{
5286
0
  HASH_SEQ_STATUS hstat;
5287
0
  ReorderBufferToastEnt *ent;
5288
5289
0
  if (txn->toast_hash == NULL)
5290
0
    return;
5291
5292
  /* sequentially walk over the hash and free everything */
5293
0
  hash_seq_init(&hstat, txn->toast_hash);
5294
0
  while ((ent = (ReorderBufferToastEnt *) hash_seq_search(&hstat)) != NULL)
5295
0
  {
5296
0
    dlist_mutable_iter it;
5297
5298
0
    if (ent->reconstructed != NULL)
5299
0
      pfree(ent->reconstructed);
5300
5301
0
    dlist_foreach_modify(it, &ent->chunks)
5302
0
    {
5303
0
      ReorderBufferChange *change =
5304
0
        dlist_container(ReorderBufferChange, node, it.cur);
5305
5306
0
      dlist_delete(&change->node);
5307
0
      ReorderBufferFreeChange(rb, change, true);
5308
0
    }
5309
0
  }
5310
5311
0
  hash_destroy(txn->toast_hash);
5312
0
  txn->toast_hash = NULL;
5313
0
}
5314
5315
5316
/* ---------------------------------------
5317
 * Visibility support for logical decoding
5318
 *
5319
 *
5320
 * Lookup actual cmin/cmax values when using decoding snapshot. We can't
5321
 * always rely on stored cmin/cmax values because of two scenarios:
5322
 *
5323
 * * A tuple got changed multiple times during a single transaction and thus
5324
 *   has got a combo CID. Combo CIDs are only valid for the duration of a
5325
 *   single transaction.
5326
 * * A tuple with a cmin but no cmax (and thus no combo CID) got
5327
 *   deleted/updated in another transaction than the one which created it
5328
 *   which we are looking at right now. As only one of cmin, cmax or combo CID
5329
 *   is actually stored in the heap we don't have access to the value we
5330
 *   need anymore.
5331
 *
5332
 * To resolve those problems we have a per-transaction hash of (cmin,
5333
 * cmax) tuples keyed by (relfilelocator, ctid) which contains the actual
5334
 * (cmin, cmax) values. That also takes care of combo CIDs by simply
5335
 * not caring about them at all. As we have the real cmin/cmax values
5336
 * combo CIDs aren't interesting.
5337
 *
5338
 * As we only care about catalog tuples here the overhead of this
5339
 * hashtable should be acceptable.
5340
 *
5341
 * Heap rewrites complicate this a bit, check rewriteheap.c for
5342
 * details.
5343
 * -------------------------------------------------------------------------
5344
 */
5345
5346
/* struct for sorting mapping files by LSN efficiently */
5347
typedef struct RewriteMappingFile
5348
{
5349
  XLogRecPtr  lsn;
5350
  char    fname[MAXPGPATH];
5351
} RewriteMappingFile;
5352
5353
#ifdef NOT_USED
5354
static void
5355
DisplayMapping(HTAB *tuplecid_data)
5356
{
5357
  HASH_SEQ_STATUS hstat;
5358
  ReorderBufferTupleCidEnt *ent;
5359
5360
  hash_seq_init(&hstat, tuplecid_data);
5361
  while ((ent = (ReorderBufferTupleCidEnt *) hash_seq_search(&hstat)) != NULL)
5362
  {
5363
    elog(DEBUG3, "mapping: node: %u/%u/%u tid: %u/%u cmin: %u, cmax: %u",
5364
       ent->key.rlocator.dbOid,
5365
       ent->key.rlocator.spcOid,
5366
       ent->key.rlocator.relNumber,
5367
       ItemPointerGetBlockNumber(&ent->key.tid),
5368
       ItemPointerGetOffsetNumber(&ent->key.tid),
5369
       ent->cmin,
5370
       ent->cmax
5371
      );
5372
  }
5373
}
5374
#endif
5375
5376
/*
5377
 * Apply a single mapping file to tuplecid_data.
5378
 *
5379
 * The mapping file has to have been verified to be a) committed b) for our
5380
 * transaction c) applied in LSN order.
5381
 */
5382
static void
5383
ApplyLogicalMappingFile(HTAB *tuplecid_data, const char *fname)
5384
0
{
5385
0
  char    path[MAXPGPATH];
5386
0
  int     fd;
5387
0
  ssize_t   readBytes;
5388
0
  LogicalRewriteMappingData map;
5389
5390
0
  sprintf(path, "%s/%s", PG_LOGICAL_MAPPINGS_DIR, fname);
5391
0
  fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
5392
0
  if (fd < 0)
5393
0
    ereport(ERROR,
5394
0
        (errcode_for_file_access(),
5395
0
         errmsg("could not open file \"%s\": %m", path)));
5396
5397
0
  while (true)
5398
0
  {
5399
0
    ReorderBufferTupleCidKey key;
5400
0
    ReorderBufferTupleCidEnt *ent;
5401
0
    ReorderBufferTupleCidEnt *new_ent;
5402
0
    bool    found;
5403
5404
    /* be careful about padding */
5405
0
    memset(&key, 0, sizeof(ReorderBufferTupleCidKey));
5406
5407
    /* read all mappings till the end of the file */
5408
0
    pgstat_report_wait_start(WAIT_EVENT_REORDER_LOGICAL_MAPPING_READ);
5409
0
    readBytes = read(fd, &map, sizeof(LogicalRewriteMappingData));
5410
0
    pgstat_report_wait_end();
5411
5412
0
    if (readBytes < 0)
5413
0
      ereport(ERROR,
5414
0
          (errcode_for_file_access(),
5415
0
           errmsg("could not read file \"%s\": %m",
5416
0
              path)));
5417
0
    else if (readBytes == 0) /* EOF */
5418
0
      break;
5419
0
    else if (readBytes != sizeof(LogicalRewriteMappingData))
5420
0
      ereport(ERROR,
5421
0
          (errcode_for_file_access(),
5422
0
           errmsg("could not read from file \"%s\": read %zd of %zu",
5423
0
              path, readBytes,
5424
0
              sizeof(LogicalRewriteMappingData))));
5425
5426
0
    key.rlocator = map.old_locator;
5427
0
    ItemPointerCopy(&map.old_tid,
5428
0
            &key.tid);
5429
5430
5431
0
    ent = (ReorderBufferTupleCidEnt *)
5432
0
      hash_search(tuplecid_data, &key, HASH_FIND, NULL);
5433
5434
    /* no existing mapping, no need to update */
5435
0
    if (!ent)
5436
0
      continue;
5437
5438
0
    key.rlocator = map.new_locator;
5439
0
    ItemPointerCopy(&map.new_tid,
5440
0
            &key.tid);
5441
5442
0
    new_ent = (ReorderBufferTupleCidEnt *)
5443
0
      hash_search(tuplecid_data, &key, HASH_ENTER, &found);
5444
5445
0
    if (found)
5446
0
    {
5447
      /*
5448
       * Make sure the existing mapping makes sense. We sometime update
5449
       * old records that did not yet have a cmax (e.g. pg_class' own
5450
       * entry while rewriting it) during rewrites, so allow that.
5451
       */
5452
0
      Assert(ent->cmin == InvalidCommandId || ent->cmin == new_ent->cmin);
5453
0
      Assert(ent->cmax == InvalidCommandId || ent->cmax == new_ent->cmax);
5454
0
    }
5455
0
    else
5456
0
    {
5457
      /* update mapping */
5458
0
      new_ent->cmin = ent->cmin;
5459
0
      new_ent->cmax = ent->cmax;
5460
0
      new_ent->combocid = ent->combocid;
5461
0
    }
5462
0
  }
5463
5464
0
  if (CloseTransientFile(fd) != 0)
5465
0
    ereport(ERROR,
5466
0
        (errcode_for_file_access(),
5467
0
         errmsg("could not close file \"%s\": %m", path)));
5468
0
}
5469
5470
5471
/*
5472
 * Check whether the TransactionId 'xid' is in the pre-sorted array 'xip'.
5473
 */
5474
static bool
5475
TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
5476
0
{
5477
0
  return bsearch(&xid, xip, num,
5478
0
           sizeof(TransactionId), xidComparator) != NULL;
5479
0
}
5480
5481
/*
5482
 * list_sort() comparator for sorting RewriteMappingFiles in LSN order.
5483
 */
5484
static int
5485
file_sort_by_lsn(const ListCell *a_p, const ListCell *b_p)
5486
0
{
5487
0
  RewriteMappingFile *a = (RewriteMappingFile *) lfirst(a_p);
5488
0
  RewriteMappingFile *b = (RewriteMappingFile *) lfirst(b_p);
5489
5490
0
  return pg_cmp_u64(a->lsn, b->lsn);
5491
0
}
5492
5493
/*
5494
 * Apply any existing logical remapping files if there are any targeted at our
5495
 * transaction for relid.
5496
 */
5497
static void
5498
UpdateLogicalMappings(HTAB *tuplecid_data, Oid relid, Snapshot snapshot)
5499
{
5500
  DIR      *mapping_dir;
5501
  struct dirent *mapping_de;
5502
  List     *files = NIL;
5503
  ListCell   *file;
5504
  Oid     dboid = IsSharedRelation(relid) ? InvalidOid : MyDatabaseId;
5505
5506
  mapping_dir = AllocateDir(PG_LOGICAL_MAPPINGS_DIR);
5507
  while ((mapping_de = ReadDir(mapping_dir, PG_LOGICAL_MAPPINGS_DIR)) != NULL)
5508
  {
5509
    Oid     f_dboid;
5510
    Oid     f_relid;
5511
    TransactionId f_mapped_xid;
5512
    TransactionId f_create_xid;
5513
    XLogRecPtr  f_lsn;
5514
    uint32    f_hi,
5515
          f_lo;
5516
    RewriteMappingFile *f;
5517
5518
    if (strcmp(mapping_de->d_name, ".") == 0 ||
5519
      strcmp(mapping_de->d_name, "..") == 0)
5520
      continue;
5521
5522
    /* Ignore files that aren't ours */
5523
    if (strncmp(mapping_de->d_name, "map-", 4) != 0)
5524
      continue;
5525
5526
    if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT,
5527
           &f_dboid, &f_relid, &f_hi, &f_lo,
5528
           &f_mapped_xid, &f_create_xid) != 6)
5529
      elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name);
5530
5531
    f_lsn = ((uint64) f_hi) << 32 | f_lo;
5532
5533
    /* mapping for another database */
5534
    if (f_dboid != dboid)
5535
      continue;
5536
5537
    /* mapping for another relation */
5538
    if (f_relid != relid)
5539
      continue;
5540
5541
    /* did the creating transaction abort? */
5542
    if (!TransactionIdDidCommit(f_create_xid))
5543
      continue;
5544
5545
    /* not for our transaction */
5546
    if (!TransactionIdInArray(f_mapped_xid, snapshot->subxip, snapshot->subxcnt))
5547
      continue;
5548
5549
    /* ok, relevant, queue for apply */
5550
    f = palloc_object(RewriteMappingFile);
5551
    f->lsn = f_lsn;
5552
    strcpy(f->fname, mapping_de->d_name);
5553
    files = lappend(files, f);
5554
  }
5555
  FreeDir(mapping_dir);
5556
5557
  /* sort files so we apply them in LSN order */
5558
  list_sort(files, file_sort_by_lsn);
5559
5560
  foreach(file, files)
5561
  {
5562
    RewriteMappingFile *f = (RewriteMappingFile *) lfirst(file);
5563
5564
    elog(DEBUG1, "applying mapping: \"%s\" in %u", f->fname,
5565
       snapshot->subxip[0]);
5566
    ApplyLogicalMappingFile(tuplecid_data, f->fname);
5567
    pfree(f);
5568
  }
5569
}
5570
5571
/*
5572
 * Lookup cmin/cmax of a tuple, during logical decoding where we can't rely on
5573
 * combo CIDs.
5574
 */
5575
bool
5576
ResolveCminCmaxDuringDecoding(HTAB *tuplecid_data,
5577
                Snapshot snapshot,
5578
                HeapTuple htup, Buffer buffer,
5579
                CommandId *cmin, CommandId *cmax)
5580
0
{
5581
0
  ReorderBufferTupleCidKey key;
5582
0
  ReorderBufferTupleCidEnt *ent;
5583
0
  ForkNumber  forkno;
5584
0
  BlockNumber blockno;
5585
0
  bool    updated_mapping = false;
5586
5587
  /*
5588
   * Return unresolved if tuplecid_data is not valid.  That's because when
5589
   * streaming in-progress transactions we may run into tuples with the CID
5590
   * before actually decoding them.  Think e.g. about INSERT followed by
5591
   * TRUNCATE, where the TRUNCATE may not be decoded yet when applying the
5592
   * INSERT.  So in such cases, we assume the CID is from the future
5593
   * command.
5594
   */
5595
0
  if (tuplecid_data == NULL)
5596
0
    return false;
5597
5598
  /* be careful about padding */
5599
0
  memset(&key, 0, sizeof(key));
5600
5601
0
  Assert(!BufferIsLocal(buffer));
5602
5603
  /*
5604
   * get relfilelocator from the buffer, no convenient way to access it
5605
   * other than that.
5606
   */
5607
0
  BufferGetTag(buffer, &key.rlocator, &forkno, &blockno);
5608
5609
  /* tuples can only be in the main fork */
5610
0
  Assert(forkno == MAIN_FORKNUM);
5611
0
  Assert(blockno == ItemPointerGetBlockNumber(&htup->t_self));
5612
5613
0
  ItemPointerCopy(&htup->t_self,
5614
0
          &key.tid);
5615
5616
0
restart:
5617
0
  ent = (ReorderBufferTupleCidEnt *)
5618
0
    hash_search(tuplecid_data, &key, HASH_FIND, NULL);
5619
5620
  /*
5621
   * failed to find a mapping, check whether the table was rewritten and
5622
   * apply mapping if so, but only do that once - there can be no new
5623
   * mappings while we are in here since we have to hold a lock on the
5624
   * relation.
5625
   */
5626
0
  if (ent == NULL && !updated_mapping)
5627
0
  {
5628
0
    UpdateLogicalMappings(tuplecid_data, htup->t_tableOid, snapshot);
5629
    /* now check but don't update for a mapping again */
5630
0
    updated_mapping = true;
5631
0
    goto restart;
5632
0
  }
5633
0
  else if (ent == NULL)
5634
0
    return false;
5635
5636
0
  if (cmin)
5637
0
    *cmin = ent->cmin;
5638
0
  if (cmax)
5639
0
    *cmax = ent->cmax;
5640
0
  return true;
5641
0
}
5642
5643
/*
5644
 * Count invalidation messages of specified transaction.
5645
 *
5646
 * Returns number of messages, and msgs is set to the pointer of the linked
5647
 * list for the messages.
5648
 */
5649
uint32
5650
ReorderBufferGetInvalidations(ReorderBuffer *rb, TransactionId xid,
5651
                SharedInvalidationMessage **msgs)
5652
0
{
5653
0
  ReorderBufferTXN *txn;
5654
5655
0
  txn = ReorderBufferTXNByXid(rb, xid, false, NULL, InvalidXLogRecPtr,
5656
0
                false);
5657
5658
0
  if (txn == NULL)
5659
0
    return 0;
5660
5661
0
  *msgs = txn->invalidations;
5662
5663
0
  return txn->ninvalidations;
5664
0
}