diff --git a/configure/os/CONFIG.Common.linuxCommon b/configure/os/CONFIG.Common.linuxCommon
index 4f91583d7..a230d1734 100644
--- a/configure/os/CONFIG.Common.linuxCommon
+++ b/configure/os/CONFIG.Common.linuxCommon
@@ -14,7 +14,7 @@ OS_CLASS = Linux
CODE_CPPFLAGS = -D_REENTRANT
-POSIX_CPPFLAGS = -D_POSIX_C_SOURCE=199506L -D_POSIX_THREADS -D_XOPEN_SOURCE=500
+POSIX_CPPFLAGS = -D_POSIX_C_SOURCE=200112L -D_POSIX_THREADS -D_XOPEN_SOURCE=500
POSIX_LDLIBS = -lpthread
# -D_BSD_SOURCE for gethostname() in unistd.h as needed by cacChannelIO.cpp.
diff --git a/configure/os/CONFIG.Common.solaris-sparc b/configure/os/CONFIG.Common.solaris-sparc
index d47a5165e..7e8788048 100644
--- a/configure/os/CONFIG.Common.solaris-sparc
+++ b/configure/os/CONFIG.Common.solaris-sparc
@@ -20,7 +20,7 @@ COMPILER_LDFLAGS += -mt
SOLARIS_VERSION = $(subst 5.,,$(shell uname -r))
-POSIX_CPPFLAGS += -D_POSIX_C_SOURCE=199506L $(POSIX_CPPFLAGS_$(SOLARIS_VERSION))
+POSIX_CPPFLAGS += -D_POSIX_C_SOURCE=200112L $(POSIX_CPPFLAGS_$(SOLARIS_VERSION))
POSIX_CPPFLAGS += -D_XOPEN_SOURCE=500
POSIX_LDLIBS += -lposix4 -lpthread $(POSIX_LDLIBS_$(SOLARIS_VERSION))
diff --git a/configure/os/CONFIG.Common.solaris-x86 b/configure/os/CONFIG.Common.solaris-x86
index d19083422..e3632a7bc 100644
--- a/configure/os/CONFIG.Common.solaris-x86
+++ b/configure/os/CONFIG.Common.solaris-x86
@@ -20,7 +20,7 @@ COMPILER_LDFLAGS += -mt
SOLARIS_VERSION = $(subst 5.,,$(shell uname -r))
-POSIX_CPPFLAGS += -D_POSIX_C_SOURCE=199506L $(POSIX_CPPFLAGS_$(SOLARIS_VERSION))
+POSIX_CPPFLAGS += -D_POSIX_C_SOURCE=200112L $(POSIX_CPPFLAGS_$(SOLARIS_VERSION))
POSIX_CPPFLAGS += -D_XOPEN_SOURCE=500
POSIX_LDLIBS += -lposix4 -lpthread $(POSIX_LDLIBS_$(SOLARIS_VERSION))
diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html
index dcb324cdc..9bffde2c2 100644
--- a/documentation/RELEASE_NOTES.html
+++ b/documentation/RELEASE_NOTES.html
@@ -15,6 +15,18 @@ EPICS Base 3.15.0.x releases are not intended for use in production systems.
Changes between 3.15.0.1 and 3.15.0.2
+Spin-locks API added
+
+The new header file epicsSpin.h adds a portable spin-locks API which is
+intended for locking very short sections of code (typically one or two lines of
+C or C++) to provide a critical section that protects against race conditions.
+On Posix platforms this uses the pthread_spinlock_t type if it's available but
+falls back to a pthread_mutex_t if not; on the UP VxWorks and RTEMS platforms
+the implementations lock out the CPU interrupts; the default implementation
+(used where no better implementation is available for the platform) uses an
+epicsMutex. Spin-locks may not be taken recursively, and the code inside the
+critical section should always be short and deterministic.
+
Improvements to aToIPAddr()
The libCom routine aToIPAddr() and the vxWorks implementation of the
diff --git a/src/libCom/osi/Makefile b/src/libCom/osi/Makefile
index e22091712..94fe92c61 100644
--- a/src/libCom/osi/Makefile
+++ b/src/libCom/osi/Makefile
@@ -20,6 +20,7 @@ INC += osdInterrupt.h
INC += epicsMutex.h
INC += osdMutex.h
+INC += epicsSpin.h
INC += epicsEvent.h
INC += osdEvent.h
INC += epicsMath.h
@@ -107,6 +108,7 @@ Com_SRCS += osdThread.c
Com_SRCS += osdThreadExtra.c
Com_SRCS += osdThreadHooks.c
Com_SRCS += osdMutex.c
+Com_SRCS += osdSpin.c
Com_SRCS += osdEvent.c
Com_SRCS += osdTime.cpp
Com_SRCS += osdProcess.c
diff --git a/src/libCom/osi/epicsSpin.h b/src/libCom/osi/epicsSpin.h
new file mode 100644
index 000000000..06bc44a09
--- /dev/null
+++ b/src/libCom/osi/epicsSpin.h
@@ -0,0 +1,31 @@
+/*************************************************************************\
+* Copyright (c) 2012 Helmholtz-Zentrum Berlin
+* fuer Materialien und Energie GmbH.
+* Copyright (c) 2012 ITER Organization.
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+#ifndef epicsSpinh
+#define epicsSpinh
+
+#include "shareLib.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct epicsSpin *epicsSpinId;
+
+epicsShareFunc epicsSpinId epicsSpinCreate();
+epicsShareFunc void epicsSpinDestroy(epicsSpinId);
+
+epicsShareFunc void epicsSpinLock(epicsSpinId);
+epicsShareFunc int epicsSpinTryLock(epicsSpinId);
+epicsShareFunc void epicsSpinUnlock(epicsSpinId);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* epicsSpinh */
diff --git a/src/libCom/osi/os/RTEMS/osdSpin.c b/src/libCom/osi/os/RTEMS/osdSpin.c
new file mode 100644
index 000000000..78600e5f1
--- /dev/null
+++ b/src/libCom/osi/os/RTEMS/osdSpin.c
@@ -0,0 +1,54 @@
+/*************************************************************************\
+* Copyright (c) 2012 Helmholtz-Zentrum Berlin
+* fuer Materialien und Energie GmbH.
+* Copyright (c) 2012 ITER Organization.
+* Copyright (c) 2013 UChicago Argonne LLC, as Operator of Argonne
+* National Laboratory.
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+/*
+ * Authors: Ralph Lange
+ * Andrew Johnson
+ *
+ * Based on epicsInterrupt.c (RTEMS implementation) by Eric Norum
+ */
+
+/*
+ * RTEMS (single CPU): LOCK INTERRUPT
+ *
+ * CAVEAT:
+ * This implementation is for UP architectures only.
+ *
+ */
+
+#include
+#include
+
+#include "epicsSpin.h"
+
+typedef struct epicsSpin {
+ rtems_interrupt_level level;
+} epicsSpin;
+
+epicsSpinId epicsSpinCreate() {
+ return calloc(1, sizeof(epicsSpin));
+}
+
+void epicsSpinDestroy(epicsSpinId spin) {
+ free(spin);
+}
+
+void epicsSpinLock(epicsSpinId spin) {
+ rtems_interrupt_disable(spin->level);
+}
+
+int epicsSpinTryLock(epicsSpinId spin) {
+ epicsSpinLock(spin);
+ return 0;
+}
+
+void epicsSpinUnlock(epicsSpinId spin) {
+ rtems_interrupt_enable(spin->level);
+}
diff --git a/src/libCom/osi/os/default/osdSpin.c b/src/libCom/osi/os/default/osdSpin.c
new file mode 100644
index 000000000..70d9fbace
--- /dev/null
+++ b/src/libCom/osi/os/default/osdSpin.c
@@ -0,0 +1,74 @@
+/*************************************************************************\
+* Copyright (c) 2012 Helmholtz-Zentrum Berlin
+* fuer Materialien und Energie GmbH.
+* Copyright (c) 2012 ITER Organization.
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+/*
+ * Author: Ralph Lange
+ */
+
+#include
+
+#define epicsExportSharedSymbols
+#include "errlog.h"
+#include "epicsMutex.h"
+#include "epicsSpin.h"
+
+/*
+ * Default: EPICS MUTEX IMPLEMENTATION
+ */
+
+typedef struct epicsSpin {
+ epicsMutexId lock;
+} epicsSpin;
+
+epicsSpinId epicsSpinCreate() {
+ epicsSpin *spin;
+
+ spin = calloc(1, sizeof(*spin));
+ if (!spin)
+ goto fail;
+
+ spin->lock = epicsMutexCreate();
+ if (!spin->lock)
+ goto fail;
+
+ return spin;
+
+fail:
+ free(spin);
+ return NULL;
+}
+
+void epicsSpinDestroy(epicsSpinId spin) {
+ epicsMutexDestroy(spin->lock);
+ free(spin);
+}
+
+void epicsSpinLock(epicsSpinId spin) {
+ epicsMutexLockStatus status;
+
+ status = epicsMutexLock(spin->lock);
+ if (status != epicsMutexLockOK) {
+ errlogPrintf("epicsSpin epicsMutexLock failed: error %s\n",
+ status == epicsMutexLockTimeout ? "epicsMutexLockTimeout" : "epicsMutexLockError");
+ }
+}
+
+int epicsSpinTryLock(epicsSpinId spin) {
+ epicsMutexLockStatus status;
+
+ status = epicsMutexTryLock(spin->lock);
+ if (status == epicsMutexLockOK) return 0;
+ if (status == epicsMutexLockTimeout) return 1;
+
+ errlogPrintf("epicsSpin epicsMutexTryLock failed: error epicsMutexLockError\n");
+ return 2;
+}
+
+void epicsSpinUnlock(epicsSpinId spin) {
+ epicsMutexUnlock(spin->lock);
+}
diff --git a/src/libCom/osi/os/posix/osdSpin.c b/src/libCom/osi/os/posix/osdSpin.c
new file mode 100644
index 000000000..71c34cca8
--- /dev/null
+++ b/src/libCom/osi/os/posix/osdSpin.c
@@ -0,0 +1,154 @@
+/*************************************************************************\
+* Copyright (c) 2012 Helmholtz-Zentrum Berlin
+* fuer Materialien und Energie GmbH.
+* Copyright (c) 2012 ITER Organization.
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+/*
+ * Author: Ralph Lange
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+#include "errlog.h"
+#include "epicsSpin.h"
+
+#define checkStatus(status,message) \
+ if ((status)) { \
+ errlogPrintf("epicsSpin %s failed: error %s\n", \
+ (message), strerror((status))); \
+ }
+
+#if defined(_POSIX_SPIN_LOCKS) && (_POSIX_SPIN_LOCKS > 1)
+
+/*
+ * POSIX SPIN LOCKS IMPLEMENTATION
+ */
+
+typedef struct epicsSpin {
+ pthread_spinlock_t lock;
+} epicsSpin;
+
+epicsSpinId epicsSpinCreate() {
+ epicsSpin *spin;
+ int status;
+
+ spin = calloc(1, sizeof(*spin));
+ if (!spin)
+ goto fail;
+
+ status = pthread_spin_init(&spin->lock, PTHREAD_PROCESS_PRIVATE);
+ checkStatus(status, "pthread_spin_init");
+ if (status)
+ goto fail;
+
+ return spin;
+
+fail:
+ free(spin);
+ return NULL;
+}
+
+void epicsSpinDestroy(epicsSpinId spin) {
+ int status;
+
+ status = pthread_spin_destroy(&spin->lock);
+ checkStatus(status, "pthread_spin_destroy");
+
+ free(spin);
+}
+
+void epicsSpinLock(epicsSpinId spin) {
+ int status;
+
+ status = pthread_spin_lock(&spin->lock);
+ checkStatus(status, "pthread_spin_lock");
+}
+
+int epicsSpinTryLock(epicsSpinId spin) {
+ int status;
+
+ status = pthread_spin_trylock(&spin->lock);
+ if (status == EBUSY)
+ return 1;
+ checkStatus(status, "pthread_spin_trylock");
+ return 0;
+}
+
+void epicsSpinUnlock(epicsSpinId spin) {
+ int status;
+
+ status = pthread_spin_unlock(&spin->lock);
+ checkStatus(status, "pthread_spin_unlock");
+}
+
+#else /* defined(_POSIX_SPIN_LOCKS) && (_POSIX_SPIN_LOCKS > 1) */
+
+/*
+ * POSIX MUTEX IMPLEMENTATION
+ */
+
+typedef struct epicsSpin {
+ pthread_mutex_t lock;
+} epicsSpin;
+
+epicsSpinId epicsSpinCreate() {
+ epicsSpin *spin;
+ int status;
+
+ spin = calloc(1, sizeof(*spin));
+ if (!spin)
+ goto fail;
+
+ status = pthread_mutex_init(&spin->lock, NULL);
+ checkStatus(status, "pthread_mutex_init");
+ if (status)
+ goto fail;
+
+ return spin;
+
+fail:
+ free(spin);
+ return NULL;
+}
+
+void epicsSpinDestroy(epicsSpinId spin) {
+ int status;
+
+ status = pthread_mutex_destroy(&spin->lock);
+ checkStatus(status, "pthread_mutex_destroy");
+
+ free(spin);
+}
+
+void epicsSpinLock(epicsSpinId spin) {
+ int status;
+
+ status = pthread_mutex_lock(&spin->lock);
+ checkStatus(status, "pthread_mutex_lock");
+}
+
+int epicsSpinTryLock(epicsSpinId spin) {
+ int status;
+
+ status = pthread_mutex_trylock(&spin->lock);
+ if (status == EBUSY)
+ return 1;
+ checkStatus(status, "pthread_mutex_trylock");
+ return 0;
+}
+
+void epicsSpinUnlock(epicsSpinId spin) {
+ int status;
+
+ status = pthread_mutex_unlock(&spin->lock);
+ checkStatus(status, "pthread_mutex_unlock");
+}
+
+#endif /* defined(_POSIX_SPIN_LOCKS) && (_POSIX_SPIN_LOCKS > 1) */
diff --git a/src/libCom/osi/os/vxWorks/osdSpin.c b/src/libCom/osi/os/vxWorks/osdSpin.c
new file mode 100644
index 000000000..9b545347d
--- /dev/null
+++ b/src/libCom/osi/os/vxWorks/osdSpin.c
@@ -0,0 +1,52 @@
+/*************************************************************************\
+* Copyright (c) 2012 Helmholtz-Zentrum Berlin
+* fuer Materialien und Energie GmbH.
+* Copyright (c) 2012 ITER Organization.
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+/*
+ * Author: Ralph Lange
+ *
+ * based on epicsInterrupt.c (vxWorks implementation) by Marty Kraimer
+ */
+
+/*
+ * vxWorks (single CPU): LOCK INTERRUPT
+ *
+ * CAVEAT:
+ * This implementation will not compile on vxWorks SMP architectures.
+ * These architectures provide spinlocks, which must be used instead.
+ *
+ */
+
+#include
+#include
+
+#include "epicsSpin.h"
+
+typedef struct epicsSpin {
+ int key;
+} epicsSpin;
+
+epicsSpinId epicsSpinCreate() {
+ return calloc(1, sizeof(epicsSpin));
+}
+
+void epicsSpinDestroy(epicsSpinId spin) {
+ free(spin);
+}
+
+void epicsSpinLock(epicsSpinId spin) {
+ spin->key = intLock();
+}
+
+int epicsSpinTryLock(epicsSpinId spin) {
+ epicsSpinLock(spin);
+ return 0;
+}
+
+void epicsSpinUnlock(epicsSpinId spin) {
+ intUnlock(spin->key);
+}
diff --git a/src/libCom/test/Makefile b/src/libCom/test/Makefile
index 40abe97ca..bef859a8f 100755
--- a/src/libCom/test/Makefile
+++ b/src/libCom/test/Makefile
@@ -131,6 +131,11 @@ epicsMutexTest_SRCS += epicsMutexTest.cpp
testHarness_SRCS += epicsMutexTest.cpp
TESTS += epicsMutexTest
+TESTPROD_HOST += epicsSpinTest
+epicsSpinTest_SRCS += epicsSpinTest.c
+testHarness_SRCS += epicsSpinTest.c
+TESTS += epicsSpinTest
+
TESTPROD_HOST += epicsAtomicTest
epicsAtomicTest_SRCS += epicsAtomicTest.cpp
testHarness_SRCS += epicsAtomicTest.cpp
diff --git a/src/libCom/test/epicsSpinTest.c b/src/libCom/test/epicsSpinTest.c
new file mode 100644
index 000000000..707345b89
--- /dev/null
+++ b/src/libCom/test/epicsSpinTest.c
@@ -0,0 +1,185 @@
+/*************************************************************************\
+* Copyright (c) 2012 Helmholtz-Zentrum Berlin
+* fuer Materialien und Energie GmbH.
+* Copyright (c) 2012 ITER Organization.
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+/*
+ * Author: Ralph Lange
+ *
+ * based on epicsMutexTest by Marty Kraimer and Jeff Hill
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "epicsTime.h"
+#include "epicsThread.h"
+#include "epicsSpin.h"
+#include "epicsEvent.h"
+#include "errlog.h"
+#include "epicsUnitTest.h"
+#include "testMain.h"
+
+typedef struct info {
+ int threadnum;
+ epicsSpinId spin;
+ int quit;
+} info;
+
+void spinThread(void *arg)
+{
+ info *pinfo = (info *) arg;
+ testDiag("spinThread %d starting", pinfo->threadnum);
+ while (pinfo->quit--) {
+ epicsSpinLock(pinfo->spin);
+ testPass("spinThread %d epicsSpinLock taken", pinfo->threadnum);
+ epicsThreadSleep(.1);
+ epicsSpinUnlock(pinfo->spin);
+ epicsThreadSleep(.9);
+ }
+ testDiag("spinThread %d exiting", pinfo->threadnum);
+ return;
+}
+
+static void lockPair(struct epicsSpin *spin)
+{
+ epicsSpinLock(spin);
+ epicsSpinUnlock(spin);
+}
+
+static void tenLockPairs(struct epicsSpin *spin)
+{
+ lockPair(spin);
+ lockPair(spin);
+ lockPair(spin);
+ lockPair(spin);
+ lockPair(spin);
+ lockPair(spin);
+ lockPair(spin);
+ lockPair(spin);
+ lockPair(spin);
+ lockPair(spin);
+}
+
+static void tenLockPairsSquared(struct epicsSpin *spin)
+{
+ tenLockPairs(spin);
+ tenLockPairs(spin);
+ tenLockPairs(spin);
+ tenLockPairs(spin);
+ tenLockPairs(spin);
+ tenLockPairs(spin);
+ tenLockPairs(spin);
+ tenLockPairs(spin);
+ tenLockPairs(spin);
+ tenLockPairs(spin);
+}
+
+void epicsSpinPerformance ()
+{
+ static const unsigned N = 10000;
+ unsigned i;
+ epicsSpinId spin;
+ epicsTimeStamp begin;
+ epicsTimeStamp end;
+ double delay;
+
+ /* Initialize spinlock */
+ spin = epicsSpinCreate();
+
+ /* test a single lock pair */
+ epicsTimeGetCurrent(&begin);
+ for ( i = 0; i < N; i++ ) {
+ tenLockPairsSquared(spin);
+ }
+ epicsTimeGetCurrent(&end);
+
+ delay = epicsTimeDiffInSeconds(&end, &begin);
+ delay /= N * 100u; /* convert to delay per lock pair */
+ delay *= 1e6; /* convert to micro seconds */
+ testDiag("lock()*1/unlock()*1 takes %f microseconds", delay);
+}
+
+struct verifyTryLock {
+ epicsSpinId spin;
+ epicsEventId done;
+};
+
+static void verifyTryLockThread(void *pArg)
+{
+ struct verifyTryLock *pVerify =
+ (struct verifyTryLock *) pArg;
+
+ testOk1(epicsSpinTryLock(pVerify->spin));
+ epicsEventSignal(pVerify->done);
+}
+
+static void verifyTryLock()
+{
+ struct verifyTryLock verify;
+
+ verify.spin = epicsSpinCreate();
+ verify.done = epicsEventMustCreate(epicsEventEmpty);
+
+ testOk1(epicsSpinTryLock(verify.spin) == 0);
+
+ epicsThreadCreate("verifyTryLockThread", 40,
+ epicsThreadGetStackSize(epicsThreadStackSmall),
+ verifyTryLockThread, &verify);
+
+ testOk1(epicsEventWait(verify.done) == epicsEventWaitOK);
+
+ epicsSpinUnlock(verify.spin);
+ epicsSpinDestroy(verify.spin);
+ epicsEventDestroy(verify.done);
+}
+
+MAIN(epicsSpinTest)
+{
+ const int nthreads = 3;
+ const int nrounds = 5;
+ unsigned int stackSize;
+ epicsThreadId *id;
+ int i;
+ char **name;
+ void **arg;
+ info **pinfo;
+ epicsSpinId spin;
+
+ testPlan(3 + nthreads * nrounds);
+
+ verifyTryLock();
+
+ spin = epicsSpinCreate();
+
+ id = (epicsThreadId *) calloc(nthreads, sizeof(epicsThreadId));
+ name = (char **) calloc(nthreads, sizeof(char *));
+ arg = (void **) calloc(nthreads, sizeof(void *));
+ pinfo = (info **) calloc(nthreads, sizeof(info *));
+ stackSize = epicsThreadGetStackSize(epicsThreadStackSmall);
+ for (i = 0; i < nthreads; i++) {
+ name[i] = (char *) calloc(10, sizeof(char));
+ sprintf(name[i],"task%d",i);
+ pinfo[i] = (info *) calloc(1, sizeof(info));
+ pinfo[i]->threadnum = i;
+ pinfo[i]->spin = spin;
+ pinfo[i]->quit = nrounds;
+ arg[i] = pinfo[i];
+ id[i] = epicsThreadCreate(name[i], 40, stackSize,
+ spinThread,
+ arg[i]);
+ }
+ epicsThreadSleep(2.0 + nrounds);
+
+ epicsSpinPerformance();
+
+ return testDone();
+}