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