Coverage Report

Created: 2026-11-09 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/storage/ipc/sinvaladt.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * sinvaladt.c
4
 *    POSTGRES shared cache invalidation data manager.
5
 *
6
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7
 * Portions Copyright (c) 1994, Regents of the University of California
8
 *
9
 *
10
 * IDENTIFICATION
11
 *    src/backend/storage/ipc/sinvaladt.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#include "postgres.h"
16
17
#include <signal.h>
18
#include <unistd.h>
19
20
#include "miscadmin.h"
21
#include "storage/ipc.h"
22
#include "storage/proc.h"
23
#include "storage/procnumber.h"
24
#include "storage/procsignal.h"
25
#include "storage/shmem.h"
26
#include "storage/sinvaladt.h"
27
#include "storage/subsystems.h"
28
29
/*
30
 * Conceptually, the shared cache invalidation messages are stored in an
31
 * infinite array, where maxMsgNum is the next array subscript to store a
32
 * submitted message in, minMsgNum is the smallest array subscript containing
33
 * a message not yet read by all backends, and we always have maxMsgNum >=
34
 * minMsgNum.  (They are equal when there are no messages pending.)  For each
35
 * active backend, there is a nextMsgNum pointer indicating the next message it
36
 * needs to read; we have maxMsgNum >= nextMsgNum >= minMsgNum for every
37
 * backend.
38
 *
39
 * (In the current implementation, minMsgNum is a lower bound for the
40
 * per-process nextMsgNum values, but it isn't rigorously kept equal to the
41
 * smallest nextMsgNum --- it may lag behind.  We only update it when
42
 * SICleanupQueue is called, and we try not to do that often.)
43
 *
44
 * In reality, the messages are stored in a circular buffer of MAXNUMMESSAGES
45
 * entries.  We translate MsgNum values into circular-buffer indexes by
46
 * computing MsgNum % MAXNUMMESSAGES (this should be fast as long as
47
 * MAXNUMMESSAGES is a constant and a power of 2).  As long as maxMsgNum
48
 * doesn't exceed minMsgNum by more than MAXNUMMESSAGES, we have enough space
49
 * in the buffer.  If the buffer does overflow, we recover by setting the
50
 * "reset" flag for each backend that has fallen too far behind.  A backend
51
 * that is in "reset" state is ignored while determining minMsgNum.  When
52
 * it does finally attempt to receive inval messages, it must discard all
53
 * its invalidatable state, since it won't know what it missed.
54
 *
55
 * To reduce the probability of needing resets, we send a "catchup" interrupt
56
 * to any backend that seems to be falling unreasonably far behind.  The
57
 * normal behavior is that at most one such interrupt is in flight at a time;
58
 * when a backend completes processing a catchup interrupt, it executes
59
 * SICleanupQueue, which will signal the next-furthest-behind backend if
60
 * needed.  This avoids undue contention from multiple backends all trying
61
 * to catch up at once.  However, the furthest-back backend might be stuck
62
 * in a state where it can't catch up.  Eventually it will get reset, so it
63
 * won't cause any more problems for anyone but itself.  But we don't want
64
 * to find that a bunch of other backends are now too close to the reset
65
 * threshold to be saved.  So SICleanupQueue is designed to occasionally
66
 * send extra catchup interrupts as the queue gets fuller, to backends that
67
 * are far behind and haven't gotten one yet.  As long as there aren't a lot
68
 * of "stuck" backends, we won't need a lot of extra interrupts, since ones
69
 * that aren't stuck will propagate their interrupts to the next guy.
70
 *
71
 * We would have problems if the MsgNum values overflow an integer, so
72
 * whenever minMsgNum exceeds MSGNUMWRAPAROUND, we subtract MSGNUMWRAPAROUND
73
 * from all the MsgNum variables simultaneously.  MSGNUMWRAPAROUND can be
74
 * large so that we don't need to do this often.  It must be a multiple of
75
 * MAXNUMMESSAGES so that the existing circular-buffer entries don't need
76
 * to be moved when we do it.
77
 *
78
 * Access to the shared sinval array is protected by two locks, SInvalReadLock
79
 * and SInvalWriteLock.  Readers take SInvalReadLock in shared mode; this
80
 * authorizes them to modify their own ProcState but not to modify or even
81
 * look at anyone else's.  When we need to perform array-wide updates,
82
 * such as in SICleanupQueue, we take SInvalReadLock in exclusive mode to
83
 * lock out all readers.  Writers take SInvalWriteLock (always in exclusive
84
 * mode) to serialize adding messages to the queue.  Note that a writer
85
 * can operate in parallel with one or more readers, because the writer
86
 * has no need to touch anyone's ProcState, except in the infrequent cases
87
 * when SICleanupQueue is needed.  The only point of overlap is that
88
 * the writer wants to change maxMsgNum while readers need to read it.
89
 * We deal with that by making maxMsgNum an atomic variable.  (The exact rule
90
 * is that you need to use a barrier-providing accessor to read maxMsgNum if
91
 * you are not holding SInvalWriteLock, and you need a barrier-providing
92
 * accessor to write maxMsgNum unless you are holding both locks.)  The
93
 * barriers ensure that messages written to the array are actually there
94
 * before maxMsgNum is increased, and that readers will see that data after
95
 * fetching maxMsgNum.
96
 */
