Coverage Report

Created: 2026-08-13 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/utils/activity/pgstat_shmem.c
Line
Count
Source
1
/* -------------------------------------------------------------------------
2
 *
3
 * pgstat_shmem.c
4
 *    Storage of stats entries in shared memory
5
 *
6
 * Copyright (c) 2001-2026, PostgreSQL Global Development Group
7
 *
8
 * IDENTIFICATION
9
 *    src/backend/utils/activity/pgstat_shmem.c
10
 * -------------------------------------------------------------------------
11
 */
12
13
#include "postgres.h"
14
15
#include "pgstat.h"
16
#include "storage/shmem.h"
17
#include "storage/subsystems.h"
18
#include "utils/memutils.h"
19
#include "utils/pgstat_internal.h"
20
21
22
0
#define PGSTAT_ENTRY_REF_HASH_SIZE  128
23
24
/* hash table entry for finding the PgStat_EntryRef for a key */
25
typedef struct PgStat_EntryRefHashEntry
26
{
27
  PgStat_HashKey key;     /* hash key */
28
  char    status;     /* for simplehash use */
29
  PgStat_EntryRef *entry_ref;
30
} PgStat_EntryRefHashEntry;
31
32
33
/* for references to shared statistics entries */
34
#define SH_PREFIX pgstat_entry_ref_hash
35
0
#define SH_ELEMENT_TYPE PgStat_EntryRefHashEntry
36
#define SH_KEY_TYPE PgStat_HashKey
37
0
#define SH_KEY key
38
#define SH_HASH_KEY(tb, key) \
39
0
  pgstat_hash_hash_key(&key, sizeof(PgStat_HashKey), NULL)
40
#define SH_EQUAL(tb, a, b) \
41
0
  pgstat_cmp_hash_key(&a, &b, sizeof(PgStat_HashKey), NULL) == 0
42
#define SH_SCOPE static inline
43
#define SH_DEFINE
44
#define SH_DECLARE
45
#include "lib/simplehash.h"
46
47
48
static void pgstat_drop_database_and_contents(Oid dboid);
49
50
static void pgstat_free_entry(PgStatShared_HashEntry *shent, dshash_seq_status *hstat);
51
52
static void pgstat_release_entry_ref(PgStat_HashKey key, PgStat_EntryRef *entry_ref, bool discard_pending);
53
static bool pgstat_need_entry_refs_gc(void);
54
static void pgstat_gc_entry_refs(void);
55
static void pgstat_release_all_entry_refs(bool discard_pending);
56
typedef bool (*ReleaseMatchCB) (PgStat_EntryRefHashEntry *, Datum data);
57
static void pgstat_release_matching_entry_refs(bool discard_pending, ReleaseMatchCB match, Datum match_data);
58
59
static void pgstat_setup_memcxt(void);
60
61
static void StatsShmemRequest(void *arg);
62
static void StatsShmemInit(void *arg);
63
64
const ShmemCallbacks StatsShmemCallbacks = {
65
  .request_fn = StatsShmemRequest,
66
  .init_fn = StatsShmemInit,
67
};
68
69
/* parameter for the shared hash */
70
static const dshash_parameters dsh_params = {
71
  sizeof(PgStat_HashKey),
72
  sizeof(PgStatShared_HashEntry),
73
  pgstat_cmp_hash_key,
74
  pgstat_hash_hash_key,
75
  dshash_memcpy,
76
  LWTRANCHE_PGSTATS_HASH
77
};
78
79
80
/*
81
 * Backend local references to shared stats entries. If there are pending
82
 * updates to a stats entry, the PgStat_EntryRef is added to the pgStatPending
83
 * list.
84
 *
85
 * When a stats entry is dropped each backend needs to release its reference
86
 * to it before the memory can be released. To trigger that
87
 * pgStatLocal.shmem->gc_request_count is incremented - which each backend
88
 * compares to their copy of pgStatSharedRefAge on a regular basis.
89
 */
90
static pgstat_entry_ref_hash_hash *pgStatEntryRefHash = NULL;
91
static int  pgStatSharedRefAge = 0; /* cache age of pgStatLocal.shmem */
92
93
/*
94
 * Memory contexts containing the pgStatEntryRefHash table and the
95
 * pgStatSharedRef entries respectively. Kept separate to make it easier to
96
 * track / attribute memory usage.
97
 */
98
static MemoryContext pgStatSharedRefContext = NULL;
99
static MemoryContext pgStatEntryRefHashContext = NULL;
100
101
102
/* ------------------------------------------------------------
103
 * Public functions called from postmaster follow
104
 * ------------------------------------------------------------
105
 */
106
107
/*
108
 * The size of the shared memory allocation for stats stored in the shared
109
 * stats hash table. This allocation will be done as part of the main shared
110
 * memory, rather than dynamic shared memory, allowing it to be initialized in
111
 * postmaster.
112
 */
113
static Size
114
pgstat_dsa_init_size(void)
115
0
{
116
0
  Size    sz;
117
118
  /*
119
   * The dshash header / initial buckets array needs to fit into "plain"
120
   * shared memory, but it's beneficial to not need dsm segments
121
   * immediately. A size of 256kB seems works well and is not
122
   * disproportional compared to other constant sized shared memory
123
   * allocations. NB: To avoid DSMs further, the user can configure
124
   * min_dynamic_shared_memory.
125
   */
126
0
  sz = 256 * 1024;
127
0
  Assert(dsa_minimum_size() <= sz);
128
0
  return MAXALIGN(sz);
129
0
}
130
131
/*
132
 * Compute shared memory space needed for cumulative statistics
133
 */
