LCOV - code coverage report
Current view: top level - src/backend/storage/ipc - shm_toc.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 55 70 78.6 %
Date: 2024-04-25 14:12:45 Functions: 6 7 85.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * shm_toc.c
       4             :  *    shared memory segment table of contents
       5             :  *
       6             :  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
       7             :  * Portions Copyright (c) 1994, Regents of the University of California
       8             :  *
       9             :  * src/backend/storage/ipc/shm_toc.c
      10             :  *
      11             :  *-------------------------------------------------------------------------
      12             :  */
      13             : 
      14             : #include "postgres.h"
      15             : 
      16             : #include "port/atomics.h"
      17             : #include "storage/shm_toc.h"
      18             : #include "storage/spin.h"
      19             : 
      20             : typedef struct shm_toc_entry
      21             : {
      22             :     uint64      key;            /* Arbitrary identifier */
      23             :     Size        offset;         /* Offset, in bytes, from TOC start */
      24             : } shm_toc_entry;
      25             : 
      26             : struct shm_toc
      27             : {
      28             :     uint64      toc_magic;      /* Magic number identifying this TOC */
      29             :     slock_t     toc_mutex;      /* Spinlock for mutual exclusion */
      30             :     Size        toc_total_bytes;    /* Bytes managed by this TOC */
      31             :     Size        toc_allocated_bytes;    /* Bytes allocated of those managed */
      32             :     uint32      toc_nentry;     /* Number of entries in TOC */
      33             :     shm_toc_entry toc_entry[FLEXIBLE_ARRAY_MEMBER];
      34             : };
      35             : 
      36             : /*
      37             :  * Initialize a region of shared memory with a table of contents.
      38             :  */
      39             : shm_toc *
      40         972 : shm_toc_create(uint64 magic, void *address, Size nbytes)
      41             : {
      42         972 :     shm_toc    *toc = (shm_toc *) address;
      43             : 
      44             :     Assert(nbytes > offsetof(shm_toc, toc_entry));
      45         972 :     toc->toc_magic = magic;
      46         972 :     SpinLockInit(&toc->toc_mutex);
      47             : 
      48             :     /*
      49             :      * The alignment code in shm_toc_allocate() assumes that the starting
      50             :      * value is buffer-aligned.
      51             :      */
      52         972 :     toc->toc_total_bytes = BUFFERALIGN_DOWN(nbytes);
      53         972 :     toc->toc_allocated_bytes = 0;
      54         972 :     toc->toc_nentry = 0;
      55             : 
      56         972 :     return toc;
      57             : }
      58             : 
      59             : /*
      60             :  * Attach to an existing table of contents.  If the magic number found at
      61             :  * the target address doesn't match our expectations, return NULL.
      62             :  */
      63             : shm_toc *
      64        5310 : shm_toc_attach(uint64 magic, void *address)
      65             : {
      66        5310 :     shm_toc    *toc = (shm_toc *) address;
      67             : 
      68        5310 :     if (toc->toc_magic != magic)
      69           0 :         return NULL;
      70             : 
      71             :     Assert(toc->toc_total_bytes >= toc->toc_allocated_bytes);
      72             :     Assert(toc->toc_total_bytes > offsetof(shm_toc, toc_entry));
      73             : 
      74        5310 :     return toc;
      75             : }
      76             : 
      77             : /*
      78             :  * Allocate shared memory from a segment managed by a table of contents.
      79             :  *
      80             :  * This is not a full-blown allocator; there's no way to free memory.  It's
      81             :  * just a way of dividing a single physical shared memory segment into logical
      82             :  * chunks that may be used for different purposes.
      83             :  *
      84             :  * We allocate backwards from the end of the segment, so that the TOC entries
      85             :  * can grow forward from the start of the segment.
      86             :  */
      87             : void *
      88       19732 : shm_toc_allocate(shm_toc *toc, Size nbytes)
      89             : {
      90       19732 :     volatile shm_toc *vtoc = toc;
      91             :     Size        total_bytes;
      92             :     Size        allocated_bytes;
      93             :     Size        nentry;
      94             :     Size        toc_bytes;
      95             : 
      96             :     /*
      97             :      * Make sure request is well-aligned.  XXX: MAXALIGN is not enough,
      98             :      * because atomic ops might need a wider alignment.  We don't have a
      99             :      * proper definition for the minimum to make atomic ops safe, but
     100             :      * BUFFERALIGN ought to be enough.
     101             :      */
     102       19732 :     nbytes = BUFFERALIGN(nbytes);
     103             : 
     104       19732 :     SpinLockAcquire(&toc->toc_mutex);
     105             : 
     106       19732 :     total_bytes = vtoc->toc_total_bytes;
     107       19732 :     allocated_bytes = vtoc->toc_allocated_bytes;
     108       19732 :     nentry = vtoc->toc_nentry;
     109       19732 :     toc_bytes = offsetof(shm_toc, toc_entry) + nentry * sizeof(shm_toc_entry)
     110       19732 :         + allocated_bytes;
     111             : 
     112             :     /* Check for memory exhaustion and overflow. */
     113       19732 :     if (toc_bytes + nbytes > total_bytes || toc_bytes + nbytes < toc_bytes)
     114             :     {
     115           0 :         SpinLockRelease(&toc->toc_mutex);
     116           0 :         ereport(ERROR,
     117             :                 (errcode(ERRCODE_OUT_OF_MEMORY),
     118             :                  errmsg("out of shared memory")));
     119             :     }
     120       19732 :     vtoc->toc_allocated_bytes += nbytes;
     121             : 
     122       19732 :     SpinLockRelease(&toc->toc_mutex);
     123             : 
     124       19732 :     return ((char *) toc) + (total_bytes - allocated_bytes - nbytes);
     125             : }
     126             : 
     127             : /*
     128             :  * Return the number of bytes that can still be allocated.
     129             :  */
     130             : Size
     131           0 : shm_toc_freespace(shm_toc *toc)
     132             : {
     133           0 :     volatile shm_toc *vtoc = toc;
     134             :     Size        total_bytes;
     135             :     Size        allocated_bytes;
     136             :     Size        nentry;
     137             :     Size        toc_bytes;
     138             : 
     139           0 :     SpinLockAcquire(&toc->toc_mutex);
     140           0 :     total_bytes = vtoc->toc_total_bytes;
     141           0 :     allocated_bytes = vtoc->toc_allocated_bytes;
     142           0 :     nentry = vtoc->toc_nentry;
     143           0 :     SpinLockRelease(&toc->toc_mutex);
     144             : 
     145           0 :     toc_bytes = offsetof(shm_toc, toc_entry) + nentry * sizeof(shm_toc_entry);
     146             :     Assert(allocated_bytes + BUFFERALIGN(toc_bytes) <= total_bytes);
     147           0 :     return total_bytes - (allocated_bytes + BUFFERALIGN(toc_bytes));
     148             : }
     149             : 
     150             : /*
     151             :  * Insert a TOC entry.
     152             :  *
     153             :  * The idea here is that the process setting up the shared memory segment will
     154             :  * register the addresses of data structures within the segment using this
     155             :  * function.  Each data structure will be identified using a 64-bit key, which
     156             :  * is assumed to be a well-known or discoverable integer.  Other processes
     157             :  * accessing the shared memory segment can pass the same key to
     158             :  * shm_toc_lookup() to discover the addresses of those data structures.
     159             :  *
     160             :  * Since the shared memory segment may be mapped at different addresses within
     161             :  * different backends, we store relative rather than absolute pointers.
     162             :  *
     163             :  * This won't scale well to a large number of keys.  Hopefully, that isn't
     164             :  * necessary; if it proves to be, we might need to provide a more sophisticated
     165             :  * data structure here.  But the real idea here is just to give someone mapping
     166             :  * a dynamic shared memory the ability to find the bare minimum number of
     167             :  * pointers that they need to bootstrap.  If you're storing a lot of stuff in
     168             :  * the TOC, you're doing it wrong.
     169             :  */
     170             : void
     171       19732 : shm_toc_insert(shm_toc *toc, uint64 key, void *address)
     172             : {
     173       19732 :     volatile shm_toc *vtoc = toc;
     174             :     Size        total_bytes;
     175             :     Size        allocated_bytes;
     176             :     Size        nentry;
     177             :     Size        toc_bytes;
     178             :     Size        offset;
     179             : 
     180             :     /* Relativize pointer. */
     181             :     Assert(address > (void *) toc);
     182       19732 :     offset = ((char *) address) - (char *) toc;
     183             : 
     184       19732 :     SpinLockAcquire(&toc->toc_mutex);
     185             : 
     186       19732 :     total_bytes = vtoc->toc_total_bytes;
     187       19732 :     allocated_bytes = vtoc->toc_allocated_bytes;
     188       19732 :     nentry = vtoc->toc_nentry;
     189       19732 :     toc_bytes = offsetof(shm_toc, toc_entry) + nentry * sizeof(shm_toc_entry)
     190       19732 :         + allocated_bytes;
     191             : 
     192             :     /* Check for memory exhaustion and overflow. */
     193       19732 :     if (toc_bytes + sizeof(shm_toc_entry) > total_bytes ||
     194       19732 :         toc_bytes + sizeof(shm_toc_entry) < toc_bytes ||
     195             :         nentry >= PG_UINT32_MAX)
     196             :     {
     197           0 :         SpinLockRelease(&toc->toc_mutex);
     198           0 :         ereport(ERROR,
     199             :                 (errcode(ERRCODE_OUT_OF_MEMORY),
     200             :                  errmsg("out of shared memory")));
     201             :     }
     202             : 
     203             :     Assert(offset < total_bytes);
     204       19732 :     vtoc->toc_entry[nentry].key = key;
     205       19732 :     vtoc->toc_entry[nentry].offset = offset;
     206             : 
     207             :     /*
     208             :      * By placing a write barrier after filling in the entry and before
     209             :      * updating the number of entries, we make it safe to read the TOC
     210             :      * unlocked.
     211             :      */
     212       19732 :     pg_write_barrier();
     213             : 
     214       19732 :     vtoc->toc_nentry++;
     215             : 
     216       19732 :     SpinLockRelease(&toc->toc_mutex);
     217       19732 : }
     218             : 
     219             : /*
     220             :  * Look up a TOC entry.
     221             :  *
     222             :  * If the key is not found, returns NULL if noError is true, otherwise
     223             :  * throws elog(ERROR).
     224             :  *
     225             :  * Unlike the other functions in this file, this operation acquires no lock;
     226             :  * it uses only barriers.  It probably wouldn't hurt concurrency very much even
     227             :  * if it did get a lock, but since it's reasonably likely that a group of
     228             :  * worker processes could each read a series of entries from the same TOC
     229             :  * right around the same time, there seems to be some value in avoiding it.
     230             :  */
     231             : void *
     232       79060 : shm_toc_lookup(shm_toc *toc, uint64 key, bool noError)
     233             : {
     234             :     uint32      nentry;
     235             :     uint32      i;
     236             : 
     237             :     /*
     238             :      * Read the number of entries before we examine any entry.  We assume that
     239             :      * reading a uint32 is atomic.
     240             :      */
     241       79060 :     nentry = toc->toc_nentry;
     242       79060 :     pg_read_barrier();
     243             : 
     244             :     /* Now search for a matching entry. */
     245     1017930 :     for (i = 0; i < nentry; ++i)
     246             :     {
     247     1009532 :         if (toc->toc_entry[i].key == key)
     248       70662 :             return ((char *) toc) + toc->toc_entry[i].offset;
     249             :     }
     250             : 
     251             :     /* No matching entry was found. */
     252        8398 :     if (!noError)
     253           0 :         elog(ERROR, "could not find key " UINT64_FORMAT " in shm TOC at %p",
     254             :              key, toc);
     255        8398 :     return NULL;
     256             : }
     257             : 
     258             : /*
     259             :  * Estimate how much shared memory will be required to store a TOC and its
     260             :  * dependent data structures.
     261             :  */
     262             : Size
     263        1002 : shm_toc_estimate(shm_toc_estimator *e)
     264             : {
     265             :     Size        sz;
     266             : 
     267        1002 :     sz = offsetof(shm_toc, toc_entry);
     268        1002 :     sz = add_size(sz, mul_size(e->number_of_keys, sizeof(shm_toc_entry)));
     269        1002 :     sz = add_size(sz, e->space_for_chunks);
     270             : 
     271        1002 :     return BUFFERALIGN(sz);
     272             : }

Generated by: LCOV version 1.14