97
98
99
/*
100
 * Configurable parameters.
101
 *
102
 * MAXNUMMESSAGES: max number of shared-inval messages we can buffer.
103
 * Must be a power of 2 for speed.
104
 *
105
 * MSGNUMWRAPAROUND: how often to reduce MsgNum variables to avoid overflow.
106
 * Must be a multiple of MAXNUMMESSAGES.  Should be large.
107
 *
108
 * CLEANUP_MIN: the minimum number of messages that must be in the buffer
109
 * before we bother to call SICleanupQueue.
110
 *
111
 * CLEANUP_QUANTUM: how often (in messages) to call SICleanupQueue once
112
 * we exceed CLEANUP_MIN.  Should be a power of 2 for speed.
113
 *
114
 * SIG_THRESHOLD: the minimum number of messages a backend must have fallen
115
 * behind before we'll send it PROCSIG_CATCHUP_INTERRUPT.
116
 *
117
 * WRITE_QUANTUM: the max number of messages to push into the buffer per
118
 * iteration of SIInsertDataEntries.  Noncritical but should be less than
119
 * CLEANUP_QUANTUM, because we only consider calling SICleanupQueue once
120
 * per iteration.
121
 */
122
123
0
#define MAXNUMMESSAGES 4096
124
0
#define MSGNUMWRAPAROUND (MAXNUMMESSAGES * 262144)
125
0
#define CLEANUP_MIN (MAXNUMMESSAGES / 2)
126
0
#define CLEANUP_QUANTUM (MAXNUMMESSAGES / 16)
127
0
#define SIG_THRESHOLD (MAXNUMMESSAGES / 2)
128
#define WRITE_QUANTUM 64
129
130
/* Per-backend state in shared invalidation structure */
131
typedef struct ProcState
132
{
133
  /* procPid is zero in an inactive ProcState array entry. */
134
  pid_t   procPid;    /* PID of backend, for signaling */
135
  /* nextMsgNum is meaningless if procPid == 0 or resetState is true. */
136
  uint32    nextMsgNum;   /* next message number to read */
137
  bool    resetState;   /* backend needs to reset its state */
138
  bool    signaled;   /* backend has been sent catchup signal */
139
  bool    hasMessages;  /* backend has unread messages */
140
141
  /*
142
   * Backend only sends invalidations, never receives them. This only makes
143
   * sense for Startup process during recovery because it doesn't maintain a
144
   * relcache, yet it fires inval messages to allow query backends to see
145
   * schema changes.
146
   */
147
  bool    sendOnly;   /* backend only sends, never receives */
148
149
  /*
150
   * Next LocalTransactionId to use for each idle backend slot.  We keep
151
   * this here because it is indexed by ProcNumber and it is convenient to
152
   * copy the value to and from local memory when MyProcNumber is set. It's
153
   * meaningless in an active ProcState entry.
154
   */
155
  LocalTransactionId nextLXID;
156
} ProcState;
157
158
/* Shared cache invalidation memory segment */
159
typedef struct SISeg
160
{
161
  /*
162
   * General state information
163
   */
164
  uint32    minMsgNum;    /* oldest message still needed */
165
  pg_atomic_uint32 maxMsgNum; /* next message number to be assigned */
166
  uint32    nextThreshold;  /* # of messages to call SICleanupQueue */
167
168
  /*
169
   * Circular buffer holding shared-inval messages
170
   */
171
  SharedInvalidationMessage buffer[MAXNUMMESSAGES];
172
173
  /*
174
   * Per-backend invalidation state info.
175
   *
176
   * 'procState' has NumProcStateSlots entries, and is indexed by pgprocno.
177
   * 'numProcs' is the number of slots currently in use, and 'pgprocnos' is
178
   * a dense array of their indexes, to speed up scanning all in-use slots.
179
   *
180
   * 'pgprocnos' is largely redundant with ProcArrayStruct->pgprocnos, but
181
   * having our separate copy avoids contention on ProcArrayLock, and allows
182
   * us to track only the processes that participate in shared cache
183
   * invalidations.
184
   */
185
  int     numProcs;
186
  int      *pgprocnos;
187
  ProcState procState[FLEXIBLE_ARRAY_MEMBER];
188
} SISeg;
189
190
/*
191
 * We reserve a slot for each possible ProcNumber, plus one for each
192
 * possible auxiliary process type.  (This scheme assumes there is not
193
 * more than one of any auxiliary process type at a time, except for
194
 * IO workers.)
195
 */