134
static Size
135
StatsShmemSize(void)
136
0
{
137
0
  Size    sz;
138
139
0
  sz = MAXALIGN(sizeof(PgStat_ShmemControl));
140
0
  sz = add_size(sz, pgstat_dsa_init_size());
141
142
  /* Add shared memory for all the custom fixed-numbered statistics */
143
0
  for (PgStat_Kind kind = PGSTAT_KIND_CUSTOM_MIN; kind <= PGSTAT_KIND_CUSTOM_MAX; kind++)
144
0
  {
145
0
    const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
146
147
0
    if (!kind_info)
148
0
      continue;
149
0
    if (!kind_info->fixed_amount)
150
0
      continue;
151
152
0
    Assert(kind_info->shared_size != 0);
153
0
    sz = add_size(sz, MAXALIGN(kind_info->shared_size));
154
0
  }
155
156
0
  return sz;
157
0
}
158
159
/*
160
 * Register shared memory area for cumulative statistics
161
 */
162
static void
163
StatsShmemRequest(void *arg)
164
0
{
165
0
  ShmemRequestStruct(.name = "Shared Memory Stats",
166
0
             .size = StatsShmemSize(),
167
0
             .ptr = (void **) &pgStatLocal.shmem,
168
0
    );
169
0
}
170
171
/*
172
 * Initialize cumulative statistics system during startup
173
 */
174
static void
175
StatsShmemInit(void *arg)
176
0
{
177
0
  dsa_area   *dsa;
178
0
  dshash_table *dsh;
179
0
  PgStat_ShmemControl *ctl = pgStatLocal.shmem;
180
0
  char     *p = (char *) ctl;
181
182
  /* the allocation of pgStatLocal.shmem itself */
183
0
  p += MAXALIGN(sizeof(PgStat_ShmemControl));
184
185
  /*
186
   * Create a small dsa allocation in plain shared memory. This is required
187
   * because postmaster cannot use dsm segments. It also provides a small
188
   * efficiency win.
189
   */
190
0
  ctl->raw_dsa_area = p;
191
0
  p += pgstat_dsa_init_size();
192
0
  dsa = dsa_create_in_place(ctl->raw_dsa_area,
193
0
                pgstat_dsa_init_size(),
194
0
                LWTRANCHE_PGSTATS_DSA, NULL);
195
0
  dsa_pin(dsa);
196
197
  /*
198
   * To ensure dshash is created in "plain" shared memory, temporarily limit
199
   * size of dsa to the initial size of the dsa.
200
   */
201
0
  dsa_set_size_limit(dsa, pgstat_dsa_init_size());
202
203
  /*
204
   * With the limit in place, create the dshash table. XXX: It'd be nice if
205
   * there were dshash_create_in_place().
206
   */
207
0
  dsh = dshash_create(dsa, &dsh_params, NULL);
208
0
  ctl->hash_handle = dshash_get_hash_table_handle(dsh);
209
210
  /* lift limit set above */
211
0
  dsa_set_size_limit(dsa, -1);
212
213
  /*
214
   * Postmaster will never access these again, thus free the local
215
   * dsa/dshash references.
216
   */
217
0
  dshash_detach(dsh);
218
0
  dsa_detach(dsa);
219
220
0
  pg_atomic_init_u64(&ctl->gc_request_count, 1);
221
222
  /* Do the per-kind initialization */
223
0
  for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
224
0
  {
225
0
    const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
226
0
    char     *ptr;
227
228
0
    if (!kind_info)
229
0
      continue;
230
231
    /* initialize entry count tracking */
232
0
    if (kind_info->track_entry_count)
233
0
      pg_atomic_init_u64(&ctl->entry_counts[kind - 1], 0);
234
235
    /* initialize fixed-numbered stats */
236
0
    if (kind_info->fixed_amount)
237
0
    {
238
0
      if (pgstat_is_kind_builtin(kind))
239
0
        ptr = ((char *) ctl) + kind_info->shared_ctl_off;
240
0
      else
241
0
      {
242
0
        int     idx = kind - PGSTAT_KIND_CUSTOM_MIN;
243
244
0
        Assert(kind_info->shared_size != 0);
245
0
        ctl->custom_data[idx] = p;
246
0
        p += MAXALIGN(kind_info->shared_size);
247
0
        ptr = ctl->custom_data[idx];
248
0
      }
249
250
0
      kind_info->init_shmem_cb(ptr);
251
0
    }
252
0
  }
253
0
}
254
255
void
256
pgstat_attach_shmem(void)
257
0
{
258
0
  MemoryContext oldcontext;
259
260
0
  Assert(pgStatLocal.dsa == NULL);
261
262
  /* stats shared memory persists for the backend lifetime */
263
0
  oldcontext = MemoryContextSwitchTo(TopMemoryContext);
264
265
0
  pgStatLocal.dsa = dsa_attach_in_place(pgStatLocal.shmem->raw_dsa_area,
266
0
                      NULL);
267
0
  dsa_pin_mapping(pgStatLocal.dsa);
268
269
0
  pgStatLocal.shared_hash = dshash_attach(pgStatLocal.dsa, &dsh_params,
270
0
                      pgStatLocal.shmem->hash_handle,
271
0
                      NULL);
272
273
0
  MemoryContextSwitchTo(oldcontext);
274
0
}
275
276
void
277
pgstat_detach_shmem(void)
278
0
{
279
0
  Assert(pgStatLocal.dsa);
280
281
  /* we shouldn't leave references to shared stats */
282
0
  pgstat_release_all_entry_refs(false);
283
284
0
  dshash_detach(pgStatLocal.shared_hash);
285
0
  pgStatLocal.shared_hash = NULL;
286
287
0
  dsa_detach(pgStatLocal.dsa);
288
289
  /*
290
   * dsa_detach() does not decrement the DSA reference count as no segment
291
   * was provided to dsa_attach_in_place(), causing no cleanup callbacks to
292
   * be registered.  Hence, release it manually now.
293
   */
294
0
  dsa_release_in_place(pgStatLocal.shmem->raw_dsa_area);
295
296
0
  pgStatLocal.dsa = NULL;
297
0
}
298
299
300
/* ------------------------------------------------------------
301
 * Maintenance of shared memory stats entries
302
 * ------------------------------------------------------------
303
 */
