Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * freelist.c
4 : : * routines for managing the buffer pool's replacement strategy.
5 : : *
6 : : *
7 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8 : : * Portions Copyright (c) 1994, Regents of the University of California
9 : : *
10 : : *
11 : : * IDENTIFICATION
12 : : * src/backend/storage/buffer/freelist.c
13 : : *
14 : : *-------------------------------------------------------------------------
15 : : */
16 : : #include "postgres.h"
17 : :
18 : : #include "pgstat.h"
19 : : #include "port/atomics.h"
20 : : #include "storage/buf_internals.h"
21 : : #include "storage/bufmgr.h"
22 : : #include "storage/proc.h"
23 : : #include "storage/shmem.h"
24 : : #include "storage/subsystems.h"
25 : :
26 : : #define INT_ACCESS_ONCE(var) ((int)(*((volatile int *)&(var))))
27 : :
28 : :
29 : : /*
30 : : * The shared freelist control information.
31 : : */
32 : : typedef struct
33 : : {
34 : : /* Spinlock: protects the values below */
35 : : slock_t buffer_strategy_lock;
36 : :
37 : : /*
38 : : * clock-sweep hand: index of next buffer to consider grabbing. Note that
39 : : * this isn't a concrete buffer - we only ever increase the value. So, to
40 : : * get an actual buffer, it needs to be used modulo NBuffers.
41 : : */
42 : : pg_atomic_uint32 nextVictimBuffer;
43 : :
44 : : /*
45 : : * Statistics. These counters should be wide enough that they can't
46 : : * overflow during a single bgwriter cycle.
47 : : */
48 : : uint32 completePasses; /* Complete cycles of the clock-sweep */
49 : : pg_atomic_uint32 numBufferAllocs; /* Buffers allocated since last reset */
50 : :
51 : : /*
52 : : * Bgworker process to be notified upon activity or -1 if none. See
53 : : * StrategyNotifyBgWriter.
54 : : */
55 : : int bgwprocno;
56 : : } BufferStrategyControl;
57 : :
58 : : /* Pointers to shared state */
59 : : static BufferStrategyControl *StrategyControl = NULL;
60 : :
61 : : static void StrategyCtlShmemRequest(void *arg);
62 : : static void StrategyCtlShmemInit(void *arg);
63 : :
64 : : const ShmemCallbacks StrategyCtlShmemCallbacks = {
65 : : .request_fn = StrategyCtlShmemRequest,
66 : : .init_fn = StrategyCtlShmemInit,
67 : : };
68 : :
69 : : /*
70 : : * Private (non-shared) state for managing a ring of shared buffers to re-use.
71 : : * This is currently the only kind of BufferAccessStrategy object, but someday
72 : : * we might have more kinds.
73 : : */
74 : : typedef struct BufferAccessStrategyData
75 : : {
76 : : /* Overall strategy type */
77 : : BufferAccessStrategyType btype;
78 : : /* Number of elements in buffers[] array */
79 : : int nbuffers;
80 : :
81 : : /*
82 : : * Index of the "current" slot in the ring, ie, the one most recently
83 : : * returned by GetBufferFromRing.
84 : : */
85 : : int current;
86 : :
87 : : /*
88 : : * Array of buffer numbers. InvalidBuffer (that is, zero) indicates we
89 : : * have not yet selected a buffer for this ring slot. For allocation
90 : : * simplicity this is palloc'd together with the fixed fields of the
91 : : * struct.
92 : : */
93 : : pg_attribute_counted_by(nbuffers) Buffer buffers[FLEXIBLE_ARRAY_MEMBER];
94 : : } BufferAccessStrategyData;
95 : :
96 : :
97 : : /* Prototypes for internal functions */
98 : : static BufferDesc *GetBufferFromRing(BufferAccessStrategy strategy,
99 : : uint64 *buf_state);
100 : : static void AddBufferToRing(BufferAccessStrategy strategy,
101 : : BufferDesc *buf);
102 : :
103 : : /*
104 : : * ClockSweepTick - Helper routine for StrategyGetBuffer()
105 : : *
106 : : * Move the clock hand one buffer ahead of its current position and return the
107 : : * id of the buffer now under the hand.
108 : : */
109 : : static inline uint32
110 : 5107642 : ClockSweepTick(void)
111 : : {
112 : : uint32 victim;
113 : :
114 : : /*
115 : : * Atomically move hand ahead one buffer - if there's several processes
116 : : * doing this, this can lead to buffers being returned slightly out of
117 : : * apparent order.
118 : : */
119 : : victim =
120 : 5107642 : pg_atomic_fetch_add_u32(&StrategyControl->nextVictimBuffer, 1);
121 : :
122 [ + + ]: 5107642 : if (victim >= NBuffers)
123 : : {
124 : 34080 : uint32 originalVictim = victim;
125 : :
126 : : /* always wrap what we look up in BufferDescriptors */
127 : 34080 : victim = victim % NBuffers;
128 : :
129 : : /*
130 : : * If we're the one that just caused a wraparound, force
131 : : * completePasses to be incremented while holding the spinlock. We
132 : : * need the spinlock so StrategySyncStart() can return a consistent
133 : : * value consisting of nextVictimBuffer and completePasses.
134 : : */
135 [ + + ]: 34080 : if (victim == 0)
136 : : {
137 : : uint32 expected;
138 : : uint32 wrapped;
139 : 34007 : bool success = false;
140 : :
141 : 34007 : expected = originalVictim + 1;
142 : :
143 [ + + ]: 68078 : while (!success)
144 : : {
145 : : /*
146 : : * Acquire the spinlock while increasing completePasses. That
147 : : * allows other readers to read nextVictimBuffer and
148 : : * completePasses in a consistent manner which is required for
149 : : * StrategySyncStart(). In theory delaying the increment
150 : : * could lead to an overflow of nextVictimBuffers, but that's
151 : : * highly unlikely and wouldn't be particularly harmful.
152 : : */
153 : 34071 : SpinLockAcquire(&StrategyControl->buffer_strategy_lock);
154 : :
155 : 34071 : wrapped = expected % NBuffers;
156 : :
157 : 34071 : success = pg_atomic_compare_exchange_u32(&StrategyControl->nextVictimBuffer,
158 : : &expected, wrapped);
159 [ + + ]: 34071 : if (success)
160 : 34007 : StrategyControl->completePasses++;
161 : 34071 : SpinLockRelease(&StrategyControl->buffer_strategy_lock);
162 : : }
163 : : }
164 : : }
165 : 5107642 : return victim;
166 : : }
167 : :
168 : : /*
169 : : * StrategyGetBuffer
170 : : *
171 : : * Called by the bufmgr to get the next candidate buffer to use in
172 : : * GetVictimBuffer(). The only hard requirement GetVictimBuffer() has is that
173 : : * the selected buffer must not currently be pinned by anyone.
174 : : *
175 : : * strategy is a BufferAccessStrategy object, or NULL for default strategy.
176 : : *
177 : : * It is the callers responsibility to ensure the buffer ownership can be
178 : : * tracked via TrackNewBufferPin().
179 : : *
180 : : * The buffer is pinned and marked as owned, using TrackNewBufferPin(),
181 : : * before returning.
182 : : */
183 : : BufferDesc *
184 : 2147159 : StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_ring)
185 : : {
186 : : BufferDesc *buf;
187 : : int bgwprocno;
188 : : int trycounter;
189 : :
190 : 2147159 : *from_ring = false;
191 : :
192 : : /*
193 : : * If given a strategy object, see whether it can select a buffer. We
194 : : * assume strategy objects don't need buffer_strategy_lock.
195 : : */
196 [ + + ]: 2147159 : if (strategy != NULL)
197 : : {
198 : 912243 : buf = GetBufferFromRing(strategy, buf_state);
199 [ + + ]: 912243 : if (buf != NULL)
200 : : {
201 : 346392 : *from_ring = true;
202 : 346392 : return buf;
203 : : }
204 : : }
205 : :
206 : : /*
207 : : * If asked, we need to waken the bgwriter. Since we don't want to rely on
208 : : * a spinlock for this we force a read from shared memory once, and then
209 : : * set the latch based on that value. We need to go through that length
210 : : * because otherwise bgwprocno might be reset while/after we check because
211 : : * the compiler might just reread from memory.
212 : : *
213 : : * This can possibly set the latch of the wrong process if the bgwriter
214 : : * dies in the wrong moment. But since PGPROC->procLatch is never
215 : : * deallocated the worst consequence of that is that we set the latch of
216 : : * some arbitrary process.
217 : : */
218 : 1800767 : bgwprocno = INT_ACCESS_ONCE(StrategyControl->bgwprocno);
219 [ + + ]: 1800767 : if (bgwprocno != -1)
220 : : {
221 : : /* reset bgwprocno first, before setting the latch */
222 : 512 : StrategyControl->bgwprocno = -1;
223 : :
224 : : /*
225 : : * Not acquiring ProcArrayLock here which is slightly icky. It's
226 : : * actually fine because procLatch isn't ever freed, so we just can
227 : : * potentially set the wrong process' (or no process') latch.
228 : : */
229 : 512 : SetLatch(&GetPGProcByNumber(bgwprocno)->procLatch);
230 : : }
231 : :
232 : : /*
233 : : * We count buffer allocation requests so that the bgwriter can estimate
234 : : * the rate of buffer consumption. Note that buffers recycled by a
235 : : * strategy object are intentionally not counted here.
236 : : */
237 : 1800767 : pg_atomic_fetch_add_u32(&StrategyControl->numBufferAllocs, 1);
238 : :
239 : : /* Use the "clock sweep" algorithm to find a free buffer */
240 : 1800767 : trycounter = NBuffers;
241 : : for (;;)
242 : 3306875 : {
243 : : uint64 old_buf_state;
244 : : uint64 local_buf_state;
245 : :
246 : 5107642 : buf = GetBufferDescriptor(ClockSweepTick());
247 : :
248 : : /*
249 : : * Check whether the buffer can be used and pin it if so. Do this
250 : : * using a CAS loop, to avoid having to lock the buffer header.
251 : : */
252 : 5107642 : old_buf_state = pg_atomic_read_u64(&buf->state);
253 : : for (;;)
254 : : {
255 : 5107672 : local_buf_state = old_buf_state;
256 : :
257 : : /*
258 : : * If the buffer is pinned or has a nonzero usage_count, we cannot
259 : : * use it; decrement the usage_count (unless pinned) and keep
260 : : * scanning.
261 : : */
262 : :
263 [ + + ]: 5107672 : if (BUF_STATE_GET_REFCOUNT(local_buf_state) != 0)
264 : : {
265 [ - + ]: 73609 : if (--trycounter == 0)
266 : : {
267 : : /*
268 : : * We've scanned all the buffers without making any state
269 : : * changes, so all the buffers are pinned (or were when we
270 : : * looked at them). We could hope that someone will free
271 : : * one eventually, but it's probably better to fail than
272 : : * to risk getting stuck in an infinite loop.
273 : : */
274 [ # # ]: 0 : ereport(ERROR,
275 : : errcode(ERRCODE_INSUFFICIENT_RESOURCES),
276 : : errmsg("could not find an unpinned shared buffer"));
277 : : }
278 : 73609 : break;
279 : : }
280 : :
281 : : /* See equivalent code in PinBuffer() */
282 [ + + ]: 5034063 : if (unlikely(local_buf_state & BM_LOCKED))
283 : : {
284 : 2 : old_buf_state = WaitBufHdrUnlocked(buf);
285 : 2 : continue;
286 : : }
287 : :
288 [ + + ]: 5034061 : if (BUF_STATE_GET_USAGECOUNT(local_buf_state) != 0)
289 : : {
290 : 3233292 : local_buf_state -= BUF_USAGECOUNT_ONE;
291 : :
292 [ + + ]: 3233292 : if (pg_atomic_compare_exchange_u64(&buf->state, &old_buf_state,
293 : : local_buf_state))
294 : : {
295 : 3233266 : trycounter = NBuffers;
296 : 3233266 : break;
297 : : }
298 : : }
299 : : else
300 : : {
301 : : /* pin the buffer if the CAS succeeds */
302 : 1800769 : local_buf_state += BUF_REFCOUNT_ONE;
303 : :
304 [ + + ]: 1800769 : if (pg_atomic_compare_exchange_u64(&buf->state, &old_buf_state,
305 : : local_buf_state))
306 : : {
307 : : /* Found a usable buffer */
308 [ + + ]: 1800767 : if (strategy != NULL)
309 : 565851 : AddBufferToRing(strategy, buf);
310 : 1800767 : *buf_state = local_buf_state;
311 : :
312 : 1800767 : TrackNewBufferPin(BufferDescriptorGetBuffer(buf));
313 : :
314 : 1800767 : return buf;
315 : : }
316 : : }
317 : : }
318 : : }
319 : : }
320 : :
321 : : /*
322 : : * StrategySyncStart -- tell BgBufferSync where to start syncing
323 : : *
324 : : * The result is the buffer index of the best buffer to sync first.
325 : : * BgBufferSync() will proceed circularly around the buffer array from there.
326 : : *
327 : : * In addition, we return the completed-pass count (which is effectively
328 : : * the higher-order bits of nextVictimBuffer) and the count of recent buffer
329 : : * allocs if non-NULL pointers are passed. The alloc count is reset after
330 : : * being read.
331 : : */
332 : : int
333 : 15468 : StrategySyncStart(uint32 *complete_passes, uint32 *num_buf_alloc)
334 : : {
335 : : uint32 nextVictimBuffer;
336 : : int result;
337 : :
338 : 15468 : SpinLockAcquire(&StrategyControl->buffer_strategy_lock);
339 : 15468 : nextVictimBuffer = pg_atomic_read_u32(&StrategyControl->nextVictimBuffer);
340 : 15468 : result = nextVictimBuffer % NBuffers;
341 : :
342 [ + - ]: 15468 : if (complete_passes)
343 : : {
344 : 15468 : *complete_passes = StrategyControl->completePasses;
345 : :
346 : : /*
347 : : * Additionally add the number of wraparounds that happened before
348 : : * completePasses could be incremented. C.f. ClockSweepTick().
349 : : */
350 : 15468 : *complete_passes += nextVictimBuffer / NBuffers;
351 : : }
352 : :
353 [ + - ]: 15468 : if (num_buf_alloc)
354 : : {
355 : 15468 : *num_buf_alloc = pg_atomic_exchange_u32(&StrategyControl->numBufferAllocs, 0);
356 : : }
357 : 15468 : SpinLockRelease(&StrategyControl->buffer_strategy_lock);
358 : 15468 : return result;
359 : : }
360 : :
361 : : /*
362 : : * StrategyNotifyBgWriter -- set or clear allocation notification latch
363 : : *
364 : : * If bgwprocno isn't -1, the next invocation of StrategyGetBuffer will
365 : : * set that latch. Pass -1 to clear the pending notification before it
366 : : * happens. This feature is used by the bgwriter process to wake itself up
367 : : * from hibernation, and is not meant for anybody else to use.
368 : : */
369 : : void
370 : 920 : StrategyNotifyBgWriter(int bgwprocno)
371 : : {
372 : : /*
373 : : * We acquire buffer_strategy_lock just to ensure that the store appears
374 : : * atomic to StrategyGetBuffer. The bgwriter should call this rather
375 : : * infrequently, so there's no performance penalty from being safe.
376 : : */
377 : 920 : SpinLockAcquire(&StrategyControl->buffer_strategy_lock);
378 : 920 : StrategyControl->bgwprocno = bgwprocno;
379 : 920 : SpinLockRelease(&StrategyControl->buffer_strategy_lock);
380 : 920 : }
381 : :
382 : :
383 : : /*
384 : : * StrategyCtlShmemRequest -- request shared memory for the buffer
385 : : * cache replacement strategy.
386 : : */
387 : : static void
388 : 1332 : StrategyCtlShmemRequest(void *arg)
389 : : {
390 : 1332 : ShmemRequestStruct(.name = "Buffer Strategy Status",
391 : : .size = sizeof(BufferStrategyControl),
392 : : .ptr = (void **) &StrategyControl
393 : : );
394 : 1332 : }
395 : :
396 : : /*
397 : : * StrategyCtlShmemInit -- initialize the buffer cache replacement strategy.
398 : : */
399 : : static void
400 : 1328 : StrategyCtlShmemInit(void *arg)
401 : : {
402 : 1328 : SpinLockInit(&StrategyControl->buffer_strategy_lock);
403 : :
404 : : /* Initialize the clock-sweep pointer */
405 : 1328 : pg_atomic_init_u32(&StrategyControl->nextVictimBuffer, 0);
406 : :
407 : : /* Clear statistics */
408 : 1328 : StrategyControl->completePasses = 0;
409 : 1328 : pg_atomic_init_u32(&StrategyControl->numBufferAllocs, 0);
410 : :
411 : : /* No pending notification */
412 : 1328 : StrategyControl->bgwprocno = -1;
413 : 1328 : }
414 : :
415 : :
416 : : /* ----------------------------------------------------------------
417 : : * Backend-private buffer ring management
418 : : * ----------------------------------------------------------------
419 : : */
420 : :
421 : :
422 : : /*
423 : : * GetAccessStrategy -- create a BufferAccessStrategy object
424 : : *
425 : : * The object is allocated in the current memory context.
426 : : */
427 : : BufferAccessStrategy
428 : 159491 : GetAccessStrategy(BufferAccessStrategyType btype)
429 : : {
430 : : int ring_size_kb;
431 : :
432 : : /*
433 : : * Select ring size to use. See buffer/README for rationales.
434 : : *
435 : : * Note: if you change the ring size for BAS_BULKREAD, see also
436 : : * SYNC_SCAN_REPORT_INTERVAL in access/heap/syncscan.c.
437 : : */
438 [ - + + + : 159491 : switch (btype)
- ]
439 : : {
440 : 0 : case BAS_NORMAL:
441 : : /* if someone asks for NORMAL, just give 'em a "default" object */
442 : 0 : return NULL;
443 : :
444 : 87860 : case BAS_BULKREAD:
445 : : {
446 : : int ring_max_kb;
447 : :
448 : : /*
449 : : * The ring always needs to be large enough to allow some
450 : : * separation in time between providing a buffer to the user
451 : : * of the strategy and that buffer being reused. Otherwise the
452 : : * user's pin will prevent reuse of the buffer, even without
453 : : * concurrent activity.
454 : : *
455 : : * We also need to ensure the ring always is large enough for
456 : : * SYNC_SCAN_REPORT_INTERVAL, as noted above.
457 : : *
458 : : * Thus we start out a minimal size and increase the size
459 : : * further if appropriate.
460 : : */
461 : 87860 : ring_size_kb = 256;
462 : :
463 : : /*
464 : : * There's no point in a larger ring if we won't be allowed to
465 : : * pin sufficiently many buffers. But we never limit to less
466 : : * than the minimal size above.
467 : : */
468 : 87860 : ring_max_kb = GetPinLimit() * (BLCKSZ / 1024);
469 : 87860 : ring_max_kb = Max(ring_size_kb, ring_max_kb);
470 : :
471 : : /*
472 : : * We would like the ring to additionally have space for the
473 : : * configured degree of IO concurrency. While being read in,
474 : : * buffers can obviously not yet be reused.
475 : : *
476 : : * Each IO can be up to io_combine_limit blocks large, and we
477 : : * want to start up to effective_io_concurrency IOs.
478 : : *
479 : : * Note that effective_io_concurrency may be 0, which disables
480 : : * AIO.
481 : : */
482 : 87860 : ring_size_kb += (BLCKSZ / 1024) *
483 : 87860 : io_combine_limit * effective_io_concurrency;
484 : :
485 [ + - ]: 87860 : if (ring_size_kb > ring_max_kb)
486 : 87860 : ring_size_kb = ring_max_kb;
487 : 87860 : break;
488 : : }
489 : 71590 : case BAS_BULKWRITE:
490 : 71590 : ring_size_kb = 16 * 1024;
491 : 71590 : break;
492 : 41 : case BAS_VACUUM:
493 : 41 : ring_size_kb = 2048;
494 : 41 : break;
495 : :
496 : 0 : default:
497 [ # # ]: 0 : elog(ERROR, "unrecognized buffer access strategy: %d",
498 : : (int) btype);
499 : : return NULL; /* keep compiler quiet */
500 : : }
501 : :
502 : 159491 : return GetAccessStrategyWithSize(btype, ring_size_kb);
503 : : }
504 : :
505 : : /*
506 : : * GetAccessStrategyWithSize -- create a BufferAccessStrategy object with a
507 : : * number of buffers equivalent to the passed in size.
508 : : *
509 : : * If the given ring size is 0, no BufferAccessStrategy will be created and
510 : : * the function will return NULL. ring_size_kb must not be negative.
511 : : */
512 : : BufferAccessStrategy
513 : 169463 : GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size_kb)
514 : : {
515 : : int ring_buffers;
516 : : BufferAccessStrategy strategy;
517 : :
518 : : Assert(ring_size_kb >= 0);
519 : :
520 : : /* Figure out how many buffers ring_size_kb is */
521 : 169463 : ring_buffers = ring_size_kb / (BLCKSZ / 1024);
522 : :
523 : : /* 0 means unlimited, so no BufferAccessStrategy required */
524 [ + + ]: 169463 : if (ring_buffers == 0)
525 : 8 : return NULL;
526 : :
527 : : /* Cap to 1/8th of shared_buffers */
528 : 169455 : ring_buffers = Min(NBuffers / 8, ring_buffers);
529 : :
530 : : /* NBuffers should never be less than 16, so this shouldn't happen */
531 : : Assert(ring_buffers > 0);
532 : :
533 : : /* Allocate the object and initialize all elements to zeroes */
534 : : strategy = (BufferAccessStrategy)
535 : 169455 : palloc0(offsetof(BufferAccessStrategyData, buffers) +
536 : : ring_buffers * sizeof(Buffer));
537 : :
538 : : /* Set fields that don't start out zero */
539 : 169455 : strategy->btype = btype;
540 : 169455 : strategy->nbuffers = ring_buffers;
541 : :
542 : 169455 : return strategy;
543 : : }
544 : :
545 : : /*
546 : : * GetAccessStrategyBufferCount -- an accessor for the number of buffers in
547 : : * the ring
548 : : *
549 : : * Returns 0 on NULL input to match behavior of GetAccessStrategyWithSize()
550 : : * returning NULL with 0 size.
551 : : */
552 : : int
553 : 27 : GetAccessStrategyBufferCount(BufferAccessStrategy strategy)
554 : : {
555 [ - + ]: 27 : if (strategy == NULL)
556 : 0 : return 0;
557 : :
558 : 27 : return strategy->nbuffers;
559 : : }
560 : :
561 : : /*
562 : : * GetAccessStrategyPinLimit -- get cap of number of buffers that should be pinned
563 : : *
564 : : * When pinning extra buffers to look ahead, users of a ring-based strategy are
565 : : * in danger of pinning too much of the ring at once while performing look-ahead.
566 : : * For some strategies, that means "escaping" from the ring, and in others it
567 : : * means forcing dirty data to disk very frequently with associated WAL
568 : : * flushing. Since external code has no insight into any of that, allow
569 : : * individual strategy types to expose a clamp that should be applied when
570 : : * deciding on a maximum number of buffers to pin at once.
571 : : *
572 : : * Callers should combine this number with other relevant limits and take the
573 : : * minimum.
574 : : */
575 : : int
576 : 678341 : GetAccessStrategyPinLimit(BufferAccessStrategy strategy)
577 : : {
578 [ + + ]: 678341 : if (strategy == NULL)
579 : 485276 : return NBuffers;
580 : :
581 [ + + ]: 193065 : switch (strategy->btype)
582 : : {
583 : 83162 : case BAS_BULKREAD:
584 : :
585 : : /*
586 : : * Since BAS_BULKREAD uses StrategyRejectBuffer(), dirty buffers
587 : : * shouldn't be a problem and the caller is free to pin up to the
588 : : * entire ring at once.
589 : : */
590 : 83162 : return strategy->nbuffers;
591 : :
592 : 109903 : default:
593 : :
594 : : /*
595 : : * Tell caller not to pin more than half the buffers in the ring.
596 : : * This is a trade-off between look ahead distance and deferring
597 : : * writeback and associated WAL traffic.
598 : : */
599 : 109903 : return strategy->nbuffers / 2;
600 : : }
601 : : }
602 : :
603 : : /*
604 : : * FreeAccessStrategy -- release a BufferAccessStrategy object
605 : : *
606 : : * A simple pfree would do at the moment, but we would prefer that callers
607 : : * don't assume that much about the representation of BufferAccessStrategy.
608 : : */
609 : : void
610 : 152974 : FreeAccessStrategy(BufferAccessStrategy strategy)
611 : : {
612 : : /* don't crash if called on a "default" strategy */
613 [ + - ]: 152974 : if (strategy != NULL)
614 : 152974 : pfree(strategy);
615 : 152974 : }
616 : :
617 : : /*
618 : : * GetBufferFromRing -- returns a buffer from the ring, or NULL if the
619 : : * ring is empty / not usable.
620 : : *
621 : : * The buffer is pinned and marked as owned, using TrackNewBufferPin(), before
622 : : * returning.
623 : : */
624 : : static BufferDesc *
625 : 912243 : GetBufferFromRing(BufferAccessStrategy strategy, uint64 *buf_state)
626 : : {
627 : : BufferDesc *buf;
628 : : Buffer bufnum;
629 : : uint64 old_buf_state;
630 : : uint64 local_buf_state; /* to avoid repeated (de-)referencing */
631 : :
632 : :
633 : : /* Advance to next ring slot */
634 [ + + ]: 912243 : if (++strategy->current >= strategy->nbuffers)
635 : 26251 : strategy->current = 0;
636 : :
637 : : /*
638 : : * If the slot hasn't been filled yet, tell the caller to allocate a new
639 : : * buffer with the normal allocation strategy. He will then fill this
640 : : * slot by calling AddBufferToRing with the new buffer.
641 : : */
642 : 912243 : bufnum = strategy->buffers[strategy->current];
643 [ + + ]: 912243 : if (bufnum == InvalidBuffer)
644 : 556276 : return NULL;
645 : :
646 : 355967 : buf = GetBufferDescriptor(bufnum - 1);
647 : :
648 : : /*
649 : : * Check whether the buffer can be used and pin it if so. Do this using a
650 : : * CAS loop, to avoid having to lock the buffer header.
651 : : */
652 : 355967 : old_buf_state = pg_atomic_read_u64(&buf->state);
653 : : for (;;)
654 : : {
655 : 355967 : local_buf_state = old_buf_state;
656 : :
657 : : /*
658 : : * If the buffer is pinned we cannot use it under any circumstances.
659 : : *
660 : : * If usage_count is 0 or 1 then the buffer is fair game (we expect 1,
661 : : * since our own previous usage of the ring element would have left it
662 : : * there, but it might've been decremented by clock-sweep since then).
663 : : * A higher usage_count indicates someone else has touched the buffer,
664 : : * so we shouldn't re-use it.
665 : : */
666 [ + + ]: 355967 : if (BUF_STATE_GET_REFCOUNT(local_buf_state) != 0
667 [ + + ]: 350340 : || BUF_STATE_GET_USAGECOUNT(local_buf_state) > 1)
668 : : break;
669 : :
670 : : /* See equivalent code in PinBuffer() */
671 [ - + ]: 346392 : if (unlikely(local_buf_state & BM_LOCKED))
672 : : {
673 : 0 : old_buf_state = WaitBufHdrUnlocked(buf);
674 : 0 : continue;
675 : : }
676 : :
677 : : /* pin the buffer if the CAS succeeds */
678 : 346392 : local_buf_state += BUF_REFCOUNT_ONE;
679 : :
680 [ + - ]: 346392 : if (pg_atomic_compare_exchange_u64(&buf->state, &old_buf_state,
681 : : local_buf_state))
682 : : {
683 : 346392 : *buf_state = local_buf_state;
684 : :
685 : 346392 : TrackNewBufferPin(BufferDescriptorGetBuffer(buf));
686 : 346392 : return buf;
687 : : }
688 : : }
689 : :
690 : : /*
691 : : * Tell caller to allocate a new buffer with the normal allocation
692 : : * strategy. He'll then replace this ring element via AddBufferToRing.
693 : : */
694 : 9575 : return NULL;
695 : : }
696 : :
697 : : /*
698 : : * AddBufferToRing -- add a buffer to the buffer ring
699 : : *
700 : : * Caller must hold the buffer header spinlock on the buffer. Since this
701 : : * is called with the spinlock held, it had better be quite cheap.
702 : : */
703 : : static void
704 : 565851 : AddBufferToRing(BufferAccessStrategy strategy, BufferDesc *buf)
705 : : {
706 : 565851 : strategy->buffers[strategy->current] = BufferDescriptorGetBuffer(buf);
707 : 565851 : }
708 : :
709 : : /*
710 : : * Utility function returning the IOContext of a given BufferAccessStrategy's
711 : : * strategy ring.
712 : : */
713 : : IOContext
714 : 89780213 : IOContextForStrategy(BufferAccessStrategy strategy)
715 : : {
716 [ + + ]: 89780213 : if (!strategy)
717 : 87189576 : return IOCONTEXT_NORMAL;
718 : :
719 [ + + + - ]: 2590637 : switch (strategy->btype)
720 : : {
721 : : case BAS_NORMAL:
722 : :
723 : : /*
724 : : * Currently, GetAccessStrategy() returns NULL for
725 : : * BufferAccessStrategyType BAS_NORMAL, so this case is
726 : : * unreachable.
727 : : */
728 : : pg_unreachable();
729 : : return IOCONTEXT_NORMAL;
730 : 1529957 : case BAS_BULKREAD:
731 : 1529957 : return IOCONTEXT_BULKREAD;
732 : 327473 : case BAS_BULKWRITE:
733 : 327473 : return IOCONTEXT_BULKWRITE;
734 : 733207 : case BAS_VACUUM:
735 : 733207 : return IOCONTEXT_VACUUM;
736 : : }
737 : :
738 [ # # ]: 0 : elog(ERROR, "unrecognized BufferAccessStrategyType: %d", strategy->btype);
739 : : pg_unreachable();
740 : : }
741 : :
742 : : /*
743 : : * StrategyRejectBuffer -- consider rejecting a dirty buffer
744 : : *
745 : : * When a nondefault strategy is used, the buffer manager calls this function
746 : : * when it turns out that the buffer selected by StrategyGetBuffer needs to
747 : : * be written out and doing so would require flushing WAL too. This gives us
748 : : * a chance to choose a different victim.
749 : : *
750 : : * Returns true if buffer manager should ask for a new victim, and false
751 : : * if this buffer should be written and re-used.
752 : : */
753 : : bool
754 : 26984 : StrategyRejectBuffer(BufferAccessStrategy strategy, BufferDesc *buf, bool from_ring)
755 : : {
756 : : /* We only do this in bulkread mode */
757 [ + + ]: 26984 : if (strategy->btype != BAS_BULKREAD)
758 : 4287 : return false;
759 : :
760 : : /* Don't muck with behavior of normal buffer-replacement strategy */
761 [ + - - + ]: 45394 : if (!from_ring ||
762 : 22697 : strategy->buffers[strategy->current] != BufferDescriptorGetBuffer(buf))
763 : 0 : return false;
764 : :
765 : : /*
766 : : * Remove the dirty buffer from the ring; necessary to prevent infinite
767 : : * loop if all ring members are dirty.
768 : : */
769 : 22697 : strategy->buffers[strategy->current] = InvalidBuffer;
770 : :
771 : 22697 : return true;
772 : : }
|