196
0
#define NumProcStateSlots (MaxBackends + NUM_AUXILIARY_PROCS)
197
198
static SISeg *shmInvalBuffer; /* pointer to the shared inval buffer */
199
200
static void SharedInvalShmemRequest(void *arg);
201
static void SharedInvalShmemInit(void *arg);
202
203
const ShmemCallbacks SharedInvalShmemCallbacks = {
204
  .request_fn = SharedInvalShmemRequest,
205
  .init_fn = SharedInvalShmemInit,
206
};
207
208
209
static LocalTransactionId nextLocalTransactionId;
210
211
static void CleanupInvalidationState(int status, Datum arg);
212
213
214
/*
215
 * SharedInvalShmemRequest
216
 *    Register shared memory needs for the SI message buffer
217
 */
218
static void
219
SharedInvalShmemRequest(void *arg)
220
0
{
221
0
  Size    size;
222
223
0
  size = offsetof(SISeg, procState);
224
0
  size = add_size(size, mul_size(sizeof(ProcState), NumProcStateSlots)); /* procState */
225
0
  size = add_size(size, mul_size(sizeof(int), NumProcStateSlots)); /* pgprocnos */
226
227
0
  ShmemRequestStruct(.name = "shmInvalBuffer",
228
0
             .size = size,
229
0
             .ptr = (void **) &shmInvalBuffer,
230
0
    );
231
0
}
232
233
static void
234
SharedInvalShmemInit(void *arg)
235
0
{
236
0
  int     i;
237
238
  /* Clear message counters */
239
0
  shmInvalBuffer->minMsgNum = 0;
240
0
  pg_atomic_init_u32(&shmInvalBuffer->maxMsgNum, 0);
241
0
  shmInvalBuffer->nextThreshold = CLEANUP_MIN;
242
243
  /* The buffer[] array is initially all unused, so we need not fill it */
244
245
  /* Mark all backends inactive, and initialize nextLXID */
246
0
  for (i = 0; i < NumProcStateSlots; i++)
247
0
  {
248
0
    shmInvalBuffer->procState[i].procPid = 0; /* inactive */
249
0
    shmInvalBuffer->procState[i].nextMsgNum = 0;  /* meaningless */
250
0
    shmInvalBuffer->procState[i].resetState = false;
251
0
    shmInvalBuffer->procState[i].signaled = false;
252
0
    shmInvalBuffer->procState[i].hasMessages = false;
253
0
    shmInvalBuffer->procState[i].nextLXID = InvalidLocalTransactionId;
254
0
  }
255
0
  shmInvalBuffer->numProcs = 0;
256
0
  shmInvalBuffer->pgprocnos = (int *) &shmInvalBuffer->procState[i];
257
0
}
258
259
/*
260
 * SharedInvalBackendInit
261
 *    Initialize a new backend to operate on the sinval buffer
262
 */
263
void
264
SharedInvalBackendInit(bool sendOnly)
265
0
{
266
0
  ProcState  *stateP;
267
0
  pid_t   oldPid;
268
0
  SISeg    *segP = shmInvalBuffer;
269
270
0
  if (MyProcNumber < 0)
271
0
    elog(ERROR, "MyProcNumber not set");
272
0
  if (MyProcNumber >= NumProcStateSlots)
273
0
    elog(PANIC, "unexpected MyProcNumber %d in SharedInvalBackendInit (max %d)",
274
0
       MyProcNumber, NumProcStateSlots);
275
0
  stateP = &segP->procState[MyProcNumber];
276
277
  /*
278
   * This can run in parallel with read operations, but not with write
279
   * operations, since SIInsertDataEntries relies on the pgprocnos array to
280
   * set hasMessages appropriately.
281
   */
282
0
  LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE);