304
305
/*
306
 * Initialize entry newly-created.
307
 *
308
 * Returns NULL in the event of an allocation failure, so as callers can
309
 * take cleanup actions as the entry initialized is already inserted in the
310
 * shared hashtable.
311
 */
312
PgStatShared_Common *
313
pgstat_init_entry(PgStat_Kind kind,
314
          PgStatShared_HashEntry *shhashent)
315
0
{
316
  /* Create new stats entry. */
317
0
  dsa_pointer chunk;
318
0
  PgStatShared_Common *shheader;
319
0
  const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
320
321
  /*
322
   * Initialize refcount to 1, marking it as valid / not dropped. The entry
323
   * can't be freed before the initialization because it can't be found as
324
   * long as we hold the dshash partition lock. Caller needs to increase
325
   * further if a longer lived reference is needed.
326
   */
327
0
  pg_atomic_init_u32(&shhashent->refcount, 1);
328
329
  /*
330
   * Initialize "generation" to 0, as freshly created.
331
   */
332
0
  pg_atomic_init_u32(&shhashent->generation, 0);
333
0
  shhashent->dropped = false;
334
335
0
  chunk = dsa_allocate_extended(pgStatLocal.dsa,
336
0
                  kind_info->shared_size,
337
0
                  DSA_ALLOC_ZERO | DSA_ALLOC_NO_OOM);
338
0
  if (chunk == InvalidDsaPointer)
339
0
    return NULL;
340
341
0
  shheader = dsa_get_address(pgStatLocal.dsa, chunk);
342
0
  shheader->magic = 0xdeadbeef;
343
344
  /* Link the new entry from the hash entry. */
345
0
  shhashent->body = chunk;
346
347
  /* Increment entry count, if required. */
348
0
  if (kind_info->track_entry_count)
349
0
    pg_atomic_fetch_add_u64(&pgStatLocal.shmem->entry_counts[kind - 1], 1);
350
351
0
  LWLockInitialize(&shheader->lock, LWTRANCHE_PGSTATS_DATA);
352
353
0
  return shheader;
354
0
}
355
356
static PgStatShared_Common *
357
pgstat_reinit_entry(PgStat_Kind kind, PgStatShared_HashEntry *shhashent)
358
0
{
359
0
  PgStatShared_Common *shheader;
360
361
0
  shheader = dsa_get_address(pgStatLocal.dsa, shhashent->body);
362
363
  /* mark as not dropped anymore */
364
0
  pg_atomic_fetch_add_u32(&shhashent->refcount, 1);
365
366
  /*
367
   * Increment "generation", to let any backend with local references know
368
   * that what they point to is outdated.
369
   */
370
0
  pg_atomic_fetch_add_u32(&shhashent->generation, 1);
371
0
  shhashent->dropped = false;
372
373
  /* reinitialize content */
374
0
  Assert(shheader->magic == 0xdeadbeef);
375
0
  memset(pgstat_get_entry_data(kind, shheader), 0,
376
0
       pgstat_get_entry_len(kind));
377
378
0
  return shheader;
379
0
}
380
381
static void
382
pgstat_setup_shared_refs(void)
383
0
{
384
0
  if (likely(pgStatEntryRefHash != NULL))
385
0
    return;
386
387
0
  pgStatEntryRefHash =
388
0
    pgstat_entry_ref_hash_create(pgStatEntryRefHashContext,
389
0
                   PGSTAT_ENTRY_REF_HASH_SIZE, NULL);
390
0
  pgStatSharedRefAge = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count);
391
0
  Assert(pgStatSharedRefAge != 0);
392
0
}
393
394
/*
395
 * Helper function for pgstat_get_entry_ref().
396
 */
397
static void
398
pgstat_acquire_entry_ref(PgStat_EntryRef *entry_ref,
399
             PgStatShared_HashEntry *shhashent,
400
             PgStatShared_Common *shheader)
401
0
{
402
0
  Assert(shheader->magic == 0xdeadbeef);
403
0
  Assert(pg_atomic_read_u32(&shhashent->refcount) > 0);
404
405
0
  pg_atomic_fetch_add_u32(&shhashent->refcount, 1);
406
407
0
  entry_ref->shared_stats = shheader;
408
0
  entry_ref->shared_entry = shhashent;
409
0
  entry_ref->generation = pg_atomic_read_u32(&shhashent->generation);
410
411
  /*
412
   * Complete the local reference before releasing the lock.  Releasing an
413
   * LWLock can process a pending interrupt, and callers may catch the
414
   * resulting error and continue using the backend-local cache.
415
   */
416
0
  dshash_release_lock(pgStatLocal.shared_hash, shhashent);
417
0
}
418
419
/*
420
 * Helper function for pgstat_get_entry_ref().
421
 */
