slock_t shmem_lock;
HASHHDR *index; /* location of ShmemIndex */
+ size_t index_size; /* size of shmem region holding ShmemIndex */
LWLock index_lock; /* protects ShmemIndex */
} ShmemAllocatorData;
#define ShmemIndexLock (&ShmemAllocator->index_lock)
+static HTAB *shmem_hash_create(void *location, size_t size, bool found,
+ const char *name, int64 nelems, HASHCTL *infoP, int hash_flags);
static void *ShmemHashAlloc(Size size, void *alloc_arg);
static void *ShmemAllocRaw(Size size, Size *allocated_size);
Datum pg_numa_available(PG_FUNCTION_ARGS);
+/*
+ * A very simple allocator used to carve out different parts of a hash table
+ * from a previously allocated contiguous shared memory area.
+ */
+typedef struct shmem_hash_allocator
+{
+ char *next; /* start of free space in the area */
+ char *end; /* end of the shmem area */
+} shmem_hash_allocator;
+
/*
* InitShmemAllocator() --- set up basic pointers to shared memory.
*
Size offset;
HASHCTL info;
int hash_flags;
- size_t size = 0;
#ifndef EXEC_BACKEND
Assert(!IsUnderPostmaster);
*/
info.keysize = SHMEM_INDEX_KEYSIZE;
info.entrysize = sizeof(ShmemIndexEnt);
- info.dsize = info.max_dsize = hash_select_dirsize(SHMEM_INDEX_SIZE);
- info.alloc = ShmemHashAlloc;
- info.alloc_arg = NULL;
- hash_flags = HASH_ELEM | HASH_STRINGS | HASH_SHARED_MEM | HASH_ALLOC | HASH_DIRSIZE | HASH_FIXED_SIZE;
+ hash_flags = HASH_ELEM | HASH_STRINGS | HASH_FIXED_SIZE;
+
if (!IsUnderPostmaster)
{
- size = hash_get_shared_size(&info, hash_flags);
- ShmemAllocator->index = (HASHHDR *) ShmemAlloc(size);
+ ShmemAllocator->index_size = hash_estimate_size(SHMEM_INDEX_SIZE, info.entrysize);
+ ShmemAllocator->index = (HASHHDR *) ShmemAlloc(ShmemAllocator->index_size);
}
- else
- hash_flags |= HASH_ATTACH;
- info.hctl = ShmemAllocator->index;
- ShmemIndex = hash_create("ShmemIndex", SHMEM_INDEX_SIZE, &info, hash_flags);
+ ShmemIndex = shmem_hash_create(ShmemAllocator->index,
+ ShmemAllocator->index_size,
+ IsUnderPostmaster,
+ "ShmemIndex", SHMEM_INDEX_SIZE,
+ &info, hash_flags);
Assert(ShmemIndex != NULL);
/*
hash_search(ShmemIndex, "ShmemIndex", HASH_ENTER, &found);
Assert(!found);
- result->size = size;
- result->allocated_size = size;
+ result->size = ShmemAllocator->index_size;
+ result->allocated_size = ShmemAllocator->index_size;
result->location = ShmemAllocator->index;
}
}
return ShmemAllocRaw(size, &allocated_size);
}
-/* Alloc callback for shared memory hash tables */
+/*
+ * ShmemHashAlloc -- alloc callback for shared memory hash tables
+ *
+ * Carve out the allocation from a pre-allocated region. All shared memory
+ * hash tables are initialized with HASH_FIXED_SIZE, so all the allocations
+ * happen upfront during initialization and no locking is required.
+ */
static void *
ShmemHashAlloc(Size size, void *alloc_arg)
{
- Size allocated_size;
+ shmem_hash_allocator *allocator = (shmem_hash_allocator *) alloc_arg;
+ void *result;
- return ShmemAllocRaw(size, &allocated_size);
+ size = MAXALIGN(size);
+
+ if (allocator->end - allocator->next < size)
+ return NULL;
+ result = allocator->next;
+ allocator->next += size;
+
+ return result;
}
/*
int hash_flags) /* info about infoP */
{
bool found;
+ size_t size;
void *location;
+ size = hash_estimate_size(nelems, infoP->entrysize);
+
+ /* look it up in the shmem index or allocate */
+ location = ShmemInitStruct(name, size, &found);
+
+ return shmem_hash_create(location, size, found,
+ name, nelems, infoP, hash_flags);
+}
+
+/*
+ * Initialize or attach to a shared hash table in the given shmem region.
+ *
+ * This is extracted from ShmemInitHash() to allow InitShmemAllocator() to
+ * share the logic for bootstrapping the ShmemIndex hash table.
+ */
+static HTAB *
+shmem_hash_create(void *location, size_t size, bool found,
+ const char *name, int64 nelems, HASHCTL *infoP, int hash_flags)
+{
+ shmem_hash_allocator allocator;
+
/*
- * Hash tables allocated in shared memory have a fixed directory; it can't
- * grow or other backends wouldn't be able to find it. So, make sure we
- * make it big enough to start with. We also allocate all the buckets
- * upfront.
+ * Hash tables allocated in shared memory have a fixed directory and have
+ * all elements allocated upfront. We don't support growing because we'd
+ * need to grow the underlying shmem region with it.
*
* The shared memory allocator must be specified too.
*/
infoP->alloc_arg = NULL;
hash_flags |= HASH_SHARED_MEM | HASH_ALLOC | HASH_DIRSIZE | HASH_FIXED_SIZE;
- /* look it up in the shmem index */
- location = ShmemInitStruct(name,
- hash_get_shared_size(infoP, hash_flags),
- &found);
-
/*
* if it already exists, attach to it rather than allocate and initialize
* new space
*/
- if (found)
+ if (!found)
+ {
+ allocator.next = (char *) location;
+ allocator.end = (char *) location + size;
+ infoP->alloc_arg = &allocator;
+ }
+ else
+ {
+ /* Pass location of hashtable header to hash_create */
+ infoP->hctl = (HASHHDR *) location;
hash_flags |= HASH_ATTACH;
-
- /* Pass location of hashtable header to hash_create */
- infoP->hctl = (HASHHDR *) location;
+ }
return hash_create(name, nelems, infoP, hash_flags);
}
int nelem_alloc; /* number of entries to allocate at once */
bool isfixed; /* if true, don't enlarge */
+ /* Current directory. In shared tables, this doesn't change */
+ HASHSEGMENT *dir;
+
#ifdef HASH_STATISTICS
/*
* hash_destroy very simple. The memory context is made a child of either
* a context specified by the caller, or TopMemoryContext if nothing is
* specified.
+ *
+ * Note that HASH_DIRSIZE and HASH_ALLOC had better be set as well.
*/
if (flags & HASH_SHARED_MEM)
{
if (flags & HASH_SHARED_MEM)
{
- /*
- * ctl structure and directory are preallocated for shared memory
- * tables. Note that HASH_DIRSIZE and HASH_ALLOC had better be set as
- * well.
- */
- hashp->hctl = info->hctl;
- hashp->dir = (HASHSEGMENT *) (((char *) info->hctl) + sizeof(HASHHDR));
hashp->hcxt = NULL;
hashp->isshared = true;
/* hash table already exists, we're just attaching to it */
if (flags & HASH_ATTACH)
{
+ /* Caller must pass the pointer to the shared header */
+ Assert(info->hctl);
+ hashp->hctl = info->hctl;
+
/* make local copies of some heavily-used values */
- hctl = hashp->hctl;
- hashp->keysize = hctl->keysize;
+ hashp->dir = info->hctl->dir;
+ hashp->keysize = info->hctl->keysize;
return hashp;
}
hashp->isshared = false;
}
+ /*
+ * Allocate the header structure.
+ *
+ * XXX: In case of a shared memory hash table, other processes need the
+ * pointer to the header to re-find the hash table. There is currently no
+ * explicit way to pass it back from here, the caller relies on the fact
+ * that this is the first allocation made with the alloc function. That's
+ * a little ugly, but works for now.
+ */
+ hashp->hctl = (HASHHDR *) hashp->alloc(sizeof(HASHHDR), hashp->alloc_arg);
if (!hashp->hctl)
- {
- hashp->hctl = (HASHHDR *) hashp->alloc(sizeof(HASHHDR), hashp->alloc_arg);
- if (!hashp->hctl)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("out of memory")));
- }
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
hashp->frozen = false;
nsegs = next_pow2_int(nsegs);
/*
- * Make sure directory is big enough. If pre-allocated directory is too
- * small, choke (caller screwed up).
+ * Make sure directory is big enough.
*/
if (nsegs > hctl->dsize)
- {
- if (!(hashp->dir))
- hctl->dsize = nsegs;
- else
- return false;
- }
+ hctl->dsize = nsegs;
/* Allocate a directory */
- if (!(hashp->dir))
- {
- hashp->dir = (HASHSEGMENT *)
- hashp->alloc(hctl->dsize * sizeof(HASHSEGMENT), hashp->alloc_arg);
- if (!hashp->dir)
- return false;
- }
+ hctl->dir = (HASHSEGMENT *)
+ hashp->alloc(hctl->dsize * sizeof(HASHSEGMENT), hashp->alloc_arg);
+ if (!hctl->dir)
+ return false;
+ hashp->dir = hctl->dir;
/* Allocate initial segments */
for (segp = hashp->dir; hctl->nsegs < nsegs; hctl->nsegs++, segp++)
return nDirEntries;
}
-/*
- * Compute the required initial memory allocation for a shared-memory
- * hashtable with the given parameters. We need space for the HASHHDR
- * and for the (non expansible) directory.
- */
-Size
-hash_get_shared_size(HASHCTL *info, int flags)
-{
- Assert(flags & HASH_DIRSIZE);
- Assert(info->dsize == info->max_dsize);
- return sizeof(HASHHDR) + info->dsize * sizeof(HASHSEGMENT);
-}
-
/********************** DESTROY ROUTINES ************************/
{
memcpy(p, old_p, old_dirsize);
MemSet(((char *) p) + old_dirsize, 0, new_dirsize - old_dirsize);
+ hashp->hctl->dir = p;
hashp->dir = p;
hashp->hctl->dsize = new_dsize;