Coverage Report

Created: 2026-08-13 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/storage/buffer/localbuf.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * localbuf.c
4
 *    local buffer manager. Fast buffer manager for temporary tables,
5
 *    which never need to be WAL-logged or checkpointed, etc.
6
 *
7
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8
 * Portions Copyright (c) 1994-5, Regents of the University of California
9
 *
10
 *
11
 * IDENTIFICATION
12
 *    src/backend/storage/buffer/localbuf.c
13
 *
14
 *-------------------------------------------------------------------------
15
 */
16
#include "postgres.h"
17
18
#include "access/parallel.h"
19
#include "executor/instrument.h"
20
#include "pgstat.h"
21
#include "storage/aio.h"
22
#include "storage/buf_internals.h"
23
#include "storage/bufmgr.h"
24
#include "storage/fd.h"
25
#include "utils/guc_hooks.h"
26
#include "utils/memdebug.h"
27
#include "utils/memutils.h"
28
#include "utils/rel.h"
29
#include "utils/resowner.h"
30
31
32
/*#define LBDEBUG*/
33
34
/* entry for buffer lookup hashtable */
35
typedef struct
36
{
37
  BufferTag key;      /* Tag of a disk page */
38
  int     id;       /* Associated local buffer's index */
39
} LocalBufferLookupEnt;
40
41
/* Note: this macro only works on local buffers, not shared ones! */
42
#define LocalBufHdrGetBlock(bufHdr) \
43
0
  LocalBufferBlockPointers[-((bufHdr)->buf_id + 2)]
44
45
int     NLocBuffer = 0;   /* until buffers are initialized */
46
47
BufferDesc *LocalBufferDescriptors = NULL;
48
Block    *LocalBufferBlockPointers = NULL;
49
int32    *LocalRefCount = NULL;
50
51
static int  nextFreeLocalBufId = 0;
52
53
static HTAB *LocalBufHash = NULL;
54
55
/* number of local buffers pinned at least once */
56
static int  NLocalPinnedBuffers = 0;
57
58
59
static void InitLocalBuffers(void);
60
static Block GetLocalBufferStorage(void);
61
static Buffer GetLocalVictimBuffer(void);
62
63
64
/*
65
 * PrefetchLocalBuffer -
66
 *    initiate asynchronous read of a block of a relation
67
 *
68
 * Do PrefetchBuffer's work for temporary relations.
69
 * No-op if prefetching isn't compiled in.
70
 */
71
PrefetchBufferResult
72
PrefetchLocalBuffer(SMgrRelation smgr, ForkNumber forkNum,
73
          BlockNumber blockNum)
74
0
{
75
0
  PrefetchBufferResult result = {InvalidBuffer, false};
76
0
  BufferTag newTag;     /* identity of requested block */
77
0
  LocalBufferLookupEnt *hresult;
78
79
0
  InitBufferTag(&newTag, &smgr->smgr_rlocator.locator, forkNum, blockNum);
80
81
  /* Initialize local buffers if first request in this session */
82
0
  if (LocalBufHash == NULL)
83
0
    InitLocalBuffers();
84
85
  /* See if the desired buffer already exists */
86
0
  hresult = (LocalBufferLookupEnt *)
87
0
    hash_search(LocalBufHash, &newTag, HASH_FIND, NULL);
88
89
0
  if (hresult)
90
0
  {
91
    /* Yes, so nothing to do */
92
0
    result.recent_buffer = -hresult->id - 1;
93
0
  }
94
0
  else
95
0
  {
96
0
#ifdef USE_PREFETCH
97
    /* Not in buffers, so initiate prefetch */
98
0
    if ((io_direct_flags & IO_DIRECT_DATA) == 0 &&
99
0
      smgrprefetch(smgr, forkNum, blockNum, 1))
100
0
    {
101
0
      result.initiated_io = true;
102
0
    }
103
0
#endif              /* USE_PREFETCH */
104
0
  }
105
106
0
  return result;
107
0
}
108
109
110
/*
111
 * LocalBufferAlloc -
112
 *    Find or create a local buffer for the given page of the given relation.
113
 *
114
 * API is similar to bufmgr.c's BufferAlloc, except that we do not need to do
115
 * any locking since this is all local.  We support only default access
116
 * strategy (hence, usage_count is always advanced).
117
 */
118
BufferDesc *
119
LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
120
         bool *foundPtr)