422
static bool
423
pgstat_get_entry_ref_cached(PgStat_HashKey key, PgStat_EntryRef **entry_ref_p)
424
0
{
425
0
  bool    found;
426
0
  PgStat_EntryRefHashEntry *cache_entry;
427
428
  /*
429
   * We immediately insert a cache entry, because it avoids 1) multiple
430
   * hashtable lookups in case of a cache miss 2) having to deal with
431
   * out-of-memory errors after incrementing PgStatShared_Common->refcount.
432
   */
433
434
0
  cache_entry = pgstat_entry_ref_hash_insert(pgStatEntryRefHash, key, &found);
435
436
0
  if (!found || !cache_entry->entry_ref)
437
0
  {
438
0
    PgStat_EntryRef *entry_ref;
439
440
0
    entry_ref = MemoryContextAllocExtended(pgStatSharedRefContext,
441
0
                         sizeof(PgStat_EntryRef),
442
0
                         MCXT_ALLOC_NO_OOM);
443
0
    if (unlikely(entry_ref == NULL))
444
0
    {
445
      /*
446
       * Clean the hash entry to keep the table consistent in the
447
       * backend.
448
       */
449
0
      pgstat_entry_ref_hash_delete(pgStatEntryRefHash, key);
450
451
0
      ereport(ERROR,
452
0
          (errcode(ERRCODE_OUT_OF_MEMORY),
453
0
           errmsg("out of memory")));
454
0
    }
455
456
0
    cache_entry->entry_ref = entry_ref;
457
0
    entry_ref->shared_stats = NULL;
458
0
    entry_ref->shared_entry = NULL;
459
0
    entry_ref->pending = NULL;
460
461
0
    found = false;
462
0
  }
463
0
  else if (cache_entry->entry_ref->shared_stats == NULL)
464
0
  {
465
0
    Assert(cache_entry->entry_ref->pending == NULL);
466
0
    found = false;
467
0
  }
468
0
  else
469
0
  {
470
0
    PgStat_EntryRef *entry_ref PG_USED_FOR_ASSERTS_ONLY;
471
472
0
    entry_ref = cache_entry->entry_ref;
473
0
    Assert(entry_ref->shared_entry != NULL);
474
0
    Assert(entry_ref->shared_stats != NULL);
475
476
0
    Assert(entry_ref->shared_stats->magic == 0xdeadbeef);
477
    /* should have at least our reference */
478
0
    Assert(pg_atomic_read_u32(&entry_ref->shared_entry->refcount) > 0);
479
0
  }
480
481
0
  *entry_ref_p = cache_entry->entry_ref;
482
0
  return found;
483
0
}
484
485
/*
486
 * Get a shared stats reference. If create is true, the shared stats object is
487
 * created if it does not exist.
488
 *
489
 * When create is true, and created_entry is non-NULL, it'll be set to true
490
 * if the entry is newly created, false otherwise.
491
 */
492
PgStat_EntryRef *
493
pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
494
           bool *created_entry)
495
0
{
496
0
  PgStat_HashKey key = {0};
497
0
  PgStatShared_HashEntry *shhashent;
498
0
  PgStatShared_Common *shheader = NULL;
499
0
  PgStat_EntryRef *entry_ref;
500
501
0
  key.kind = kind;
502
0
  key.dboid = dboid;
503
0
  key.objid = objid;
504
505
  /*
506
   * passing in created_entry only makes sense if we possibly could create
507
   * entry.
508
   */
509
0
  Assert(create || created_entry == NULL);
510
0
  pgstat_assert_is_up();
511
0
  Assert(pgStatLocal.shared_hash != NULL);
512
0
  Assert(!pgStatLocal.shmem->is_shutdown);
513
514
0
  pgstat_setup_memcxt();
515
0
  pgstat_setup_shared_refs();
516
517
0
  if (created_entry != NULL)
518
0
    *created_entry = false;
519
520
  /*
521
   * Check if other backends dropped stats that could not be deleted because
522
   * somebody held references to it. If so, check this backend's references.
523
   * This is not expected to happen often. The location of the check is a
524
   * bit random, but this is a relatively frequently called path, so better
525
   * than most.
526
   */
527
0
  if (pgstat_need_entry_refs_gc())
528
0
    pgstat_gc_entry_refs();
529
530
  /*
531
   * First check the lookup cache hashtable in local memory. If we find a
532
   * match here we can avoid taking locks / causing contention.
533
   */
534
0
  if (pgstat_get_entry_ref_cached(key, &entry_ref))
535
0
    return entry_ref;
536
537
0
  Assert(entry_ref != NULL);
538
539
  /*
540
   * Do a lookup in the hash table first - it's quite likely that the entry
541
   * already exists, and that way we only need a shared lock.
542
   */
543
0
  shhashent = dshash_find(pgStatLocal.shared_hash, &key, false);
544
545
0
  if (create && !shhashent)
546
0
  {
547
0
    bool    shfound;
548
549
    /*
550
     * It's possible that somebody created the entry since the above
551
     * lookup. If so, fall through to the same path as if we'd have if it
552
     * already had been created before the dshash_find() calls.
553
     */
554
0
    shhashent = dshash_find_or_insert_extended(pgStatLocal.shared_hash,
555
0
                           &key, &shfound,
556
0
                           DSHASH_INSERT_NO_OOM);
557
0
    if (!shhashent)
558
0
    {
559
      /*
560
       * Clean up the local reference when failing insert into the
561
       * shared hashtable.
562
       */
563
0
      pgstat_release_entry_ref(key, entry_ref, false);
564
0
      ereport(ERROR,
565
0
          (errcode(ERRCODE_OUT_OF_MEMORY),
566
0
           errmsg("out of memory"),
567
0
           errdetail("Failed while inserting entry %u/%u/%" PRIu64 ".",
568
0
                 key.kind, key.dboid, key.objid)));
569
0
    }
570
571
0
    if (!shfound)
572
0
    {
573
0
      shheader = pgstat_init_entry(kind, shhashent);
574
0
      if (shheader == NULL)
575
0
      {
576
        /*
577
         * Failed the allocation of a new entry, so clean up both the
578
         * local reference and the shared hashtable before giving up.
579
         * Clean the local state first, since releasing the dshash
580
         * lock can process a pending interrupt.
581
         */
582
0
        pgstat_release_entry_ref(key, entry_ref, false);
583
0
        dshash_delete_entry(pgStatLocal.shared_hash, shhashent);
584
585
0
        ereport(ERROR,
586
0
            (errcode(ERRCODE_OUT_OF_MEMORY),
587
0
             errmsg("out of memory"),
588
0
             errdetail("Failed while allocating entry %u/%u/%" PRIu64 ".",
589
0
                   key.kind, key.dboid, key.objid)));
590
0
      }
591
0
      pgstat_acquire_entry_ref(entry_ref, shhashent, shheader);
592
593
0
      if (created_entry != NULL)
594
0
        *created_entry = true;
595
596
0
      return entry_ref;
597
0
    }
598
0
  }
599
600
0
  if (!shhashent)
601
0
  {
602
    /*
603
     * If we're not creating, delete the reference again. In all
604
     * likelihood it's just a stats lookup - no point wasting memory for a
605
     * shared ref to nothing...
606
     */
607
0
    pgstat_release_entry_ref(key, entry_ref, false);
608
609
0
    return NULL;
610
0
  }
611
0
  else
612
0
  {
613
    /*
614
     * Can get here either because dshash_find() found a match, or if
615
     * dshash_find_or_insert() found a concurrently inserted entry.
616
     */
617
618
0
    if (shhashent->dropped && create)
619
0
    {
620
      /*
621
       * There are legitimate cases where the old stats entry might not
622
       * yet have been dropped by the time it's reused. The most obvious
623
       * case are replication slot stats, where a new slot can be
624
       * created with the same index just after dropping. But oid
625
       * wraparound can lead to other cases as well. We just reset the
626
       * stats to their plain state, while incrementing its "generation"
627
       * in the shared entry for any remaining local references.
628
       */
629
0
      shheader = pgstat_reinit_entry(kind, shhashent);
630
0
      pgstat_acquire_entry_ref(entry_ref, shhashent, shheader);
631
632
0
      if (created_entry != NULL)
633
0
        *created_entry = true;
634
635
0
      return entry_ref;
636
0
    }
637
0
    else if (shhashent->dropped)
638
0
    {
639
0
      dshash_release_lock(pgStatLocal.shared_hash, shhashent);
640
0
      pgstat_release_entry_ref(key, entry_ref, false);
641
642
0
      return NULL;
643
0
    }
644
0
    else
645
0
    {
646
0
      shheader = dsa_get_address(pgStatLocal.dsa, shhashent->body);
647
0
      pgstat_acquire_entry_ref(entry_ref, shhashent, shheader);
648
649
0
      return entry_ref;
650
0
    }
651
0
  }
652
0
}
653
654
static void
655
pgstat_release_entry_ref(PgStat_HashKey key, PgStat_EntryRef *entry_ref,
656
             bool discard_pending)
