summaryrefslogtreecommitdiff
path: root/src/backend/port/posix_sema.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2002-05-05 00:03:29 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2002-05-05 00:03:29 +0000
commit72a3902a664c7fbceb2034e28e444b28f96fa717 (patch)
treeff42e4494af6ea1c1cdf524f3feb5fc670217f0c /src/backend/port/posix_sema.c
parent91fc10fdacfcbadc123fd9d8ee16a4568f8c636b (diff)
Create an internal semaphore API that is not tied to SysV semaphores.
As proof of concept, provide an alternate implementation based on POSIX semaphores. Also push the SysV shared-memory implementation into a separate file so that it can be replaced conveniently.
Diffstat (limited to 'src/backend/port/posix_sema.c')
-rw-r--r--src/backend/port/posix_sema.c357
1 files changed, 357 insertions, 0 deletions
diff --git a/src/backend/port/posix_sema.c b/src/backend/port/posix_sema.c
new file mode 100644
index 00000000000..1dd02f8def6
--- /dev/null
+++ b/src/backend/port/posix_sema.c
@@ -0,0 +1,357 @@
+/*-------------------------------------------------------------------------
+ *
+ * posix_sema.c
+ * Implement PGSemaphores using POSIX semaphore facilities
+ *
+ * We prefer the unnamed style of POSIX semaphore (the kind made with
+ * sem_init). We can cope with the kind made with sem_open, however.
+ *
+ *
+ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * $Header: /cvsroot/pgsql/src/backend/port/posix_sema.c,v 1.1 2002/05/05 00:03:28 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "storage/pg_sema.h"
+
+
+#ifdef USE_NAMED_POSIX_SEMAPHORES
+/* PGSemaphore is pointer to pointer to sem_t */
+#define PG_SEM_REF(x) (*(x))
+#else
+/* PGSemaphore is pointer to sem_t */
+#define PG_SEM_REF(x) (x)
+#endif
+
+
+#define IPCProtection (0600) /* access/modify by user only */
+
+static sem_t **mySemPointers; /* keep track of created semaphores */
+static int numSems; /* number of semas acquired so far */
+static int maxSems; /* allocated size of mySemaPointers array */
+static int nextSemKey; /* next name to try */
+
+
+static void ReleaseSemaphores(int status, Datum arg);
+
+
+#ifdef USE_NAMED_POSIX_SEMAPHORES
+
+/*
+ * PosixSemaphoreCreate
+ *
+ * Attempt to create a new named semaphore.
+ *
+ * If we fail with a failure code other than collision-with-existing-sema,
+ * print out an error and abort. Other types of errors suggest nonrecoverable
+ * problems.
+ */
+static sem_t *
+PosixSemaphoreCreate(void)
+{
+ int semKey;
+ char semname[64];
+ sem_t *mySem;
+
+ for (;;)
+ {
+ semKey = nextSemKey++;
+
+ snprintf(semname, sizeof(semname), "/pgsql-%d", semKey);
+
+ mySem = sem_open(semname, O_CREAT | O_EXCL,
+ (mode_t) IPCProtection, (unsigned) 1);
+ if (mySem != SEM_FAILED)
+ break;
+
+ /* Loop if error indicates a collision */
+ if (errno == EEXIST || errno == EACCES || errno == EINTR)
+ continue;
+
+ /*
+ * Else complain and abort
+ */
+ fprintf(stderr, "PosixSemaphoreCreate: sem_open(%s) failed: %s\n",
+ semname, strerror(errno));
+ proc_exit(1);
+ }
+
+ /*
+ * Unlink the semaphore immediately, so it can't be accessed externally.
+ * This also ensures that it will go away if we crash.
+ */
+ sem_unlink(semname);
+
+ return mySem;
+}
+
+#else /* !USE_NAMED_POSIX_SEMAPHORES */
+
+/*
+ * PosixSemaphoreCreate
+ *
+ * Attempt to create a new unnamed semaphore.
+ */
+static void
+PosixSemaphoreCreate(sem_t *sem)
+{
+ if (sem_init(sem, 1, 1) < 0)
+ {
+ fprintf(stderr, "PosixSemaphoreCreate: sem_init failed: %s\n",
+ strerror(errno));
+ proc_exit(1);
+ }
+}
+
+#endif /* USE_NAMED_POSIX_SEMAPHORES */
+
+
+/*
+ * PosixSemaphoreKill - removes a semaphore
+ */
+static void
+PosixSemaphoreKill(sem_t *sem)
+{
+#ifdef USE_NAMED_POSIX_SEMAPHORES
+ /* Got to use sem_close for named semaphores */
+ if (sem_close(sem) < 0)
+ fprintf(stderr, "PosixSemaphoreKill: sem_close failed: %s\n",
+ strerror(errno));
+#else
+ /* Got to use sem_destroy for unnamed semaphores */
+ if (sem_destroy(sem) < 0)
+ fprintf(stderr, "PosixSemaphoreKill: sem_destroy failed: %s\n",
+ strerror(errno));
+#endif
+}
+
+
+/*
+ * PGReserveSemaphores --- initialize semaphore support
+ *
+ * This is called during postmaster start or shared memory reinitialization.
+ * It should do whatever is needed to be able to support up to maxSemas
+ * subsequent PGSemaphoreCreate calls. Also, if any system resources
+ * are acquired here or in PGSemaphoreCreate, register an on_shmem_exit
+ * callback to release them.
+ *
+ * The port number is passed for possible use as a key (for Posix, we use
+ * it to generate the starting semaphore name). In a standalone backend,
+ * zero will be passed.
+ *
+ * In the Posix implementation, we acquire semaphores on-demand; the
+ * maxSemas parameter is just used to size the array that keeps track of
+ * acquired semas for subsequent releasing.
+ */
+void
+PGReserveSemaphores(int maxSemas, int port)
+{
+ mySemPointers = (sem_t **) malloc(maxSemas * sizeof(sem_t *));
+ if (mySemPointers == NULL)
+ elog(PANIC, "Out of memory in PGReserveSemaphores");
+ numSems = 0;
+ maxSems = maxSemas;
+ nextSemKey = port * 1000;
+
+ on_shmem_exit(ReleaseSemaphores, 0);
+}
+
+/*
+ * Release semaphores at shutdown or shmem reinitialization
+ *
+ * (called as an on_shmem_exit callback, hence funny argument list)
+ */
+static void
+ReleaseSemaphores(int status, Datum arg)
+{
+ int i;
+
+ for (i = 0; i < numSems; i++)
+ PosixSemaphoreKill(mySemPointers[i]);
+ free(mySemPointers);
+}
+
+/*
+ * PGSemaphoreCreate
+ *
+ * Initialize a PGSemaphore structure to represent a sema with count 1
+ */
+void
+PGSemaphoreCreate(PGSemaphore sema)
+{
+ sem_t *newsem;
+
+ /* Can't do this in a backend, because static state is postmaster's */
+ Assert(!IsUnderPostmaster);
+
+ if (numSems >= maxSems)
+ elog(PANIC, "PGSemaphoreCreate: too many semaphores created");
+
+#ifdef USE_NAMED_POSIX_SEMAPHORES
+ *sema = newsem = PosixSemaphoreCreate();
+#else
+ PosixSemaphoreCreate(sema);
+ newsem = sema;
+#endif
+
+ /* Remember new sema for ReleaseSemaphores */
+ mySemPointers[numSems++] = newsem;
+}
+
+/*
+ * PGSemaphoreReset
+ *
+ * Reset a previously-initialized PGSemaphore to have count 0
+ */
+void
+PGSemaphoreReset(PGSemaphore sema)
+{
+ /*
+ * There's no direct API for this in POSIX, so we have to ratchet the
+ * semaphore down to 0 with repeated trywait's.
+ */
+ for (;;)
+ {
+ if (sem_trywait(PG_SEM_REF(sema)) < 0)
+ {
+ if (errno == EAGAIN || errno == EDEADLK)
+ break; /* got it down to 0 */
+ if (errno == EINTR)
+ continue; /* can this happen? */
+ fprintf(stderr, "PGSemaphoreReset: sem_trywait failed: %s\n",
+ strerror(errno));
+ proc_exit(1);
+ }
+ }
+}
+
+/*
+ * PGSemaphoreLock
+ *
+ * Lock a semaphore (decrement count), blocking if count would be < 0
+ */
+void
+PGSemaphoreLock(PGSemaphore sema, bool interruptOK)
+{
+ int errStatus;
+
+ /*
+ * Note: if errStatus is -1 and errno == EINTR then it means we
+ * returned from the operation prematurely because we were sent a
+ * signal. So we try and lock the semaphore again.
+ *
+ * Each time around the loop, we check for a cancel/die interrupt. We
+ * assume that if such an interrupt comes in while we are waiting, it
+ * will cause the sem_wait() call to exit with errno == EINTR, so that we
+ * will be able to service the interrupt (if not in a critical section
+ * already).
+ *
+ * Once we acquire the lock, we do NOT check for an interrupt before
+ * returning. The caller needs to be able to record ownership of the
+ * lock before any interrupt can be accepted.
+ *
+ * There is a window of a few instructions between CHECK_FOR_INTERRUPTS
+ * and entering the sem_wait() call. If a cancel/die interrupt occurs in
+ * that window, we would fail to notice it until after we acquire the
+ * lock (or get another interrupt to escape the sem_wait()). We can
+ * avoid this problem by temporarily setting ImmediateInterruptOK to
+ * true before we do CHECK_FOR_INTERRUPTS; then, a die() interrupt in
+ * this interval will execute directly. However, there is a huge
+ * pitfall: there is another window of a few instructions after the
+ * sem_wait() before we are able to reset ImmediateInterruptOK. If an
+ * interrupt occurs then, we'll lose control, which means that the
+ * lock has been acquired but our caller did not get a chance to
+ * record the fact. Therefore, we only set ImmediateInterruptOK if the
+ * caller tells us it's OK to do so, ie, the caller does not need to
+ * record acquiring the lock. (This is currently true for lockmanager
+ * locks, since the process that granted us the lock did all the
+ * necessary state updates. It's not true for Posix semaphores used to
+ * implement LW locks or emulate spinlocks --- but the wait time for
+ * such locks should not be very long, anyway.)
+ */
+ do
+ {
+ ImmediateInterruptOK = interruptOK;
+ CHECK_FOR_INTERRUPTS();
+ errStatus = sem_wait(PG_SEM_REF(sema));
+ ImmediateInterruptOK = false;
+ } while (errStatus < 0 && errno == EINTR);
+
+ if (errStatus < 0)
+ {
+ fprintf(stderr, "PGSemaphoreLock: sem_wait failed: %s\n",
+ strerror(errno));
+ proc_exit(255);
+ }
+}
+
+/*
+ * PGSemaphoreUnlock
+ *
+ * Unlock a semaphore (increment count)
+ */
+void
+PGSemaphoreUnlock(PGSemaphore sema)
+{
+ int errStatus;
+
+ /*
+ * Note: if errStatus is -1 and errno == EINTR then it means we
+ * returned from the operation prematurely because we were sent a
+ * signal. So we try and unlock the semaphore again. Not clear this
+ * can really happen, but might as well cope.
+ */
+ do
+ {
+ errStatus = sem_post(PG_SEM_REF(sema));
+ } while (errStatus < 0 && errno == EINTR);
+
+ if (errStatus < 0)
+ {
+ fprintf(stderr, "PGSemaphoreUnlock: sem_post failed: %s\n",
+ strerror(errno));
+ proc_exit(255);
+ }
+}
+
+/*
+ * PGSemaphoreTryLock
+ *
+ * Lock a semaphore only if able to do so without blocking
+ */
+bool
+PGSemaphoreTryLock(PGSemaphore sema)
+{
+ int errStatus;
+
+ /*
+ * Note: if errStatus is -1 and errno == EINTR then it means we
+ * returned from the operation prematurely because we were sent a
+ * signal. So we try and lock the semaphore again.
+ */
+ do
+ {
+ errStatus = sem_trywait(PG_SEM_REF(sema));
+ } while (errStatus < 0 && errno == EINTR);
+
+ if (errStatus < 0)
+ {
+ if (errno == EAGAIN || errno == EDEADLK)
+ return false; /* failed to lock it */
+ /* Otherwise we got trouble */
+ fprintf(stderr, "PGSemaphoreTryLock: sem_trywait failed: %s\n",
+ strerror(errno));
+ proc_exit(255);
+ }
+
+ return true;
+}