121
0
{
122
0
  BufferTag newTag;     /* identity of requested block */
123
0
  LocalBufferLookupEnt *hresult;
124
0
  BufferDesc *bufHdr;
125
0
  Buffer    victim_buffer;
126
0
  int     bufid;
127
0
  bool    found;
128
129
0
  InitBufferTag(&newTag, &smgr->smgr_rlocator.locator, forkNum, blockNum);
130
131
  /* Initialize local buffers if first request in this session */
132
0
  if (LocalBufHash == NULL)
133
0
    InitLocalBuffers();
134
135
0
  ResourceOwnerEnlarge(CurrentResourceOwner);
136
137
  /* See if the desired buffer already exists */
138
0
  hresult = (LocalBufferLookupEnt *)
139
0
    hash_search(LocalBufHash, &newTag, HASH_FIND, NULL);
140
141
0
  if (hresult)
142
0
  {
143
0
    bufid = hresult->id;
144
0
    bufHdr = GetLocalBufferDescriptor(bufid);
145
0
    Assert(BufferTagsEqual(&bufHdr->tag, &newTag));
146
147
0
    *foundPtr = PinLocalBuffer(bufHdr, true);
148
0
  }
149
0
  else
150
0
  {
151
0
    uint64    buf_state;
152
153
0
    victim_buffer = GetLocalVictimBuffer();
154
0
    bufid = -victim_buffer - 1;
155
0
    bufHdr = GetLocalBufferDescriptor(bufid);
156
157
0
    hresult = (LocalBufferLookupEnt *)
158
0
      hash_search(LocalBufHash, &newTag, HASH_ENTER, &found);
159
0
    if (found)       /* shouldn't happen */
160
0
      elog(ERROR, "local buffer hash table corrupted");
161
0
    hresult->id = bufid;
162
163
    /*
164
     * it's all ours now.
165
     */
166
0
    bufHdr->tag = newTag;
167
168
0
    buf_state = pg_atomic_read_u64(&bufHdr->state);
169
0
    buf_state &= ~(BUF_FLAG_MASK | BUF_USAGECOUNT_MASK);
170
0
    buf_state |= BM_TAG_VALID | BUF_USAGECOUNT_ONE;
171
0
    pg_atomic_unlocked_write_u64(&bufHdr->state, buf_state);
172
173
0
    *foundPtr = false;
174
0
  }
175
176
0
  return bufHdr;
177
0
}
178
179
/*
180
 * Like FlushBuffer(), just for local buffers.
181
 */
182
void
183
FlushLocalBuffer(BufferDesc *bufHdr, SMgrRelation reln)
184
0
{
185
0
  instr_time  io_start;
186
0
  Page    localpage = (char *) LocalBufHdrGetBlock(bufHdr);
187
188
0
  Assert(LocalRefCount[-BufferDescriptorGetBuffer(bufHdr) - 1] > 0);
189
190
  /*
191
   * Try to start an I/O operation.  There currently are no reasons for
192
   * StartLocalBufferIO to return anything other than
193
   * BUFFER_IO_READY_FOR_IO, so we raise an error in that case.
194
   */
195
0
  if (StartLocalBufferIO(bufHdr, false, true, NULL) != BUFFER_IO_READY_FOR_IO)
196
0
    elog(ERROR, "failed to start write IO on local buffer");
197
198
  /* Find smgr relation for buffer */
199
0
  if (reln == NULL)
200
0
    reln = smgropen(BufTagGetRelFileLocator(&bufHdr->tag),
201
0
            MyProcNumber);
202
203
0
  PageSetChecksum(localpage, bufHdr->tag.blockNum);
204
205
0
  io_start = pgstat_prepare_io_time(track_io_timing);
206
207
  /* And write... */
208
0
  smgrwrite(reln,
209
0
        BufTagGetForkNum(&bufHdr->tag),
210
0
        bufHdr->tag.blockNum,
211
0
        localpage,
212
0
        false);
213
214
  /* Temporary table I/O does not use Buffer Access Strategies */
215
0
  pgstat_count_io_op_time(IOOBJECT_TEMP_RELATION, IOCONTEXT_NORMAL,
216
0
              IOOP_WRITE, io_start, 1, BLCKSZ);
217
218
  /* Mark not-dirty */
219
0
  TerminateLocalBufferIO(bufHdr, true, 0, false);
220
221
0
  pgBufferUsage.local_blks_written++;
222
0
}
223
224
static Buffer
225
GetLocalVictimBuffer(void)
226
0
{
227
0
  int     victim_bufid;
228
0
  int     trycounter;
229
0
  BufferDesc *bufHdr;
230
231
0
  ResourceOwnerEnlarge(CurrentResourceOwner);
232
233
  /*
234
   * Need to get a new buffer.  We use a clock-sweep algorithm (essentially
235
   * the same as what freelist.c does now...)
236
   */
237
0
  trycounter = NLocBuffer;
238
0
  for (;;)
239
0
  {
240
0
    victim_bufid = nextFreeLocalBufId;
241
242
0
    if (++nextFreeLocalBufId >= NLocBuffer)
243
0
      nextFreeLocalBufId = 0;
244
245
0
    bufHdr = GetLocalBufferDescriptor(victim_bufid);
246
247
0
    if (LocalRefCount[victim_bufid] == 0)
248
0
    {
249
0
      uint64    buf_state = pg_atomic_read_u64(&bufHdr->state);
250
251
0
      if (BUF_STATE_GET_USAGECOUNT(buf_state) > 0)
252
0
      {
253
0
        buf_state -= BUF_USAGECOUNT_ONE;
254
0
        pg_atomic_unlocked_write_u64(&bufHdr->state, buf_state);
255
0
        trycounter = NLocBuffer;
256
0
      }
257
0
      else if (BUF_STATE_GET_REFCOUNT(buf_state) > 0)
258
0
      {
259
        /*
260
         * This can be reached if the backend initiated AIO for this
261
         * buffer and then errored out.
262
         */
263
0
      }
264
0
      else
265
0
      {
266
        /* Found a usable buffer */
267
0
        PinLocalBuffer(bufHdr, false);
268
0
        break;
269
0
      }
270
0
    }
271
0
    else if (--trycounter == 0)
272
0
      ereport(ERROR,
273
0
          (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
274
0
           errmsg("no empty local buffer available")));
275
0
  }
276
277
  /*
278
   * lazy memory allocation: allocate space on first use of a buffer.
279
   */
280
0
  if (LocalBufHdrGetBlock(bufHdr) == NULL)
281
0
  {
282
    /* Set pointer for use by BufferGetBlock() macro */
283
0
    LocalBufHdrGetBlock(bufHdr) = GetLocalBufferStorage();
284
0
  }
285
286
  /*
287
   * this buffer is not referenced but it might still be dirty. if that's
288
   * the case, write it out before reusing it!
289
   */
290
0
  if (pg_atomic_read_u64(&bufHdr->state) & BM_DIRTY)
291
0
    FlushLocalBuffer(bufHdr, NULL);
292
293
  /*
294
   * Remove the victim buffer from the hashtable and mark as invalid.
295
   */
296
0
  if (pg_atomic_read_u64(&bufHdr->state) & BM_TAG_VALID)
297
0
  {
298
0
    InvalidateLocalBuffer(bufHdr, false);
299
300
0
    pgstat_count_io_op(IOOBJECT_TEMP_RELATION, IOCONTEXT_NORMAL, IOOP_EVICT, 1, 0);
301
0
  }
302
303
0
  return BufferDescriptorGetBuffer(bufHdr);
304
0
}
305
306
/* see GetPinLimit() */
307
uint32
308
GetLocalPinLimit(void)
309
0
{
310
  /*
311
   * Every backend has its own temporary buffers, but we leave headroom for
312
   * concurrent pin-holders -- like multiple scans in the same query.
313
   */
314
0
  return num_temp_buffers / 4;
315
0
}
316
317
/* see GetAdditionalPinLimit() */
318
uint32
319
GetAdditionalLocalPinLimit(void)
320
0
{
321
0
  uint32    total = GetLocalPinLimit();
322
323
0
  Assert(NLocalPinnedBuffers <= num_temp_buffers);
324
325
0
  if (NLocalPinnedBuffers >= total)
326
0
    return 0;
327
0
  return total - NLocalPinnedBuffers;
328
0
}
329
330
/* see LimitAdditionalPins() */
331
void
332
LimitAdditionalLocalPins(uint32 *additional_pins)
333
0
{
334
0
  uint32    max_pins;
335
336
0
  if (*additional_pins <= 1)
337
0
    return;
338
339
  /*
340
   * In contrast to LimitAdditionalPins() other backends don't play a role
341
   * here. We can allow up to NLocBuffer pins in total, but it might not be
342
   * initialized yet so read num_temp_buffers.
343
   */
344
0
  max_pins = (num_temp_buffers - NLocalPinnedBuffers);
345
346
0
  if (*additional_pins >= max_pins)
347
0
    *additional_pins = max_pins;
348
0
}
349
350
/*
351
 * Implementation of ExtendBufferedRelBy() and ExtendBufferedRelTo() for
352
 * temporary buffers.
353
 */