657
0
{
658
0
  if (entry_ref && entry_ref->pending)
659
0
  {
660
0
    if (discard_pending)
661
0
      pgstat_delete_pending_entry(entry_ref);
662
0
    else
663
0
      elog(ERROR, "releasing ref with pending data");
664
0
  }
665
666
0
  if (entry_ref && entry_ref->shared_stats)
667
0
  {
668
0
    Assert(entry_ref->shared_stats->magic == 0xdeadbeef);
669
0
    Assert(entry_ref->pending == NULL);
670
671
    /*
672
     * This can't race with another backend looking up the stats entry and
673
     * increasing the refcount because it is not "legal" to create
674
     * additional references to dropped entries.
675
     */
676
0
    if (pg_atomic_fetch_sub_u32(&entry_ref->shared_entry->refcount, 1) == 1)
677
0
    {
678
0
      PgStatShared_HashEntry *shent;
679
680
      /*
681
       * We're the last referrer to this entry, try to drop the shared
682
       * entry.
683
       */
684
685
      /* only dropped entries can reach a 0 refcount */
686
0
      Assert(entry_ref->shared_entry->dropped);
687
688
0
      shent = dshash_find(pgStatLocal.shared_hash,
689
0
                &entry_ref->shared_entry->key,
690
0
                true);
691
0
      if (!shent)
692
0
        elog(ERROR, "could not find just referenced shared stats entry");
693
694
      /*
695
       * This entry may have been reinitialized while trying to release
696
       * it, so double-check that it has not been reused while holding a
697
       * lock on its shared entry.
698
       */
699
0
      if (pg_atomic_read_u32(&entry_ref->shared_entry->generation) ==
700
0
        entry_ref->generation)
701
0
      {
702
        /* Same "generation", so we're OK with the removal */
703
0
        Assert(pg_atomic_read_u32(&entry_ref->shared_entry->refcount) == 0);
704
0
        Assert(entry_ref->shared_entry == shent);
705
0
        pgstat_free_entry(shent, NULL);
706
0
      }
707
0
      else
708
0
      {
709
        /*
710
         * Shared stats entry has been reinitialized, so do not drop
711
         * its shared entry, only release its lock.
712
         */
713
0
        dshash_release_lock(pgStatLocal.shared_hash, shent);
714
0
      }
715
0
    }
716
0
  }
717
718
0
  if (!pgstat_entry_ref_hash_delete(pgStatEntryRefHash, key))
719
0
    elog(ERROR, "entry ref vanished before deletion");
720
721
0
  if (entry_ref)
722
0
    pfree(entry_ref);
723
0
}
724
725
/*
726
 * Acquire exclusive lock on the entry.
727
 *
728
 * If nowait is true, it's just a conditional acquire, and the result
729
 * *must* be checked to verify success.
730
 * If nowait is false, waits as necessary, always returning true.
731
 */
732
bool
733
pgstat_lock_entry(PgStat_EntryRef *entry_ref, bool nowait)
734
0
{
735
0
  LWLock     *lock = &entry_ref->shared_stats->lock;
736
737
0
  if (nowait)
738
0
    return LWLockConditionalAcquire(lock, LW_EXCLUSIVE);
739
740
0
  LWLockAcquire(lock, LW_EXCLUSIVE);
741
0
  return true;
742
0
}
743
744
/*
745
 * Acquire shared lock on the entry.
746
 *
747
 * Separate from pgstat_lock_entry() as most callers will need to lock
748
 * exclusively.  The wait semantics are identical.
749
 */
750
bool
751
pgstat_lock_entry_shared(PgStat_EntryRef *entry_ref, bool nowait)
752
0
{
753
0
  LWLock     *lock = &entry_ref->shared_stats->lock;
754
755
0
  if (nowait)
756
0
    return LWLockConditionalAcquire(lock, LW_SHARED);
757
758
0
  LWLockAcquire(lock, LW_SHARED);
759
0
  return true;
760
0
}
761
762
void
763
pgstat_unlock_entry(PgStat_EntryRef *entry_ref)
764
0
{
765
0
  LWLockRelease(&entry_ref->shared_stats->lock);
766
0
}
767
768
/*
769
 * Helper function to fetch and lock shared stats.
770
 */