283
284
0
  oldPid = stateP->procPid;
285
0
  if (oldPid != 0)
286
0
  {
287
0
    LWLockRelease(SInvalWriteLock);
288
0
    elog(ERROR, "sinval slot for backend %d is already in use by process %d",
289
0
       MyProcNumber, (int) oldPid);
290
0
  }
291
292
0
  shmInvalBuffer->pgprocnos[shmInvalBuffer->numProcs++] = MyProcNumber;
293
294
  /* Fetch next local transaction ID into local memory */
295
0
  nextLocalTransactionId = stateP->nextLXID;
296
297
  /* mark myself active, with all extant messages already read */
298
0
  stateP->procPid = MyProcPid;
299
0
  stateP->nextMsgNum = pg_atomic_read_u32(&segP->maxMsgNum);
300
0
  stateP->resetState = false;
301
0
  stateP->signaled = false;
302
0
  stateP->hasMessages = false;
303
0
  stateP->sendOnly = sendOnly;
304
305
0
  LWLockRelease(SInvalWriteLock);
306
307
  /* register exit routine to mark my entry inactive at exit */
308
0
  on_shmem_exit(CleanupInvalidationState, PointerGetDatum(segP));
309
0
}
310
311
/*
312
 * CleanupInvalidationState
313
 *    Mark the current backend as no longer active.
314
 *
315
 * This function is called via on_shmem_exit() during backend shutdown.
316
 *
317
 * arg is really of type "SISeg*".
318
 */
319
static void
320
CleanupInvalidationState(int status, Datum arg)
321
0
{
322
0
  SISeg    *segP = (SISeg *) DatumGetPointer(arg);
323
0
  ProcState  *stateP;
324
0
  int     i;
325
326
0
  Assert(segP);
327
328
0
  LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE);
329
330
0
  stateP = &segP->procState[MyProcNumber];
331
332
  /* Update next local transaction ID for next holder of this proc number */
333
0
  stateP->nextLXID = nextLocalTransactionId;
334
335
  /* Mark myself inactive */
336
0
  stateP->procPid = 0;
337
0
  stateP->nextMsgNum = 0;
338
0
  stateP->resetState = false;
339
0
  stateP->signaled = false;
340
341
0
  for (i = segP->numProcs - 1; i >= 0; i--)
342
0
  {
343
0
    if (segP->pgprocnos[i] == MyProcNumber)
344
0
    {
345
0
      if (i != segP->numProcs - 1)
346
0
        segP->pgprocnos[i] = segP->pgprocnos[segP->numProcs - 1];
347
0
      break;
348
0
    }
349
0
  }
350
0
  if (i < 0)
351
0
    elog(PANIC, "could not find entry in sinval array");
352
0
  segP->numProcs--;
353
354
0
  LWLockRelease(SInvalWriteLock);
355
0
}
356
357
/*
358
 * SIInsertDataEntries
359
 *    Add new invalidation message(s) to the buffer.
360
 */