354
BlockNumber
355
ExtendBufferedRelLocal(BufferManagerRelation bmr,
356
             ForkNumber fork,
357
             uint32 flags,
358
             uint32 extend_by,
359
             BlockNumber extend_upto,
360
             Buffer *buffers,
361
             uint32 *extended_by)
362
0
{
363
0
  BlockNumber first_block;
364
0
  instr_time  io_start;
365
366
  /* Initialize local buffers if first request in this session */
367
0
  if (LocalBufHash == NULL)
368
0
    InitLocalBuffers();
369
370
0
  LimitAdditionalLocalPins(&extend_by);
371
372
0
  for (uint32 i = 0; i < extend_by; i++)
373
0
  {
374
0
    BufferDesc *buf_hdr;
375
0
    Block   buf_block;
376
377
0
    buffers[i] = GetLocalVictimBuffer();
378
0
    buf_hdr = GetLocalBufferDescriptor(-buffers[i] - 1);
379
0
    buf_block = LocalBufHdrGetBlock(buf_hdr);
380
381
    /* new buffers are zero-filled */
382
0
    MemSet(buf_block, 0, BLCKSZ);
383
0
  }
384
385
0
  first_block = smgrnblocks(BMR_GET_SMGR(bmr), fork);
386
387
0
  if (extend_upto != InvalidBlockNumber)
388
0
  {
389
    /*
390
     * In contrast to shared relations, nothing could change the relation
391
     * size concurrently. Thus we shouldn't end up finding that we don't
392
     * need to do anything.
393
     */
394
0
    Assert(first_block <= extend_upto);
395
396
0
    Assert((uint64) first_block + extend_by <= extend_upto);
397
0
  }
398
399
  /* Fail if relation is already at maximum possible length */
400
0
  if ((uint64) first_block + extend_by >= MaxBlockNumber)
401
0
    ereport(ERROR,
402
0
        (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
403
0
         errmsg("cannot extend relation %s beyond %u blocks",
404
0
            relpath(BMR_GET_SMGR(bmr)->smgr_rlocator, fork).str,
405
0
            MaxBlockNumber)));
406
407
0
  for (uint32 i = 0; i < extend_by; i++)
408
0
  {
409
0
    int     victim_buf_id;
410
0
    BufferDesc *victim_buf_hdr;
411
0
    BufferTag tag;
412
0
    LocalBufferLookupEnt *hresult;
413
0
    bool    found;
414
415
0
    victim_buf_id = -buffers[i] - 1;
416
0
    victim_buf_hdr = GetLocalBufferDescriptor(victim_buf_id);
417
418
    /* in case we need to pin an existing buffer below */
419
0
    ResourceOwnerEnlarge(CurrentResourceOwner);
420
421
0
    InitBufferTag(&tag, &BMR_GET_SMGR(bmr)->smgr_rlocator.locator, fork,
422
0
            first_block + i);
423
424
0
    hresult = (LocalBufferLookupEnt *)
425
0
      hash_search(LocalBufHash, &tag, HASH_ENTER, &found);
426
0
    if (found)
427
0
    {
428
0
      BufferDesc *existing_hdr;
429
0
      uint64    buf_state;
430
431
0
      UnpinLocalBuffer(BufferDescriptorGetBuffer(victim_buf_hdr));
432
433
0
      existing_hdr = GetLocalBufferDescriptor(hresult->id);
434
0
      PinLocalBuffer(existing_hdr, false);
435
0
      buffers[i] = BufferDescriptorGetBuffer(existing_hdr);
436
437
      /*
438
       * Clear the BM_VALID bit, do StartLocalBufferIO() and proceed.
439
       */
440
0
      buf_state = pg_atomic_read_u64(&existing_hdr->state);
441
0
      Assert(buf_state & BM_TAG_VALID);
442
0
      Assert(!(buf_state & BM_DIRTY));
443
0
      buf_state &= ~BM_VALID;
444
0
      pg_atomic_unlocked_write_u64(&existing_hdr->state, buf_state);
445
446
      /* no need to loop for local buffers */
447
0
      StartLocalBufferIO(existing_hdr, true, true, NULL);
448
0
    }
449
0
    else
450
0
    {
451
0
      uint64    buf_state = pg_atomic_read_u64(&victim_buf_hdr->state);
452
453
0
      Assert(!(buf_state & (BM_VALID | BM_TAG_VALID | BM_DIRTY)));
454
455
0
      victim_buf_hdr->tag = tag;
456
457
0
      buf_state |= BM_TAG_VALID | BUF_USAGECOUNT_ONE;
458
459
0
      pg_atomic_unlocked_write_u64(&victim_buf_hdr->state, buf_state);
460
461
0
      hresult->id = victim_buf_id;
462
463
0
      StartLocalBufferIO(victim_buf_hdr, true, true, NULL);
464
0
    }
465
0
  }
466
467
0
  io_start = pgstat_prepare_io_time(track_io_timing);
468
469
  /* actually extend relation */
470
0
  smgrzeroextend(BMR_GET_SMGR(bmr), fork, first_block, extend_by, false);
471
472
0
  pgstat_count_io_op_time(IOOBJECT_TEMP_RELATION, IOCONTEXT_NORMAL, IOOP_EXTEND,
473
0
              io_start, 1, extend_by * BLCKSZ);
474
475
0
  for (uint32 i = 0; i < extend_by; i++)
476
0
  {
477
0
    Buffer    buf = buffers[i];
478
0
    BufferDesc *buf_hdr;
479
0
    uint64    buf_state;
480
481
0
    buf_hdr = GetLocalBufferDescriptor(-buf - 1);
482
483
0
    buf_state = pg_atomic_read_u64(&buf_hdr->state);
484
0
    buf_state |= BM_VALID;
485
0
    pg_atomic_unlocked_write_u64(&buf_hdr->state, buf_state);
486
0
  }
487
488
0
  *extended_by = extend_by;
489
490
0
  pgBufferUsage.local_blks_written += extend_by;
491
492
0
  return first_block;
493
0
}
494
495
/*
496
 * MarkLocalBufferDirty -
497
 *    mark a local buffer dirty
498
 */