771
PgStat_EntryRef *
772
pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, uint64 objid,
773
              bool nowait)
774
0
{
775
0
  PgStat_EntryRef *entry_ref;
776
777
  /* find shared table stats entry corresponding to the local entry */
778
0
  entry_ref = pgstat_get_entry_ref(kind, dboid, objid, true, NULL);
779
780
  /* lock the shared entry to protect the content, skip if failed */
781
0
  if (!pgstat_lock_entry(entry_ref, nowait))
782
0
    return NULL;
783
784
0
  return entry_ref;
785
0
}
786
787
void
788
pgstat_request_entry_refs_gc(void)
789
0
{
790
0
  pg_atomic_fetch_add_u64(&pgStatLocal.shmem->gc_request_count, 1);
791
0
}
792
793
static bool
794
pgstat_need_entry_refs_gc(void)
795
0
{
796
0
  uint64    curage;
797
798
0
  if (!pgStatEntryRefHash)
799
0
    return false;
800
801
  /* should have been initialized when creating pgStatEntryRefHash */
802
0
  Assert(pgStatSharedRefAge != 0);
803
804
0
  curage = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count);
805
806
0
  return pgStatSharedRefAge != curage;
807
0
}
808
809
static void
810
pgstat_gc_entry_refs(void)
811
0
{
812
0
  pgstat_entry_ref_hash_iterator i;
813
0
  PgStat_EntryRefHashEntry *ent;
814
0
  uint64    curage;
815
816
0
  curage = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count);
817
0
  Assert(curage != 0);
818
819
  /*
820
   * Some entries have been dropped or reinitialized.  Invalidate cache
821
   * pointer to them.
822
   */
823
0
  pgstat_entry_ref_hash_start_iterate(pgStatEntryRefHash, &i);
824
0
  while ((ent = pgstat_entry_ref_hash_iterate(pgStatEntryRefHash, &i)) != NULL)
825
0
  {
826
0
    PgStat_EntryRef *entry_ref = ent->entry_ref;
827
828
0
    Assert(!entry_ref->shared_stats ||
829
0
         entry_ref->shared_stats->magic == 0xdeadbeef);
830
831
    /*
832
     * "generation" checks for the case of entries being reinitialized,
833
     * and "dropped" for the case where these are..  dropped.
834
     */
835
0
    if (!entry_ref->shared_entry->dropped &&
836
0
      pg_atomic_read_u32(&entry_ref->shared_entry->generation) ==
837
0
      entry_ref->generation)
838
0
      continue;
839
840
    /* cannot gc shared ref that has pending data */
841
0
    if (entry_ref->pending != NULL)
842
0
      continue;
843
844
0
    pgstat_release_entry_ref(ent->key, entry_ref, false);
845
0
  }
846
847
0
  pgStatSharedRefAge = curage;
848
0
}
849
850
static void
851
pgstat_release_matching_entry_refs(bool discard_pending, ReleaseMatchCB match,
852
                   Datum match_data)
853
0
{
854
0
  pgstat_entry_ref_hash_iterator i;
855
0
  PgStat_EntryRefHashEntry *ent;
856
857
0
  if (pgStatEntryRefHash == NULL)
858
0
    return;
859
860
0
  pgstat_entry_ref_hash_start_iterate(pgStatEntryRefHash, &i);
861
862
0
  while ((ent = pgstat_entry_ref_hash_iterate(pgStatEntryRefHash, &i))
863
0
       != NULL)
864
0
  {
865
0
    Assert(ent->entry_ref != NULL);
866
867
0
    if (match && !match(ent, match_data))
868
0
      continue;
869
870
0
    pgstat_release_entry_ref(ent->key, ent->entry_ref, discard_pending);
871
0
  }
872
0
}
873
874
/*
875
 * Release all local references to shared stats entries.
876
 *
877
 * When a process exits it cannot do so while still holding references onto
878
 * stats entries, otherwise the shared stats entries could never be freed.
879
 */
880
static void
881
pgstat_release_all_entry_refs(bool discard_pending)
882
0
{
883
0
  if (pgStatEntryRefHash == NULL)
884
0
    return;
885
886
0
  pgstat_release_matching_entry_refs(discard_pending, NULL, 0);
887
0
  Assert(pgStatEntryRefHash->members == 0);
888
0
  pgstat_entry_ref_hash_destroy(pgStatEntryRefHash);
889
0
  pgStatEntryRefHash = NULL;
890
0
}
891
892
static bool
893
match_db(PgStat_EntryRefHashEntry *ent, Datum match_data)
894
0
{
895
0
  Oid     dboid = DatumGetObjectId(match_data);
896
897
0
  return ent->key.dboid == dboid;
898
0
}
899
900
static void
901
pgstat_release_db_entry_refs(Oid dboid)
902
0
{
903
0
  pgstat_release_matching_entry_refs( /* discard pending = */ true,
904
0
                     match_db,
905
0
                     ObjectIdGetDatum(dboid));
906
0
}
907
908
909
/* ------------------------------------------------------------
910
 * Dropping and resetting of stats entries
911
 * ------------------------------------------------------------
912
 */
913
914
static void
915
pgstat_free_entry(PgStatShared_HashEntry *shent, dshash_seq_status *hstat)
916
0
{
917
0
  dsa_pointer pdsa;
918
0
  PgStat_Kind kind = shent->key.kind;
919
920
  /*
921
   * Fetch dsa pointer before deleting entry - that way we can free the
922
   * memory after releasing the lock.
923
   */
924
0
  pdsa = shent->body;
925
926
0
  if (!hstat)
927
0
    dshash_delete_entry(pgStatLocal.shared_hash, shent);
928
0
  else
929
0
    dshash_delete_current(hstat);
930
931
0
  dsa_free(pgStatLocal.dsa, pdsa);
932
933
  /* Decrement entry count, if required. */
934
0
  if (pgstat_get_kind_info(kind)->track_entry_count)
935
0
    pg_atomic_sub_fetch_u64(&pgStatLocal.shmem->entry_counts[kind - 1], 1);
936
0
}
937
938
/*
939
 * Helper for both pgstat_drop_database_and_contents() and
940
 * pgstat_drop_entry(). If hstat is non-null delete the shared entry using
941
 * dshash_delete_current(), otherwise use dshash_delete_entry(). In either
942
 * case the entry needs to be already locked.
943
 */