361
void
362
SIInsertDataEntries(const SharedInvalidationMessage *data, int n)
363
0
{
364
0
  SISeg    *segP = shmInvalBuffer;
365
366
  /*
367
   * N can be arbitrarily large.  We divide the work into groups of no more
368
   * than WRITE_QUANTUM messages, to be sure that we don't hold the lock for
369
   * an unreasonably long time.  (This is not so much because we care about
370
   * letting in other writers, as that some just-caught-up backend might be
371
   * trying to do SICleanupQueue to pass on its signal, and we don't want it
372
   * to have to wait a long time.)  Also, we need to consider calling
373
   * SICleanupQueue every so often.
374
   */
375
0
  while (n > 0)
376
0
  {
377
0
    int     nthistime = Min(n, WRITE_QUANTUM);
378
0
    uint32    numMsgs;
379
0
    uint32    max;
380
0
    int     i;
381
382
0
    n -= nthistime;
383
384
0
    LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE);
385
386
    /*
387
     * If the buffer is full, we *must* acquire some space.  Clean the
388
     * queue and reset anyone who is preventing space from being freed.
389
     * Otherwise, clean the queue only when it's exceeded the next
390
     * fullness threshold.  We have to loop and recheck the buffer state
391
     * after any call of SICleanupQueue.
392
     */
393
0
    for (;;)
394
0
    {
395
0
      numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum;
396
0
      if (numMsgs + nthistime > MAXNUMMESSAGES ||
397
0
        numMsgs >= segP->nextThreshold)
398
0
        SICleanupQueue(true, nthistime);
399
0
      else
400
0
        break;
401
0
    }
402
403
    /*
404
     * Insert new message(s) into proper slot of circular buffer
405
     */
406
0
    max = pg_atomic_read_u32(&segP->maxMsgNum);
407
0
    while (nthistime-- > 0)
408
0
    {
409
0
      segP->buffer[max % MAXNUMMESSAGES] = *data++;
410
0
      max++;
411
0
    }
412
413
    /* Update current value of maxMsgNum using barrier */
414
0
    pg_atomic_write_membarrier_u32(&segP->maxMsgNum, max);
415
416
    /*
417
     * Now that the maxMsgNum change is globally visible, we give everyone
418
     * a swift kick to make sure they read the newly added messages.
419
     * Releasing SInvalWriteLock will enforce a full memory barrier, so
420
     * these (unlocked) changes will be committed to memory before we exit
421
     * the function.
422
     */
423
0
    for (i = 0; i < segP->numProcs; i++)
424
0
    {
425
0
      ProcState  *stateP = &segP->procState[segP->pgprocnos[i]];
426
427
0
      stateP->hasMessages = true;
428
0
    }
429
430
0
    LWLockRelease(SInvalWriteLock);
431
0
  }
432
0
}
433
434
/*
435
 * SIGetDataEntries
436
 *    get next SI message(s) for current backend, if there are any
437
 *
438
 * Possible return values:
439
 *  0:   no SI message available
440
 *  n>0: next n SI messages have been extracted into data[]
441
 * -1:   SI reset message extracted
442
 *
443
 * If the return value is less than the array size "datasize", the caller
444
 * can assume that there are no more SI messages after the one(s) returned.
445
 * Otherwise, another call is needed to collect more messages.
446
 *
447
 * NB: this can run in parallel with other instances of SIGetDataEntries
448
 * executing on behalf of other backends, since each instance will modify only
449
 * fields of its own backend's ProcState, and no instance will look at fields
450
 * of other backends' ProcStates.  We express this by grabbing SInvalReadLock
451
 * in shared mode.  Note that this is not exactly the normal (read-only)
452
 * interpretation of a shared lock! Look closely at the interactions before
453
 * allowing SInvalReadLock to be grabbed in shared mode for any other reason!
454
 *
455
 * NB: this can also run in parallel with SIInsertDataEntries.  It is not
456
 * guaranteed that we will return any messages added after the routine is
457
 * entered.
458
 *
459
 * Note: we assume that "datasize" is not so large that it might be important
460
 * to break our hold on SInvalReadLock into segments.
461
 */