499
void
500
MarkLocalBufferDirty(Buffer buffer)
501
0
{
502
0
  int     bufid;
503
0
  BufferDesc *bufHdr;
504
0
  uint64    buf_state;
505
506
0
  Assert(BufferIsLocal(buffer));
507
508
#ifdef LBDEBUG
509
  fprintf(stderr, "LB DIRTY %d\n", buffer);
510
#endif
511
512
0
  bufid = -buffer - 1;
513
514
0
  Assert(LocalRefCount[bufid] > 0);
515
516
0
  bufHdr = GetLocalBufferDescriptor(bufid);
517
518
0
  buf_state = pg_atomic_read_u64(&bufHdr->state);
519
520
0
  if (!(buf_state & BM_DIRTY))
521
0
    pgBufferUsage.local_blks_dirtied++;
522
523
0
  buf_state |= BM_DIRTY;
524
525
0
  pg_atomic_unlocked_write_u64(&bufHdr->state, buf_state);
526
0
}
527
528
/*
529
 * Like StartSharedBufferIO, but for local buffers
530
 */
531
StartBufferIOResult
532
StartLocalBufferIO(BufferDesc *bufHdr, bool forInput, bool wait, PgAioWaitRef *io_wref)
533
0
{
534
0
  uint64    buf_state;
535
536
  /*
537
   * With AIO the buffer could have IO in progress, e.g. when there are two
538
   * scans of the same relation.  Either wait for the other IO (if wait =
539
   * true and io_wref == NULL) or return BUFFER_IO_IN_PROGRESS;
540
   */
541
0
  if (pgaio_wref_valid(&bufHdr->io_wref))
542
0
  {
543
0
    PgAioWaitRef buf_wref = bufHdr->io_wref;
544
545
0
    if (io_wref != NULL)
546
0
    {
547
      /* We've already asynchronously started this IO, so join it */
548
0
      *io_wref = buf_wref;
549
0
      return BUFFER_IO_IN_PROGRESS;
550
0
    }
551
552
    /*
553
     * For temp buffers we should never need to wait in
554
     * StartLocalBufferIO() when called with io_wref == NULL while there
555
     * are staged IOs, as it's not allowed to call code that is not aware
556
     * of AIO while in batch mode.
557
     */
558
0
    Assert(!pgaio_have_staged());
559
560
0
    if (!wait)
561
0
      return BUFFER_IO_IN_PROGRESS;
562
563
0
    pgaio_wref_wait(&buf_wref);
564
0
  }
565
566
  /* Once we get here, there is definitely no I/O active on this buffer */
567
568
  /* Check if someone else already did the I/O */
569
0
  buf_state = pg_atomic_read_u64(&bufHdr->state);
570
0
  if (forInput ? (buf_state & BM_VALID) : !(buf_state & BM_DIRTY))
571
0
  {
572
0
    return BUFFER_IO_ALREADY_DONE;
573
0
  }
574
575
  /* BM_IO_IN_PROGRESS isn't currently used for local buffers */
576
577
  /* local buffers don't track IO using resowners */
578
579
0
  return BUFFER_IO_READY_FOR_IO;
580
0
}
581
582
/*
583
 * Like TerminateBufferIO, but for local buffers
584
 */