944
static bool
945
pgstat_drop_entry_internal(PgStatShared_HashEntry *shent,
946
               dshash_seq_status *hstat)
947
0
{
948
0
  Assert(shent->body != InvalidDsaPointer);
949
950
  /* should already have released local reference */
951
0
  if (pgStatEntryRefHash)
952
0
    Assert(!pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, shent->key));
953
954
  /*
955
   * Signal that the entry is dropped - this will eventually cause other
956
   * backends to release their references.
957
   */
958
0
  Assert(!shent->dropped);
959
0
  shent->dropped = true;
960
961
  /* release refcount marking entry as not dropped */
962
0
  if (pg_atomic_sub_fetch_u32(&shent->refcount, 1) == 0)
963
0
  {
964
0
    pgstat_free_entry(shent, hstat);
965
0
    return true;
966
0
  }
967
0
  else
968
0
  {
969
0
    if (!hstat)
970
0
      dshash_release_lock(pgStatLocal.shared_hash, shent);
971
0
    return false;
972
0
  }
973
0
}
974
975
/*
976
 * Drop stats for the database and all the objects inside that database.
977
 */
978
static void
979
pgstat_drop_database_and_contents(Oid dboid)
980
0
{
981
0
  dshash_seq_status hstat;
982
0
  PgStatShared_HashEntry *p;
983
0
  uint64    not_freed_count = 0;
984
985
0
  Assert(OidIsValid(dboid));
986
987
0
  Assert(pgStatLocal.shared_hash != NULL);
988
989
  /*
990
   * This backend might very well be the only backend holding a reference to
991
   * about-to-be-dropped entries. Ensure that we're not preventing it from
992
   * being cleaned up till later.
993
   *
994
   * Doing this separately from the dshash iteration below avoids having to
995
   * do so while holding a partition lock on the shared hashtable.
996
   */
997
0
  pgstat_release_db_entry_refs(dboid);
998
999
  /* some of the dshash entries are to be removed, take exclusive lock. */
1000
0
  dshash_seq_init(&hstat, pgStatLocal.shared_hash, true);
1001
0
  while ((p = dshash_seq_next(&hstat)) != NULL)
1002
0
  {
1003
0
    if (p->dropped)
1004
0
      continue;
1005
1006
0
    if (p->key.dboid != dboid)
1007
0
      continue;
1008
1009
0
    if (!pgstat_drop_entry_internal(p, &hstat))
1010
0
    {
1011
      /*
1012
       * Even statistics for a dropped database might currently be
1013
       * accessed (consider e.g. database stats for pg_stat_database).
1014
       */
1015
0
      not_freed_count++;
1016
0
    }
1017
0
  }
1018
0
  dshash_seq_term(&hstat);
1019
1020
  /*
1021
   * If some of the stats data could not be freed, signal the reference
1022
   * holders to run garbage collection of their cached pgStatLocal.shmem.
1023
   */
1024
0
  if (not_freed_count > 0)
1025
0
    pgstat_request_entry_refs_gc();
1026
0
}
1027
1028
/*
1029
 * Drop a single stats entry.
1030
 *
1031
 * This routine returns false if the stats entry of the dropped object could
1032
 * not be freed, true otherwise.
1033
 *
1034
 * If missing_ok is true, skip entries that have been concurrently dropped.
1035
 *
1036
 * The callers of this function should call pgstat_request_entry_refs_gc()
1037
 * if the stats entry could not be freed, to ensure that this entry's memory
1038
 * can be reclaimed later by a different backend calling
1039
 * pgstat_gc_entry_refs().
1040
 */
1041
bool
1042
pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid,
1043
          bool missing_ok)
1044
0
{
1045
0
  PgStat_HashKey key = {0};
1046
0
  PgStatShared_HashEntry *shent;
1047
0
  bool    freed = true;
1048
1049
0
  key.kind = kind;
1050
0
  key.dboid = dboid;
1051
0
  key.objid = objid;
1052
1053
  /* delete local reference */
1054
0
  if (pgStatEntryRefHash)
1055
0
  {
1056
0
    PgStat_EntryRefHashEntry *lohashent =
1057
0
      pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, key);
1058
1059
0
    if (lohashent)
1060
0
      pgstat_release_entry_ref(lohashent->key, lohashent->entry_ref,
1061
0
                   true);
1062
0
  }
1063
1064
  /* mark entry in shared hashtable as deleted, drop if possible */
1065
0
  shent = dshash_find(pgStatLocal.shared_hash, &key, true);
1066
0
  if (shent)
1067
0
  {
1068
0
    if (shent->dropped)
1069
0
    {
1070
0
      if (!missing_ok)
1071
0
        elog(ERROR,
1072
0
           "trying to drop stats entry already dropped: kind=%s dboid=%u objid=%" PRIu64 " refcount=%u generation=%u",
1073
0
           pgstat_get_kind_info(shent->key.kind)->name,
1074
0
           shent->key.dboid,
1075
0
           shent->key.objid,
1076
0
           pg_atomic_read_u32(&shent->refcount),
1077
0
           pg_atomic_read_u32(&shent->generation));
1078
0
      dshash_release_lock(pgStatLocal.shared_hash, shent);
1079
0
      return true;
1080
0
    }
1081
1082
0
    freed = pgstat_drop_entry_internal(shent, NULL);
1083
1084
    /*
1085
     * Database stats contain other stats. Drop those as well when
1086
     * dropping the database. XXX: Perhaps this should be done in a
1087
     * slightly more principled way? But not obvious what that'd look
1088
     * like, and so far this is the only case...
1089
     */
1090
0
    if (key.kind == PGSTAT_KIND_DATABASE)
1091
0
      pgstat_drop_database_and_contents(key.dboid);
1092
0
  }