462
int
463
SIGetDataEntries(SharedInvalidationMessage *data, int datasize)
464
0
{
465
0
  SISeg    *segP;
466
0
  ProcState  *stateP;
467
0
  uint32    max;
468
0
  int     n;
469
470
0
  segP = shmInvalBuffer;
471
0
  stateP = &segP->procState[MyProcNumber];
472
473
  /*
474
   * Before starting to take locks, do a quick, unlocked test to see whether
475
   * there can possibly be anything to read.  On a multiprocessor system,
476
   * it's possible that this load could migrate backwards and occur before
477
   * we actually enter this function, so we might miss a sinval message that
478
   * was just added by some other processor.  But they can't migrate
479
   * backwards over a preceding lock acquisition, so it should be OK.  If we
480
   * haven't acquired a lock preventing against further relevant
481
   * invalidations, any such occurrence is not much different than if the
482
   * invalidation had arrived slightly later in the first place.
483
   */
484
0
  if (!stateP->hasMessages)
485
0
    return 0;
486
487
0
  LWLockAcquire(SInvalReadLock, LW_SHARED);
488
489
  /*
490
   * We must reset hasMessages before determining how many messages we're
491
   * going to read.  That way, if new messages arrive after we have
492
   * determined how many we're reading, the flag will get reset and we'll
493
   * notice those messages part-way through.
494
   *
495
   * Note that, if we don't end up reading all of the messages, we had
496
   * better be certain to reset this flag before exiting!
497
   */
498
0
  stateP->hasMessages = false;
499
500
  /* Fetch current value of maxMsgNum using barrier */
501
0
  max = pg_atomic_read_membarrier_u32(&segP->maxMsgNum);
502
503
0
  if (stateP->resetState)
504
0
  {
505
    /*
506
     * Force reset.  We can say we have dealt with any messages added
507
     * since the reset, as well; and that means we should clear the
508
     * signaled flag, too.
509
     */
510
0
    stateP->nextMsgNum = max;
511
0
    stateP->resetState = false;
512
0
    stateP->signaled = false;
513
0
    LWLockRelease(SInvalReadLock);
514
0
    return -1;
515
0
  }
516
517
  /*
518
   * Retrieve messages and advance backend's counter, until data array is
519
   * full or there are no more messages.
520
   *
521
   * There may be other backends that haven't read the message(s), so we
522
   * cannot delete them here.  SICleanupQueue() will eventually remove them
523
   * from the queue.
524
   */
525
0
  n = 0;
526
0
  while (n < datasize && stateP->nextMsgNum < max)
527
0
  {
528
0
    data[n++] = segP->buffer[stateP->nextMsgNum % MAXNUMMESSAGES];
529
0
    stateP->nextMsgNum++;
530
0
  }
531
532
  /*
533
   * If we have caught up completely, reset our "signaled" flag so that
534
   * we'll get another signal if we fall behind again.
535
   *
536
   * If we haven't caught up completely, reset the hasMessages flag so that
537
   * we see the remaining messages next time.
538
   */
539
0
  if (stateP->nextMsgNum >= max)
540
0
    stateP->signaled = false;
541
0
  else
542
0
    stateP->hasMessages = true;
543
544
0
  LWLockRelease(SInvalReadLock);
545
0
  return n;
546
0
}
547
548
/*
549
 * SICleanupQueue
550
 *    Remove messages that have been consumed by all active backends
551
 *
552
 * callerHasWriteLock is true if caller is holding SInvalWriteLock.
553
 * minFree is the minimum number of message slots to make free.
554
 *
555
 * Possible side effects of this routine include marking one or more
556
 * backends as "reset" in the array, and sending PROCSIG_CATCHUP_INTERRUPT
557
 * to some backend that seems to be getting too far behind.  We signal at
558
 * most one backend at a time, for reasons explained at the top of the file.
559
 *
560
 * Caution: because we transiently release write lock when we have to signal
561
 * some other backend, it is NOT guaranteed that there are still minFree
562
 * free message slots at exit.  Caller must recheck and perhaps retry.
563
 */
564
void
565
SICleanupQueue(bool callerHasWriteLock, int minFree)
566
0
{
567
0
  SISeg    *segP = shmInvalBuffer;
568
0
  uint32    min,
569
0
        minsig,
570
0
        lowbound,
571
0
        numMsgs;
572
0
  int     i;
573
0
  ProcState  *needSig = NULL;
574
575
  /* Lock out all writers and readers */
576
0
  if (!callerHasWriteLock)
577
0
    LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE);
578
0
  LWLockAcquire(SInvalReadLock, LW_EXCLUSIVE);
579
580
  /*
581
   * Recompute minMsgNum = minimum of all backends' nextMsgNum, identify the
582
   * furthest-back backend that needs signaling (if any), and reset any
583
   * backends that are too far back.  Note that because we ignore sendOnly
584
   * backends here it is possible for them to keep sending messages without
585
   * a problem even when they are the only active backend.
586
   */
587
0
  min = pg_atomic_read_u32(&segP->maxMsgNum);
588
589
  /* clamp at zero to avoid underflow */
590
0
  if (min > SIG_THRESHOLD)
591
0
    minsig = min - SIG_THRESHOLD;
592
0
  else
593
0
    minsig = 0;
594
595
  /* clamp at zero to avoid underflow */
596
0
  if (min + minFree > MAXNUMMESSAGES)
597
0
    lowbound = min + minFree - MAXNUMMESSAGES;
598
0
  else
599
0
    lowbound = 0;
600
601
0
  for (i = 0; i < segP->numProcs; i++)