585
void
586
TerminateLocalBufferIO(BufferDesc *bufHdr, bool clear_dirty, uint64 set_flag_bits,
587
             bool release_aio)
588
0
{
589
  /* Only need to adjust flags */
590
0
  uint64    buf_state = pg_atomic_read_u64(&bufHdr->state);
591
592
  /* BM_IO_IN_PROGRESS isn't currently used for local buffers */
593
594
  /* Clear earlier errors, if this IO failed, it'll be marked again */
595
0
  buf_state &= ~BM_IO_ERROR;
596
597
0
  if (clear_dirty)
598
0
    buf_state &= ~BM_DIRTY;
599
600
0
  if (release_aio)
601
0
  {
602
    /* release pin held by IO subsystem, see also buffer_stage_common() */
603
0
    Assert(BUF_STATE_GET_REFCOUNT(buf_state) > 0);
604
0
    buf_state -= BUF_REFCOUNT_ONE;
605
0
    pgaio_wref_clear(&bufHdr->io_wref);
606
0
  }
607
608
0
  buf_state |= set_flag_bits;
609
0
  pg_atomic_unlocked_write_u64(&bufHdr->state, buf_state);
610
611
  /* local buffers don't track IO using resowners */
612
613
  /* local buffers don't use the IO CV, as no other process can see buffer */
614
615
  /* local buffers don't use BM_PIN_COUNT_WAITER, so no need to wake */
616
0
}
617
618
/*
619
 * InvalidateLocalBuffer -- mark a local buffer invalid.
620
 *
621
 * If check_unreferenced is true, error out if the buffer is still
622
 * pinned. Passing false is appropriate when calling InvalidateLocalBuffer()
623
 * as part of changing the identity of a buffer, instead of just dropping the
624
 * buffer.
625
 *
626
 * See also InvalidateBuffer().
627
 */
628
void
629
InvalidateLocalBuffer(BufferDesc *bufHdr, bool check_unreferenced)
630
0
{
631
0
  Buffer    buffer = BufferDescriptorGetBuffer(bufHdr);
632
0
  int     bufid = -buffer - 1;
633
0
  uint64    buf_state;
634
0
  LocalBufferLookupEnt *hresult;
635
636
  /*
637
   * It's possible that we started IO on this buffer before e.g. aborting
638
   * the transaction that created a table. We need to wait for that IO to
639
   * complete before removing / reusing the buffer.
640
   */
641
0
  if (pgaio_wref_valid(&bufHdr->io_wref))
642
0
  {
643
0
    PgAioWaitRef iow = bufHdr->io_wref;
644
645
0
    pgaio_wref_wait(&iow);
646
0
    Assert(!pgaio_wref_valid(&bufHdr->io_wref));
647
0
  }
648
649
0
  buf_state = pg_atomic_read_u64(&bufHdr->state);
650
651
  /*
652
   * We need to test not just LocalRefCount[bufid] but also the BufferDesc
653
   * itself, as the latter is used to represent a pin by the AIO subsystem.
654
   * This can happen if AIO is initiated and then the query errors out.
655
   */
656
0
  if (check_unreferenced &&
657
0
    (LocalRefCount[bufid] != 0 || BUF_STATE_GET_REFCOUNT(buf_state) != 0))
658
0
    elog(ERROR, "block %u of %s is still referenced (local %d)",
659
0
       bufHdr->tag.blockNum,
660
0
       relpathbackend(BufTagGetRelFileLocator(&bufHdr->tag),
661
0
              MyProcNumber,
662
0
              BufTagGetForkNum(&bufHdr->tag)).str,
663
0
       LocalRefCount[bufid]);
664
665
  /* Remove entry from hashtable */
666
0
  hresult = (LocalBufferLookupEnt *)
667
0
    hash_search(LocalBufHash, &bufHdr->tag, HASH_REMOVE, NULL);
668
0
  if (!hresult)       /* shouldn't happen */
669
0
    elog(ERROR, "local buffer hash table corrupted");
670
  /* Mark buffer invalid */
671
0
  ClearBufferTag(&bufHdr->tag);
672
0
  buf_state &= ~BUF_FLAG_MASK;
673
0
  buf_state &= ~BUF_USAGECOUNT_MASK;
674
0
  pg_atomic_unlocked_write_u64(&bufHdr->state, buf_state);
675
0
}
676
677
/*
678
 * DropRelationLocalBuffers
679
 *    This function removes from the buffer pool all the pages of the
680
 *    specified relation that have block numbers >= firstDelBlock.
681
 *    (In particular, with firstDelBlock = 0, all pages are removed.)
682
 *    Dirty pages are simply dropped, without bothering to write them
683
 *    out first.  Therefore, this is NOT rollback-able, and so should be
684
 *    used only with extreme caution!
685
 *
686
 *    See DropRelationBuffers in bufmgr.c for more notes.
687
 */