1093
1094
0
  return freed;
1095
0
}
1096
1097
/*
1098
 * Scan through the shared hashtable of stats, dropping statistics if
1099
 * approved by the optional do_drop() function.
1100
 */
1101
void
1102
pgstat_drop_matching_entries(bool (*do_drop) (PgStatShared_HashEntry *, Datum),
1103
               Datum match_data)
1104
0
{
1105
0
  dshash_seq_status hstat;
1106
0
  PgStatShared_HashEntry *ps;
1107
0
  uint64    not_freed_count = 0;
1108
1109
  /* entries are removed, take an exclusive lock */
1110
0
  dshash_seq_init(&hstat, pgStatLocal.shared_hash, true);
1111
0
  while ((ps = dshash_seq_next(&hstat)) != NULL)
1112
0
  {
1113
0
    if (ps->dropped)
1114
0
      continue;
1115
1116
0
    if (do_drop != NULL && !do_drop(ps, match_data))
1117
0
      continue;
1118
1119
    /* delete local reference */
1120
0
    if (pgStatEntryRefHash)
1121
0
    {
1122
0
      PgStat_EntryRefHashEntry *lohashent =
1123
0
        pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, ps->key);
1124
1125
0
      if (lohashent)
1126
0
        pgstat_release_entry_ref(lohashent->key, lohashent->entry_ref,
1127
0
                     true);
1128
0
    }
1129
1130
0
    if (!pgstat_drop_entry_internal(ps, &hstat))
1131
0
      not_freed_count++;
1132
0
  }
1133
0
  dshash_seq_term(&hstat);
1134
1135
0
  if (not_freed_count > 0)
1136
0
    pgstat_request_entry_refs_gc();
1137
0
}
1138
1139
/*
1140
 * Scan through the shared hashtable of stats and drop all entries.
1141
 */
1142
void
1143
pgstat_drop_all_entries(void)
1144
0
{
1145
0
  pgstat_drop_matching_entries(NULL, 0);
1146
0
}
1147
1148
static void
1149
shared_stat_reset_contents(PgStat_Kind kind, PgStatShared_Common *header,
1150
               TimestampTz ts)
1151
0
{
1152
0
  const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
1153
1154
0
  memset(pgstat_get_entry_data(kind, header), 0,
1155
0
       pgstat_get_entry_len(kind));
1156
1157
0
  if (kind_info->reset_timestamp_cb)
1158
0
    kind_info->reset_timestamp_cb(header, ts);
1159
0
}
1160
1161
/*
1162
 * Reset one variable-numbered stats entry.
1163
 */
1164
void
1165
pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
1166
0
{
1167
0
  PgStat_EntryRef *entry_ref;
1168
1169
0
  Assert(!pgstat_get_kind_info(kind)->fixed_amount);
1170
1171
0
  entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
1172
0
  if (!entry_ref || entry_ref->shared_entry->dropped)
1173
0
    return;
1174
1175
0
  (void) pgstat_lock_entry(entry_ref, false);
1176
0
  shared_stat_reset_contents(kind, entry_ref->shared_stats, ts);
1177
0
  pgstat_unlock_entry(entry_ref);
1178
0
}
1179
1180
/*
1181
 * Scan through the shared hashtable of stats, resetting statistics if
1182
 * approved by the provided do_reset() function.
1183
 */
1184
void
1185
pgstat_reset_matching_entries(bool (*do_reset) (PgStatShared_HashEntry *, Datum),
1186
                Datum match_data, TimestampTz ts)
1187
0
{
1188
0
  dshash_seq_status hstat;
1189
0
  PgStatShared_HashEntry *p;
1190
1191
  /* dshash entry is not modified, take shared lock */
1192
0
  dshash_seq_init(&hstat, pgStatLocal.shared_hash, false);
1193
0
  while ((p = dshash_seq_next(&hstat)) != NULL)
1194
0
  {
1195
0
    PgStatShared_Common *header;
1196
1197
0
    if (p->dropped)
1198
0
      continue;
1199
1200
0
    if (!do_reset(p, match_data))
1201
0
      continue;
1202
1203
0
    header = dsa_get_address(pgStatLocal.dsa, p->body);
1204
1205
0
    LWLockAcquire(&header->lock, LW_EXCLUSIVE);
1206
1207
0
    shared_stat_reset_contents(p->key.kind, header, ts);
1208
1209
0
    LWLockRelease(&header->lock);
1210
0
  }
1211
0
  dshash_seq_term(&hstat);
1212
0
}
1213
1214
static bool
1215
match_kind(PgStatShared_HashEntry *p, Datum match_data)
1216
0
{
1217
0
  return p->key.kind == DatumGetInt32(match_data);
1218
0
}
1219
1220
void
1221
pgstat_reset_entries_of_kind(PgStat_Kind kind, TimestampTz ts)
1222
0
{
1223
0
  pgstat_reset_matching_entries(match_kind, Int32GetDatum(kind), ts);
1224
0
}
1225
1226
static void
1227
pgstat_setup_memcxt(void)
1228
0
{
1229
0
  if (unlikely(!pgStatSharedRefContext))
1230
0
    pgStatSharedRefContext =
1231
0
      AllocSetContextCreate(TopMemoryContext,
1232
0
                  "PgStat Shared Ref",
1233
0
                  ALLOCSET_SMALL_SIZES);
1234
0
  if (unlikely(!pgStatEntryRefHashContext))
1235
0
    pgStatEntryRefHashContext =
1236
0
      AllocSetContextCreate(TopMemoryContext,
1237
0
                  "PgStat Shared Ref Hash",
1238
0
                  ALLOCSET_SMALL_SIZES);
1239
0
}