602
0
  {
603
0
    ProcState  *stateP = &segP->procState[segP->pgprocnos[i]];
604
0
    uint32    n = stateP->nextMsgNum;
605
606
    /* Ignore if already in reset state */
607
0
    Assert(stateP->procPid != 0);
608
0
    if (stateP->resetState || stateP->sendOnly)
609
0
      continue;
610
611
    /*
612
     * If we must free some space and this backend is preventing it, force
613
     * him into reset state and then ignore until he catches up.
614
     */
615
0
    if (n < lowbound)
616
0
    {
617
0
      stateP->resetState = true;
618
      /* no point in signaling him ... */
619
0
      continue;
620
0
    }
621
622
    /* Track the global minimum nextMsgNum */
623
0
    if (n < min)
624
0
      min = n;
625
626
    /* Also see who's furthest back of the unsignaled backends */
627
0
    if (n < minsig && !stateP->signaled)
628
0
    {
629
0
      minsig = n;
630
0
      needSig = stateP;
631
0
    }
632
0
  }
633
0
  segP->minMsgNum = min;
634
635
  /*
636
   * When minMsgNum gets really large, decrement all message counters so as
637
   * to forestall overflow of the counters.  This happens seldom enough that
638
   * folding it into the previous loop would be a loser.
639
   */
640
0
  if (min >= MSGNUMWRAPAROUND)
641
0
  {
642
0
    segP->minMsgNum -= MSGNUMWRAPAROUND;
643
0
    pg_atomic_fetch_sub_u32(&segP->maxMsgNum, MSGNUMWRAPAROUND);
644
0
    for (i = 0; i < segP->numProcs; i++)
645
0
      segP->procState[segP->pgprocnos[i]].nextMsgNum -= MSGNUMWRAPAROUND;
646
0
  }
647
648
  /*
649
   * Determine how many messages are still in the queue, and set the
650
   * threshold at which we should repeat SICleanupQueue().
651
   */
652
0
  numMsgs = pg_atomic_read_u32(&segP->maxMsgNum) - segP->minMsgNum;
653
0
  if (numMsgs < CLEANUP_MIN)
654
0
    segP->nextThreshold = CLEANUP_MIN;
655
0
  else
656
0
    segP->nextThreshold = (numMsgs / CLEANUP_QUANTUM + 1) * CLEANUP_QUANTUM;
657
658
  /*
659
   * Lastly, signal anyone who needs a catchup interrupt.  Since
660
   * SendProcSignal() might not be fast, we don't want to hold locks while
661
   * executing it.
662
   */
663
0
  if (needSig)
664
0
  {
665
0
    pid_t   his_pid = needSig->procPid;
666
0
    ProcNumber  his_procNumber = (needSig - &segP->procState[0]);
667
668
0
    needSig->signaled = true;
669
0
    LWLockRelease(SInvalReadLock);
670
0
    LWLockRelease(SInvalWriteLock);
671
0
    elog(DEBUG4, "sending sinval catchup signal to PID %d", (int) his_pid);
672
0
    SendProcSignal(his_pid, PROCSIG_CATCHUP_INTERRUPT, his_procNumber);
673
0
    if (callerHasWriteLock)
674
0
      LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE);
675
0
  }
676
0
  else
677
0
  {
678
0
    LWLockRelease(SInvalReadLock);
679
0
    if (!callerHasWriteLock)
680
0
      LWLockRelease(SInvalWriteLock);
681
0
  }
682
0
}
683
684
685
/*
686
 * GetNextLocalTransactionId --- allocate a new LocalTransactionId
687
 *
688
 * We split VirtualTransactionIds into two parts so that it is possible
689
 * to allocate a new one without any contention for shared memory, except
690
 * for a bit of additional overhead during backend startup/shutdown.
691
 * The high-order part of a VirtualTransactionId is a ProcNumber, and the
692
 * low-order part is a LocalTransactionId, which we assign from a local
693
 * counter.  To avoid the risk of a VirtualTransactionId being reused
694
 * within a short interval, successive procs occupying the same PGPROC slot
695
 * should use a consecutive sequence of local IDs, which is implemented
696
 * by copying nextLocalTransactionId as seen above.
697
 */
698
LocalTransactionId
699
GetNextLocalTransactionId(void)
700
0
{
701
0
  LocalTransactionId result;
702
703
  /* loop to avoid returning InvalidLocalTransactionId at wraparound */
704
0
  do
705
0
  {
706
0
    result = nextLocalTransactionId++;
707
0
  } while (!LocalTransactionIdIsValid(result));
708
709
0
  return result;
710
0
}