688
void
689
DropRelationLocalBuffers(RelFileLocator rlocator, ForkNumber *forkNum,
690
             int nforks, BlockNumber *firstDelBlock)
691
0
{
692
0
  int     i;
693
0
  int     j;
694
695
0
  for (i = 0; i < NLocBuffer; i++)
696
0
  {
697
0
    BufferDesc *bufHdr = GetLocalBufferDescriptor(i);
698
0
    uint64    buf_state;
699
700
0
    buf_state = pg_atomic_read_u64(&bufHdr->state);
701
702
0
    if (!(buf_state & BM_TAG_VALID) ||
703
0
      !BufTagMatchesRelFileLocator(&bufHdr->tag, &rlocator))
704
0
      continue;
705
706
0
    for (j = 0; j < nforks; j++)
707
0
    {
708
0
      if (BufTagGetForkNum(&bufHdr->tag) == forkNum[j] &&
709
0
        bufHdr->tag.blockNum >= firstDelBlock[j])
710
0
      {
711
0
        InvalidateLocalBuffer(bufHdr, true);
712
0
        break;
713
0
      }
714
0
    }
715
0
  }
716
0
}
717
718
/*
719
 * DropRelationAllLocalBuffers
720
 *    This function removes from the buffer pool all pages of all forks
721
 *    of the specified relation.
722
 *
723
 *    See DropRelationsAllBuffers in bufmgr.c for more notes.
724
 */
725
void
726
DropRelationAllLocalBuffers(RelFileLocator rlocator)
727
0
{
728
0
  int     i;
729
730
0
  for (i = 0; i < NLocBuffer; i++)
731
0
  {
732
0
    BufferDesc *bufHdr = GetLocalBufferDescriptor(i);
733
0
    uint64    buf_state;
734
735
0
    buf_state = pg_atomic_read_u64(&bufHdr->state);
736
737
0
    if ((buf_state & BM_TAG_VALID) &&
738
0
      BufTagMatchesRelFileLocator(&bufHdr->tag, &rlocator))
739
0
    {
740
0
      InvalidateLocalBuffer(bufHdr, true);
741
0
    }
742
0
  }
743
0
}
744
745
/*
746
 * InitLocalBuffers -
747
 *    init the local buffer cache. Since most queries (esp. multi-user ones)
748
 *    don't involve local buffers, we delay allocating actual memory for the
749
 *    buffers until we need them; just make the buffer headers here.
750
 */
