]> granicus.if.org Git - postgresql/blob - src/backend/storage/ipc/shmem.c
Ensure that all places that are complaining about exhaustion of shared
[postgresql] / src / backend / storage / ipc / shmem.c
1 /*-------------------------------------------------------------------------
2  *
3  * shmem.c
4  *        create shared memory and initialize shared memory data structures.
5  *
6  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        $Header: /cvsroot/pgsql/src/backend/storage/ipc/shmem.c,v 1.73 2003/10/16 20:59:35 tgl Exp $
12  *
13  *-------------------------------------------------------------------------
14  */
15 /*
16  * POSTGRES processes share one or more regions of shared memory.
17  * The shared memory is created by a postmaster and is inherited
18  * by each backend via fork().  The routines in this file are used for
19  * allocating and binding to shared memory data structures.
20  *
21  * NOTES:
22  *              (a) There are three kinds of shared memory data structures
23  *      available to POSTGRES: fixed-size structures, queues and hash
24  *      tables.  Fixed-size structures contain things like global variables
25  *      for a module and should never be allocated after the process
26  *      initialization phase.  Hash tables have a fixed maximum size, but
27  *      their actual size can vary dynamically.  When entries are added
28  *      to the table, more space is allocated.  Queues link data structures
29  *      that have been allocated either as fixed size structures or as hash
30  *      buckets.  Each shared data structure has a string name to identify
31  *      it (assigned in the module that declares it).
32  *
33  *              (b) During initialization, each module looks for its
34  *      shared data structures in a hash table called the "Shmem Index".
35  *      If the data structure is not present, the caller can allocate
36  *      a new one and initialize it.  If the data structure is present,
37  *      the caller "attaches" to the structure by initializing a pointer
38  *      in the local address space.
39  *              The shmem index has two purposes: first, it gives us
40  *      a simple model of how the world looks when a backend process
41  *      initializes.  If something is present in the shmem index,
42  *      it is initialized.      If it is not, it is uninitialized.      Second,
43  *      the shmem index allows us to allocate shared memory on demand
44  *      instead of trying to preallocate structures and hard-wire the
45  *      sizes and locations in header files.  If you are using a lot
46  *      of shared memory in a lot of different places (and changing
47  *      things during development), this is important.
48  *
49  *              (c) memory allocation model: shared memory can never be
50  *      freed, once allocated.   Each hash table has its own free list,
51  *      so hash buckets can be reused when an item is deleted.  However,
52  *      if one hash table grows very large and then shrinks, its space
53  *      cannot be redistributed to other tables.  We could build a simple
54  *      hash bucket garbage collector if need be.  Right now, it seems
55  *      unnecessary.
56  *
57  *              See InitSem() in sem.c for an example of how to use the
58  *      shmem index.
59  */
60
61 #include "postgres.h"
62
63 #include "access/transam.h"
64 #include "storage/pg_shmem.h"
65 #include "storage/spin.h"
66 #include "utils/tqual.h"
67
68
69 /* shared memory global variables */
70
71 static PGShmemHeader *ShmemSegHdr;              /* shared mem segment header */
72
73 SHMEM_OFFSET ShmemBase;                 /* start address of shared memory */
74
75 static SHMEM_OFFSET ShmemEnd;   /* end+1 address of shared memory */
76
77 static slock_t *ShmemLock;              /* spinlock for shared memory allocation */
78
79 static HTAB *ShmemIndex = NULL; /* primary index hashtable for shmem */
80
81 static bool ShmemBootstrap = false;             /* bootstrapping shmem index? */
82
83
84 /*
85  *      InitShmemAllocation() --- set up shared-memory allocation.
86  *
87  * Note: the argument should be declared "PGShmemHeader *seghdr",
88  * but we use void to avoid having to include ipc.h in shmem.h.
89  */
90 void
91 InitShmemAllocation(void *seghdr)
92 {
93         PGShmemHeader *shmhdr = (PGShmemHeader *) seghdr;
94
95         /* Set up basic pointers to shared memory */
96         ShmemSegHdr = shmhdr;
97         ShmemBase = (SHMEM_OFFSET) shmhdr;
98         ShmemEnd = ShmemBase + shmhdr->totalsize;
99
100         /*
101          * Initialize the spinlock used by ShmemAlloc.  We have to do the
102          * space allocation the hard way, since ShmemAlloc can't be called
103          * yet.
104          */
105         ShmemLock = (slock_t *) (((char *) shmhdr) + shmhdr->freeoffset);
106         shmhdr->freeoffset += MAXALIGN(sizeof(slock_t));
107         Assert(shmhdr->freeoffset <= shmhdr->totalsize);
108
109         SpinLockInit(ShmemLock);
110
111         /* ShmemIndex can't be set up yet (need LWLocks first) */
112         ShmemIndex = (HTAB *) NULL;
113
114         /*
115          * Initialize ShmemVariableCache for transaction manager.
116          */
117         ShmemVariableCache = (VariableCache)
118                 ShmemAlloc(sizeof(*ShmemVariableCache));
119         memset(ShmemVariableCache, 0, sizeof(*ShmemVariableCache));
120 }
121
122 /*
123  * ShmemAlloc -- allocate max-aligned chunk from shared memory
124  *
125  * Assumes ShmemLock and ShmemSegHdr are initialized.
126  *
127  * Returns: real pointer to memory or NULL if we are out
128  *              of space.  Has to return a real pointer in order
129  *              to be compatible with malloc().
130  */
131 void *
132 ShmemAlloc(Size size)
133 {
134         uint32          newStart;
135         uint32          newFree;
136         void       *newSpace;
137
138         /* use volatile pointer to prevent code rearrangement */
139         volatile PGShmemHeader *shmemseghdr = ShmemSegHdr;
140
141         /*
142          * ensure all space is adequately aligned.
143          */
144         size = MAXALIGN(size);
145
146         Assert(shmemseghdr != NULL);
147
148         SpinLockAcquire(ShmemLock);
149
150         newStart = shmemseghdr->freeoffset;
151
152         /* extra alignment for large requests, since they are probably buffers */
153         if (size >= BLCKSZ)
154                 newStart = BUFFERALIGN(newStart);
155
156         newFree = newStart + size;
157         if (newFree <= shmemseghdr->totalsize)
158         {
159                 newSpace = (void *) MAKE_PTR(newStart);
160                 shmemseghdr->freeoffset = newFree;
161         }
162         else
163                 newSpace = NULL;
164
165         SpinLockRelease(ShmemLock);
166
167         if (!newSpace)
168                 ereport(WARNING,
169                                 (errcode(ERRCODE_OUT_OF_MEMORY),
170                                  errmsg("out of shared memory")));
171
172         return newSpace;
173 }
174
175 /*
176  * ShmemIsValid -- test if an offset refers to valid shared memory
177  *
178  * Returns TRUE if the pointer is valid.
179  */
180 bool
181 ShmemIsValid(unsigned long addr)
182 {
183         return (addr < ShmemEnd) && (addr >= ShmemBase);
184 }
185
186 /*
187  *      InitShmemIndex() --- set up shmem index table.
188  */
189 void
190 InitShmemIndex(void)
191 {
192         HASHCTL         info;
193         int                     hash_flags;
194         ShmemIndexEnt *result,
195                                 item;
196         bool            found;
197
198         /*
199          * Since ShmemInitHash calls ShmemInitStruct, which expects the
200          * ShmemIndex hashtable to exist already, we have a bit of a
201          * circularity problem in initializing the ShmemIndex itself.  We set
202          * ShmemBootstrap to tell ShmemInitStruct to fake it.
203          */
204         ShmemBootstrap = true;
205
206         /* create the shared memory shmem index */
207         info.keysize = SHMEM_INDEX_KEYSIZE;
208         info.entrysize = sizeof(ShmemIndexEnt);
209         hash_flags = HASH_ELEM;
210
211         /* This will acquire the shmem index lock, but not release it. */
212         ShmemIndex = ShmemInitHash("ShmemIndex",
213                                                            SHMEM_INDEX_SIZE, SHMEM_INDEX_SIZE,
214                                                            &info, hash_flags);
215         if (!ShmemIndex)
216                 elog(FATAL, "could not initialize Shmem Index");
217
218         /*
219          * Now, create an entry in the hashtable for the index itself.
220          */
221         MemSet(item.key, 0, SHMEM_INDEX_KEYSIZE);
222         strncpy(item.key, "ShmemIndex", SHMEM_INDEX_KEYSIZE);
223
224         result = (ShmemIndexEnt *)
225                 hash_search(ShmemIndex, (void *) &item, HASH_ENTER, &found);
226         if (!result)
227                 ereport(FATAL,
228                                 (errcode(ERRCODE_OUT_OF_MEMORY),
229                                  errmsg("out of shared memory")));
230
231         Assert(ShmemBootstrap && !found);
232
233         result->location = MAKE_OFFSET(ShmemIndex->hctl);
234         result->size = SHMEM_INDEX_SIZE;
235
236         ShmemBootstrap = false;
237
238         /* now release the lock acquired in ShmemInitStruct */
239         LWLockRelease(ShmemIndexLock);
240 }
241
242 /*
243  * ShmemInitHash -- Create/Attach to and initialize
244  *              shared memory hash table.
245  *
246  * Notes:
247  *
248  * assume caller is doing some kind of synchronization
249  * so that two people dont try to create/initialize the
250  * table at once.
251  */
252 HTAB *
253 ShmemInitHash(const char *name, /* table string name for shmem index */
254                           long init_size,       /* initial table size */
255                           long max_size,        /* max size of the table */
256                           HASHCTL *infoP,       /* info about key and bucket size */
257                           int hash_flags)       /* info about infoP */
258 {
259         bool            found;
260         void       *location;
261
262         /*
263          * Hash tables allocated in shared memory have a fixed directory; it
264          * can't grow or other backends wouldn't be able to find it. So, make
265          * sure we make it big enough to start with.
266          *
267          * The shared memory allocator must be specified too.
268          */
269         infoP->dsize = infoP->max_dsize = hash_select_dirsize(max_size);
270         infoP->alloc = ShmemAlloc;
271         hash_flags |= HASH_SHARED_MEM | HASH_DIRSIZE;
272
273         /* look it up in the shmem index */
274         location = ShmemInitStruct(name,
275                                         sizeof(HASHHDR) + infoP->dsize * sizeof(HASHSEGMENT),
276                                                            &found);
277
278         /*
279          * shmem index is corrupted.    Let someone else give the error
280          * message since they have more information
281          */
282         if (location == NULL)
283                 return NULL;
284
285         /*
286          * if it already exists, attach to it rather than allocate and
287          * initialize new space
288          */
289         if (found)
290                 hash_flags |= HASH_ATTACH;
291
292         /* Now provide the header and directory pointers */
293         infoP->hctl = (HASHHDR *) location;
294         infoP->dir = (HASHSEGMENT *) (((char *) location) + sizeof(HASHHDR));
295
296         return hash_create(name, init_size, infoP, hash_flags);
297 }
298
299 /*
300  * ShmemInitStruct -- Create/attach to a structure in shared
301  *              memory.
302  *
303  *      This is called during initialization to find or allocate
304  *              a data structure in shared memory.      If no other processes
305  *              have created the structure, this routine allocates space
306  *              for it.  If it exists already, a pointer to the existing
307  *              table is returned.
308  *
309  *      Returns: real pointer to the object.  FoundPtr is TRUE if
310  *              the object is already in the shmem index (hence, already
311  *              initialized).
312  */
313 void *
314 ShmemInitStruct(const char *name, Size size, bool *foundPtr)
315 {
316         ShmemIndexEnt *result,
317                                 item;
318         void       *structPtr;
319
320         strncpy(item.key, name, SHMEM_INDEX_KEYSIZE);
321         item.location = BAD_LOCATION;
322
323         LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE);
324
325         if (!ShmemIndex)
326         {
327                 /*
328                  * If the shmem index doesn't exist, we are bootstrapping: we must
329                  * be trying to init the shmem index itself.
330                  *
331                  * Notice that the ShmemIndexLock is held until the shmem index has
332                  * been completely initialized.
333                  */
334                 Assert(strcmp(name, "ShmemIndex") == 0);
335                 Assert(ShmemBootstrap);
336                 *foundPtr = FALSE;
337                 return ShmemAlloc(size);
338         }
339
340         /* look it up in the shmem index */
341         result = (ShmemIndexEnt *)
342                 hash_search(ShmemIndex, (void *) &item, HASH_ENTER, foundPtr);
343
344         if (!result)
345         {
346                 LWLockRelease(ShmemIndexLock);
347                 ereport(ERROR,
348                                 (errcode(ERRCODE_OUT_OF_MEMORY),
349                                  errmsg("out of shared memory")));
350                 return NULL;
351         }
352
353         if (*foundPtr)
354         {
355                 /*
356                  * Structure is in the shmem index so someone else has allocated
357                  * it already.  The size better be the same as the size we are
358                  * trying to initialize to or there is a name conflict (or worse).
359                  */
360                 if (result->size != size)
361                 {
362                         LWLockRelease(ShmemIndexLock);
363
364                         elog(WARNING, "ShmemIndex entry size is wrong");
365                         /* let caller print its message too */
366                         return NULL;
367                 }
368                 structPtr = (void *) MAKE_PTR(result->location);
369         }
370         else
371         {
372                 /* It isn't in the table yet. allocate and initialize it */
373                 structPtr = ShmemAlloc(size);
374                 if (!structPtr)
375                 {
376                         /* out of memory */
377                         Assert(ShmemIndex);
378                         hash_search(ShmemIndex, (void *) &item, HASH_REMOVE, NULL);
379                         LWLockRelease(ShmemIndexLock);
380
381                         ereport(WARNING,
382                                         (errcode(ERRCODE_OUT_OF_MEMORY),
383                                          errmsg("could not allocate shared memory segment \"%s\"", name)));
384                         *foundPtr = FALSE;
385                         return NULL;
386                 }
387                 result->size = size;
388                 result->location = MAKE_OFFSET(structPtr);
389         }
390         Assert(ShmemIsValid((unsigned long) structPtr));
391
392         LWLockRelease(ShmemIndexLock);
393         return structPtr;
394 }