blob: 618fa5717a279028d6d30ad0bb8ee336fd14b7c9 [file] [log] [blame]
/*-------------------------------------------------------------------------
*
* win32_shmem.c
* Implement shared memory using win32 facilities
*
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/port/win32_shmem.c,v 1.11.2.1 2009/08/11 11:51:22 mha Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
unsigned long UsedShmemSegID = 0;
void *UsedShmemSegAddr = NULL;
static Size UsedShmemSegSize = 0;
static void pgwin32_SharedMemoryDelete(int status, Datum shmId);
/*
* Generate shared memory segment name. Expand the data directory, to generate
* an identifier unique for this data directory. Then replace all backslashes
* with forward slashes, since backslashes aren't permitted in global object names.
*
* Store the shared memory segment in the Global\ namespace (requires NT2 TSE or
* 2000, but that's all we support for other reasons as well), to make sure you can't
* open two postmasters in different sessions against the same data directory.
*
* XXX: What happens with junctions? It's only someone breaking things on purpose,
* and this is still better than before, but we might want to do something about
* that sometime in the future.
*/
static char *
GetSharedMemName(void)
{
char *retptr;
DWORD bufsize;
DWORD r;
char *cp;
bufsize = GetFullPathName(DataDir, 0, NULL, NULL);
if (bufsize == 0)
elog(FATAL, "could not get size for full pathname of datadir %s: %lu",
DataDir, GetLastError());
retptr = malloc(bufsize + 18); /* 18 for Global\PostgreSQL: */
if (retptr == NULL)
elog(FATAL, "could not allocate memory for shared memory name");
strcpy(retptr, "Global\\PostgreSQL:");
r = GetFullPathName(DataDir, bufsize, retptr + 18, NULL);
if (r == 0 || r > bufsize)
elog(FATAL, "could not generate full pathname for datadir %s: %lu",
DataDir, GetLastError());
/*
* XXX: Intentionally overwriting the Global\ part here. This was not the
* original approach, but putting it in the actual Global\ namespace
* causes permission errors in a lot of cases, so we leave it in the
* default namespace for now.
*/
for (cp = retptr; *cp; cp++)
if (*cp == '\\')
*cp = '/';
return retptr;
}
/*
* PGSharedMemoryIsInUse
*
* Is a previously-existing shmem segment still existing and in use?
*
* The point of this exercise is to detect the case where a prior postmaster
* crashed, but it left child backends that are still running. Therefore
* we only care about shmem segments that are associated with the intended
* DataDir. This is an important consideration since accidental matches of
* shmem segment IDs are reasonably common.
*
*/
bool
PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2)
{
char *szShareMem;
HANDLE hmap;
szShareMem = GetSharedMemName();
hmap = OpenFileMapping(FILE_MAP_READ, FALSE, szShareMem);
free(szShareMem);
if (hmap == NULL)
return false;
CloseHandle(hmap);
return true;
}
/*
* PGSharedMemoryCreate
*
* Create a shared memory segment of the given size and initialize its
* standard header.
*
* makePrivate means to always create a new segment, rather than attach to
* or recycle any existing segment. On win32, we always create a new segment,
* since there is no need for recycling (segments go away automatically
* when the last backend exits)
*
*/
PGShmemHeader *
PGSharedMemoryCreate(Size size, bool makePrivate, int port)
{
void *memAddress;
PGShmemHeader *hdr;
HANDLE hmap,
hmap2;
char *szShareMem;
int i;
/* Room for a header? */
Assert(size > MAXALIGN(sizeof(PGShmemHeader)));
szShareMem = GetSharedMemName();
UsedShmemSegAddr = NULL;
/*
* When recycling a shared memory segment, it may take a short while
* before it gets dropped from the global namespace. So re-try after
* sleeping for a second, and continue retrying 10 times. (both the 1
* second time and the 10 retries are completely arbitrary)
*/
for (i = 0; i < 10; i++)
{
/*
* In case CreateFileMapping() doesn't set the error code to 0 on
* success
*/
SetLastError(0);
hmap = CreateFileMapping((HANDLE) 0xFFFFFFFF, /* Use the pagefile */
NULL, /* Default security attrs */
PAGE_READWRITE, /* Memory is Read/Write */
0L, /* Size Upper 32 Bits */
(DWORD) size, /* Size Lower 32 bits */
szShareMem);
if (!hmap)
ereport(FATAL,
(errmsg("could not create shared memory segment: %lu", GetLastError()),
errdetail("Failed system call was CreateFileMapping(size=%lu, name=%s).",
(unsigned long) size, szShareMem)));
/*
* If the segment already existed, CreateFileMapping() will return a
* handle to the existing one and set ERROR_ALREADY_EXISTS.
*/
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
CloseHandle(hmap); /* Close the handle, since we got a valid one
* to the previous segment. */
hmap = NULL;
Sleep(1000);
continue;
}
break;
}
/*
* If the last call in the loop still returned ERROR_ALREADY_EXISTS, this
* shared memory segment exists and we assume it belongs to somebody else.
*/
if (!hmap)
ereport(FATAL,
(errmsg("pre-existing shared memory block is still in use"),
errhint("Check if there are any old server processes still running, and terminate them.")));
free(szShareMem);
/*
* Make the handle inheritable
*/
if (!DuplicateHandle(GetCurrentProcess(), hmap, GetCurrentProcess(), &hmap2, 0, TRUE, DUPLICATE_SAME_ACCESS))
ereport(FATAL,
(errmsg("could not create shared memory segment: %lu", GetLastError()),
errdetail("Failed system call was DuplicateHandle.")));
/*
* Close the old, non-inheritable handle. If this fails we don't really
* care.
*/
if (!CloseHandle(hmap))
elog(LOG, "could not close handle to shared memory: %lu", GetLastError());
/* Register on-exit routine to delete the new segment */
on_shmem_exit(pgwin32_SharedMemoryDelete, Int32GetDatum((unsigned long) hmap2));
/*
* Get a pointer to the new shared memory segment. Map the whole segment
* at once, and let the system decide on the initial address.
*/
memAddress = MapViewOfFileEx(hmap2, FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, 0, NULL);
if (!memAddress)
ereport(FATAL,
(errmsg("could not create shared memory segment: %lu", GetLastError()),
errdetail("Failed system call was MapViewOfFileEx.")));
/*
* OK, we created a new segment. Mark it as created by this process. The
* order of assignments here is critical so that another Postgres process
* can't see the header as valid but belonging to an invalid PID!
*/
hdr = (PGShmemHeader *) memAddress;
hdr->creatorPID = getpid();
hdr->magic = PGShmemMagic;
/*
* Initialize space allocation status for segment.
*/
hdr->totalsize = size;
hdr->freeoffset = MAXALIGN(sizeof(PGShmemHeader));
/* Save info for possible future use */
UsedShmemSegAddr = memAddress;
UsedShmemSegSize = size;
UsedShmemSegID = (unsigned long) hmap2;
return hdr;
}
/*
* PGSharedMemoryReAttach
*
* Re-attach to an already existing shared memory segment. Use the
* handle inherited from the postmaster.
*
* UsedShmemSegID and UsedShmemSegAddr are implicit parameters to this
* routine. The caller must have already restored them to the postmaster's
* values.
*/
void
PGSharedMemoryReAttach(void)
{
PGShmemHeader *hdr;
void *origUsedShmemSegAddr = UsedShmemSegAddr;
Assert(UsedShmemSegAddr != NULL);
Assert(IsUnderPostmaster);
/*
* Release memory region reservation that was made by the postmaster
*/
if (VirtualFree(UsedShmemSegAddr, 0, MEM_RELEASE) == 0)
elog(FATAL, "failed to release reserved memory region (addr=%p): %lu",
UsedShmemSegAddr, GetLastError());
hdr = (PGShmemHeader *) MapViewOfFileEx((HANDLE) UsedShmemSegID, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0, UsedShmemSegAddr);
if (!hdr)
elog(FATAL, "could not reattach to shared memory (key=%d, addr=%p): %lu",
(int) UsedShmemSegID, UsedShmemSegAddr, GetLastError());
if (hdr != origUsedShmemSegAddr)
elog(FATAL, "reattaching to shared memory returned unexpected address (got %p, expected %p)",
hdr, origUsedShmemSegAddr);
if (hdr->magic != PGShmemMagic)
elog(FATAL, "reattaching to shared memory returned non-PostgreSQL memory");
UsedShmemSegAddr = hdr; /* probably redundant */
}
/*
* PGSharedMemoryDetach
*
* Detach from the shared memory segment, if still attached. This is not
* intended for use by the process that originally created the segment. Rather,
* this is for subprocesses that have inherited an attachment and want to
* get rid of it.
*/
void
PGSharedMemoryDetach(void)
{
if (UsedShmemSegAddr != NULL)
{
if (!UnmapViewOfFile(UsedShmemSegAddr))
elog(LOG, "could not unmap view of shared memory: %lu", GetLastError());
UsedShmemSegAddr = NULL;
}
}
/*
* pgwin32_SharedMemoryDelete(status, shmId) deletes a shared memory segment
* (called as an on_shmem_exit callback, hence funny argument list)
*/
static void
pgwin32_SharedMemoryDelete(int status, Datum shmId)
{
PGSharedMemoryDetach();
if (!CloseHandle((HANDLE) DatumGetInt32(shmId)))
elog(LOG, "could not close handle to shared memory: %lu", GetLastError());
}
/*
* pgwin32_ReserveSharedMemoryRegion(hChild)
*
* Reserve the memory region that will be used for shared memory in a child
* process. It is called before the child process starts, to make sure the
* memory is available.
*
* Once the child starts, DLLs loading in different order or threads getting
* scheduled differently may allocate memory which can conflict with the
* address space we need for our shared memory. By reserving the shared
* memory region before the child starts, and freeing it only just before we
* attempt to get access to the shared memory forces these allocations to
* be given different address ranges that don't conflict.
*
* NOTE! This function executes in the postmaster, and should for this
* reason not use elog(FATAL) since that would take down the postmaster.
*/
int
pgwin32_ReserveSharedMemoryRegion(HANDLE hChild)
{
void *address;
Assert(UsedShmemSegAddr != NULL);
Assert(UsedShmemSegSize != 0);
address = VirtualAllocEx(hChild, UsedShmemSegAddr, UsedShmemSegSize,
MEM_RESERVE, PAGE_READWRITE);
if (address == NULL) {
/* Don't use FATAL since we're running in the postmaster */
elog(LOG, "could not reserve shared memory region (addr=%p) for child %lu: %lu",
UsedShmemSegAddr, hChild, GetLastError());
return false;
}
if (address != UsedShmemSegAddr)
{
/*
* Should never happen - in theory if allocation granularity causes strange
* effects it could, so check just in case.
*
* Don't use FATAL since we're running in the postmaster.
*/
elog(LOG, "reserved shared memory region got incorrect address %p, expected %p",
address, UsedShmemSegAddr);
VirtualFreeEx(hChild, address, 0, MEM_RELEASE);
return false;
}
return true;
}