751
static void
752
InitLocalBuffers(void)
753
0
{
754
0
  int     nbufs = num_temp_buffers;
755
0
  HASHCTL   info;
756
0
  int     i;
757
758
  /*
759
   * Parallel workers can't access data in temporary tables, because they
760
   * have no visibility into the local buffers of their leader.  This is a
761
   * convenient, low-cost place to provide a backstop check for that.  Note
762
   * that we don't wish to prevent a parallel worker from accessing catalog
763
   * metadata about a temp table, so checks at higher levels would be
764
   * inappropriate.
765
   */
766
0
  if (IsParallelWorker())
767
0
    ereport(ERROR,
768
0
        (errcode(ERRCODE_INVALID_TRANSACTION_STATE),
769
0
         errmsg("cannot access temporary tables during a parallel operation")));
770
771
  /* Allocate and zero buffer headers and auxiliary arrays */
772
0
  LocalBufferDescriptors = (BufferDesc *) calloc(nbufs, sizeof(BufferDesc));
773
0
  LocalBufferBlockPointers = (Block *) calloc(nbufs, sizeof(Block));
774
0
  LocalRefCount = (int32 *) calloc(nbufs, sizeof(int32));
775
0
  if (!LocalBufferDescriptors || !LocalBufferBlockPointers || !LocalRefCount)
776
0
    ereport(FATAL,
777
0
        (errcode(ERRCODE_OUT_OF_MEMORY),
778
0
         errmsg("out of memory")));
779
780
0
  nextFreeLocalBufId = 0;
781
782
  /* initialize fields that need to start off nonzero */
783
0
  for (i = 0; i < nbufs; i++)
784
0
  {
785
0
    BufferDesc *buf = GetLocalBufferDescriptor(i);
786
787
    /*
788
     * negative to indicate local buffer. This is tricky: shared buffers
789
     * start with 0. We have to start with -2. (Note that the routine
790
     * BufferDescriptorGetBuffer adds 1 to buf_id so our first buffer id
791
     * is -1.)
792
     */
793
0
    buf->buf_id = -i - 2;
794
795
0
    pgaio_wref_clear(&buf->io_wref);
796
797
    /*
798
     * Intentionally do not initialize the buffer's atomic variable
799
     * (besides zeroing the underlying memory above). That way we get
800
     * errors on platforms without atomics, if somebody (re-)introduces
801
     * atomic operations for local buffers.
802
     */
803
0
  }
804
805
  /* Create the lookup hash table */
806
0
  info.keysize = sizeof(BufferTag);
807
0
  info.entrysize = sizeof(LocalBufferLookupEnt);
808
809
0
  LocalBufHash = hash_create("Local Buffer Lookup Table",
810
0
                 nbufs,
811
0
                 &info,
812
0
                 HASH_ELEM | HASH_BLOBS);
813
814
0
  if (!LocalBufHash)
815
0
    elog(ERROR, "could not initialize local buffer hash table");
816
817
  /* Initialization done, mark buffers allocated */
818
0
  NLocBuffer = nbufs;
819
0
}
820
821
/*
822
 * XXX: We could have a slightly more efficient version of PinLocalBuffer()
823
 * that does not support adjusting the usagecount - but so far it does not
824
 * seem worth the trouble.
825
 *
826
 * Note that ResourceOwnerEnlarge() must have been done already.
827
 */
828
bool
829
PinLocalBuffer(BufferDesc *buf_hdr, bool adjust_usagecount)
830
0
{
831
0
  uint64    buf_state;
832
0
  Buffer    buffer = BufferDescriptorGetBuffer(buf_hdr);
833
0
  int     bufid = -buffer - 1;
834
835
0
  buf_state = pg_atomic_read_u64(&buf_hdr->state);
836
837
0
  if (LocalRefCount[bufid] == 0)
838
0
  {
839
0
    NLocalPinnedBuffers++;
840
0
    buf_state += BUF_REFCOUNT_ONE;
841
0
    if (adjust_usagecount &&
842
0
      BUF_STATE_GET_USAGECOUNT(buf_state) < BM_MAX_USAGE_COUNT)
843
0
    {
844
0
      buf_state += BUF_USAGECOUNT_ONE;
845
0
    }
846
0
    pg_atomic_unlocked_write_u64(&buf_hdr->state, buf_state);
847
848
    /*
849
     * See comment in PinBuffer().
850
     *
851
     * If the buffer isn't allocated yet, it'll be marked as defined in
852
     * GetLocalBufferStorage().
853
     */
854
0
    if (LocalBufHdrGetBlock(buf_hdr) != NULL)
855
0
      VALGRIND_MAKE_MEM_DEFINED(LocalBufHdrGetBlock(buf_hdr), BLCKSZ);
856
0
  }
857
0
  LocalRefCount[bufid]++;
858
0
  ResourceOwnerRememberBuffer(CurrentResourceOwner,
859
0
                BufferDescriptorGetBuffer(buf_hdr));
860
861
0
  return buf_state & BM_VALID;
862
0
}
863
864
void
865
UnpinLocalBuffer(Buffer buffer)
866
0
{
867
0
  UnpinLocalBufferNoOwner(buffer);
868
0
  ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
869
0
}
870
871
void
872
UnpinLocalBufferNoOwner(Buffer buffer)
873
0
{
874
0
  int     buffid = -buffer - 1;
875
876
0
  Assert(BufferIsLocal(buffer));
877
0
  Assert(LocalRefCount[buffid] > 0);
878
0
  Assert(NLocalPinnedBuffers > 0);
879
880
0
  if (--LocalRefCount[buffid] == 0)
881
0
  {
882
0
    BufferDesc *buf_hdr = GetLocalBufferDescriptor(buffid);
883
0
    uint64    buf_state;
884
885
0
    NLocalPinnedBuffers--;
886
887
0
    buf_state = pg_atomic_read_u64(&buf_hdr->state);
888
0
    Assert(BUF_STATE_GET_REFCOUNT(buf_state) > 0);
889
0
    buf_state -= BUF_REFCOUNT_ONE;
890
0
    pg_atomic_unlocked_write_u64(&buf_hdr->state, buf_state);
891
892
    /* see comment in UnpinBufferNoOwner */
893
0
    VALGRIND_MAKE_MEM_NOACCESS(LocalBufHdrGetBlock(buf_hdr), BLCKSZ);
894
0
  }
895
0
}
896
897
/*
898
 * GUC check_hook for temp_buffers
899
 */
900
bool
901
check_temp_buffers(int *newval, void **extra, GucSource source)
902
2
{
903
  /*
904
   * Once local buffers have been initialized, it's too late to change this.
905
   * However, if this is only a test call, allow it.
906
   */
907
2
  if (source != PGC_S_TEST && NLocBuffer && NLocBuffer != *newval)
908
0
  {
909
0
    GUC_check_errdetail("\"temp_buffers\" cannot be changed after any temporary tables have been accessed in the session.");
910
0
    return false;
911
0
  }
912
2
  return true;
913
2
}
914
915
/*
916
 * GetLocalBufferStorage - allocate memory for a local buffer
917
 *
918
 * The idea of this function is to aggregate our requests for storage
919
 * so that the memory manager doesn't see a whole lot of relatively small
920
 * requests.  Since we'll never give back a local buffer once it's created
921
 * within a particular process, no point in burdening memmgr with separately
922
 * managed chunks.
923
 */
924
static Block
925
GetLocalBufferStorage(void)
926
0
{
927
0
  static char *cur_block = NULL;
928
0
  static int  next_buf_in_block = 0;
929
0
  static int  num_bufs_in_block = 0;
930
0
  static int  total_bufs_allocated = 0;
931
0
  static MemoryContext LocalBufferContext = NULL;
932
933
0
  char     *this_buf;
934
935
0
  Assert(total_bufs_allocated < NLocBuffer);
936
937
0
  if (next_buf_in_block >= num_bufs_in_block)
938
0
  {
939
    /* Need to make a new request to memmgr */
940
0
    int     num_bufs;
941
942
    /*
943
     * We allocate local buffers in a context of their own, so that the
944
     * space eaten for them is easily recognizable in MemoryContextStats
945
     * output.  Create the context on first use.
946
     */
947
0
    if (LocalBufferContext == NULL)
948
0
      LocalBufferContext =
949
0
        AllocSetContextCreate(TopMemoryContext,
950
0
                    "LocalBufferContext",
951
0
                    ALLOCSET_DEFAULT_SIZES);
952
953
    /* Start with a 16-buffer request; subsequent ones double each time */
954
0
    num_bufs = Max(num_bufs_in_block * 2, 16);
955
    /* But not more than what we need for all remaining local bufs */
956
0
    num_bufs = Min(num_bufs, NLocBuffer - total_bufs_allocated);
957
    /* And don't overflow MaxAllocSize, either */
958
0
    num_bufs = Min(num_bufs, MaxAllocSize / BLCKSZ);
959
960
    /* Buffers should be I/O aligned. */
961
0
    cur_block = MemoryContextAllocAligned(LocalBufferContext,
962
0
                        num_bufs * BLCKSZ,
963
0
                        PG_IO_ALIGN_SIZE,
964
0
                        0);
965
966
0
    next_buf_in_block = 0;
967
0
    num_bufs_in_block = num_bufs;
968
0
  }
969
970
  /* Allocate next buffer in current memory block */
971
0
  this_buf = cur_block + next_buf_in_block * BLCKSZ;
972
0
  next_buf_in_block++;
973
0
  total_bufs_allocated++;
974
975
  /*
976
   * Caller's PinLocalBuffer() was too early for Valgrind updates, so do it
977
   * here.  The block is actually undefined, but we want consistency with
978
   * the regular case of not needing to allocate memory.  This is
979
   * specifically needed when method_io_uring.c fills the block, because
980
   * Valgrind doesn't recognize io_uring reads causing undefined memory to
981
   * become defined.
982
   */
983
0
  VALGRIND_MAKE_MEM_DEFINED(this_buf, BLCKSZ);
984
985
0
  return (Block) this_buf;
986
0
}
987
988
/*
989
 * CheckForLocalBufferLeaks - ensure this backend holds no local buffer pins
990
 *
991
 * This is just like CheckForBufferLeaks(), but for local buffers.
992
 */
993
static void
994
CheckForLocalBufferLeaks(void)
995
0
{
996
#ifdef USE_ASSERT_CHECKING
997
  if (LocalRefCount)
998
  {
999
    int     RefCountErrors = 0;
1000
    int     i;
1001
1002
    for (i = 0; i < NLocBuffer; i++)
1003
    {
1004
      if (LocalRefCount[i] != 0)
1005
      {
1006
        Buffer    b = -i - 1;
1007
        char     *s;
1008
1009
        s = DebugPrintBufferRefcount(b);
1010
        elog(WARNING, "local buffer refcount leak: %s", s);
1011
        pfree(s);
1012
1013
        RefCountErrors++;
1014
      }
1015
    }
1016
    Assert(RefCountErrors == 0);
1017
  }
1018
#endif
1019
0
}
1020
1021
/*
1022
 * AtEOXact_LocalBuffers - clean up at end of transaction.
1023
 *
1024
 * This is just like AtEOXact_Buffers, but for local buffers.
1025
 */
1026
void
1027
AtEOXact_LocalBuffers(bool isCommit)
1028
0
{
1029
0
  CheckForLocalBufferLeaks();
1030
0
}
1031
1032
/*
1033
 * AtProcExit_LocalBuffers - ensure we have dropped pins during backend exit.
1034
 *
1035
 * This is just like AtProcExit_Buffers, but for local buffers.
1036
 */
1037
void
1038
AtProcExit_LocalBuffers(void)
1039
0
{
1040
  /*
1041
   * We shouldn't be holding any remaining pins; if we are, and assertions
1042
   * aren't enabled, we'll fail later in DropRelationBuffers while trying to
1043
   * drop the temp rels.
1044
   */
1045
0
  CheckForLocalBufferLeaks();
1046
0
}