diff options
Diffstat (limited to 'src/platform')
| -rw-r--r-- | src/platform/platform.h | 31 | ||||
| -rw-r--r-- | src/platform/posix/posix_clock.h | 149 | ||||
| -rw-r--r-- | src/platform/posix/posix_config.h | 60 | ||||
| -rw-r--r-- | src/platform/posix/posix_impl.h | 4 | ||||
| -rw-r--r-- | src/platform/posix/posix_synch.h | 82 |
5 files changed, 297 insertions, 29 deletions
diff --git a/src/platform/platform.h b/src/platform/platform.h index 36ec207c..0d958c65 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -23,10 +23,16 @@ #ifndef PLATFORM_H #define PLATFORM_H -#include <stdlib.h> -#include <stdint.h> +/* + * We require some standard C header files. The only one of these that might + * be problematic is <stdint.h>, which is required for C99. Older versions + * of the Windows compilers might not have this. However, latest versions of + * MS Studio have a functional <stdint.h>. If this impacts you, just upgrade + * your tool chain. + */ #include <stdarg.h> -#include <string.h> +#include <stddef.h> +#include <stdint.h> /* * These are the APIs that a platform must implement to support nng. @@ -113,11 +119,24 @@ void nni_cond_wait(nni_cond_t); /* * nni_cond_timedwait waits for a wakeup on the condition variable, just * as with nni_condwait, but it will also wake after the given number of - * milliseconds has passed. (This is a relative timed wait.) Early + * microseconds has passed. (This is a relative timed wait.) Early * wakeups are permitted, and the caller must take care to double check any * conditions. The return value is 0 on success, or an error code, which - * can be NNG_ETIMEDOUT. + * can be NNG_ETIMEDOUT. Note that it is permissible to wait for longer + * than the timeout based on the resolution of your system clock. + */ +int nni_cond_timedwait(nni_cond_t, uint64_t); + +/* + * nn_clock returns a number of microseconds since some arbitrary time + * in the past. The values returned by nni_clock may be used with + * nni_cond_timedwait. + */ +uint64_t nni_clock(void); + +/* + * nni_usleep sleeps for the specified number of microseconds (at least). */ -int nnp_cond_timedwait(nni_cond_t, int); +void nni_usleep(uint64_t); #endif /* PLATFORM_H */ diff --git a/src/platform/posix/posix_clock.h b/src/platform/posix/posix_clock.h new file mode 100644 index 00000000..07fc603d --- /dev/null +++ b/src/platform/posix/posix_clock.h @@ -0,0 +1,149 @@ +/* + * Copyright 2016 Garrett D'Amore <garrett@damore.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* + * This is more of a direct #include of a .c rather than .h file. + * But having it be a .h makes compiler rules work out properly. Do + * not include this more than once into your program, or you will + * get multiple symbols defined. + */ + +/* + * POSIX clock stuff. + */ + +#include <time.h> +#include <errno.h> +#include <string.h> + +#ifndef NNG_USE_GETTIMEOFDAY + +/* + * Use POSIX realtime stuff. + */ + +uint64_t +nni_clock(void) +{ + struct timespec ts; + uint64_t usec; + + if (clock_gettime(NNG_USE_CLOCKID, &ts) != 0) { + /* This should never ever occur. */ + nni_panic("clock_gettime failed: %s", strerror(errno)); + } + + usec = ts.tv_sec; + usec *= 1000000; + usec += (ts.tv_nsec / 1000); + return (usec); +} + + +void +nni_usleep(uint64_t usec) +{ + struct timespec ts; + + ts.tv_sec = usec / 1000000; + ts.tv_nsec = (usec % 1000000) * 1000; + + /* Do this in a loop, so that interrupts don't actually wake us. */ + while (ts.tv_sec || ts.tv_nsec) { + (void) nanosleep(&ts, &ts); + } +} + +#else /* NNG_USE_GETTIMEOFDAY */ + +/* + * If you're here, its because you don't have a modern clock_gettime with + * monotonic clocks, or the necessary pthread_condattr_settclock(). In + * this case, you should be advised that *bad* things can happen if your + * system clock changes time while programs using this library are running. + * (Basically, timeouts can take longer or shorter, leading to either hangs + * or apparent spurious errors. Eventually it should all sort itself out, + * but if you change the clock by a large amount you might wonder what the + * heck is happening until it does.) + */ + +#include <pthread.h> +#include <sys/time.h> + +uint64_t +nni_clock(void) +{ + uint64_t usec; + + struct timeval tv; + if (gettimeofday(&tv, NULL) != 0) { + nni_panic("gettimeofday failed: %s", strerror(errno)); + } + + usec = tv.tv_sec; + usec *= 1000000; + usec += tv.tv_usec; + return (usec); +} + +void +nni_usleep(uint64_t usec) +{ + /* + * So probably there is no nanosleep. We could in theory use + * pthread condition variables, but that means doing memory + * allocation, or forcing the use of pthreads where the platform + * might be preferring the use of another threading package. + * Additionally, use of pthreads means that we cannot use + * relative times in a clock_settime safe manner. + * So we can use poll() instead, which is rather coarse, but + * pretty much guaranteed to work. + */ + struct pollfd pfd; + uint64_t now; + uint64_t expire; + + /* + * Possibly we could pass NULL instead of pfd, but passing a valid + * pointer ensures that if the system dereferences the pointer it + * won't come back with EFAULT. + */ + pfd.fd = -1; + pfd.events = 0; + + now = nni_clock(); + expire = now + usec; + + while (now < expire) { + /* + * In theory we could round up to a whole number of msec, + * but under the covers poll already does some rounding up, + * and the loop above guarantees that we will not bail out + * early. So this gives us a better chance to avoid adding + * nearly an extra unneeded millisecond to the wait. + */ + (void) poll(&pfd, 0, (int)((expire - now) / 1000)); + now = nni_clock(); + } +} + +#endif /* NNG_USE_GETTIMEOFDAY */ diff --git a/src/platform/posix/posix_config.h b/src/platform/posix/posix_config.h new file mode 100644 index 00000000..1e83a948 --- /dev/null +++ b/src/platform/posix/posix_config.h @@ -0,0 +1,60 @@ +/* + * Copyright 2016 Garrett D'Amore <garrett@damore.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* + * The following adjustments to the platform may be defined. These can + * be defined in either platform/config.h or loaded in via external + * defines using cmake. + * + * #define NNG_USE_GETTIMEOFDAY + * This macro is defined if you lack a working clock_gettime, + * nanosleep, or pthread_condattr_setclock. In this case the + * library uses the system clock for relative sleeps, timers, etc. + * This can be dangerous if the system clock is changed, so only + * use this if you have no other choice. If it appears that + * the system lacks clock_gettime, then it will choose this automatically. + * This value may be ignored on platforms that don't use POSIX clocks. + * + * #define NNG_USE_CLOCKID + * This macro may be defined to a different clock id (see + * clock_gettime()). By default we use CLOCK_MONOTONIC if it exists, + * or CLOCK_REALTIME otherwise. This is ignored if NNG_USE_GETTIMEOFDAY + * is defined. Platforms that don't use POSIX clocks will probably + * ignore any setting here. + * + * #define NNG_HAVE_BACKTRACE + * If your system has a working backtrace(), and backtrace_symbols(), + * along with <execinfo.h>, you can define this to get richer backtrace + * information for debugging. + */ + +#include <time.h> + +#ifndef CLOCK_REALTIME +#define NNG_USE_GETTIMEOFDAY +#elif !defined(NNG_USE_CLOCKID) +#ifdef CLOCK_MONOTONIC +#define NNG_USE_CLOCKID CLOCK_MONOTONIC +#else +#define NNG_USE_CLOCKID CLOCK_REALTIME +#endif +#endif /* CLOCK_REALTIME */ diff --git a/src/platform/posix/posix_impl.h b/src/platform/posix/posix_impl.h index 291e3f2e..89022e60 100644 --- a/src/platform/posix/posix_impl.h +++ b/src/platform/posix/posix_impl.h @@ -31,11 +31,11 @@ */ #ifdef PLATFORM_POSIX - +#include "platform/posix/posix_config.h" #include "platform/posix/posix_debug.h" #include "platform/posix/posix_alloc.h" +#include "platform/posix/posix_clock.h" #include "platform/posix/posix_synch.h" /* #include "platform/posix/posix_thread.h" */ #include "platform/posix/posix_vsnprintf.h" - #endif diff --git a/src/platform/posix/posix_synch.h b/src/platform/posix/posix_synch.h index 0eae133e..b3b15fee 100644 --- a/src/platform/posix/posix_synch.h +++ b/src/platform/posix/posix_synch.h @@ -28,7 +28,8 @@ */ /* - * POSIX synchronization (mutexes and condition variables). + * POSIX synchronization (mutexes and condition variables). This uses + * pthreads. */ #include <pthread.h> @@ -117,19 +118,73 @@ nni_mutex_tryenter(nni_mutex_t m) return (0); } + +int +cond_attr(pthread_condattr_t **attrpp) +{ +#if defined(NNG_USE_GETTIMEOFDAY) || NNG_USE_CLOCKID == CLOCK_REALTIME + *attrpp = NULL; + return (0); +#else + /* In order to make this fast, avoid reinitializing attrs. */ + static pthread_condattr_t attr; + static pthread_mutex_t mx = PTHREAD_MUTEX_INITIALIZER; + static int init = 0; + int rv; + + /* + * For efficiency's sake, we try to reuse the same attr for the + * life of the library. This avoids many reallocations. Technically + * this means that we will leak the attr on exit(), but this is + * preferable to constantly allocating and reallocating it. + */ + if (init) { + *attrpp = &attr; + return (0); + } + + (void) pthread_mutex_lock(&mx); + while (!init) { + if ((rv = pthread_condattr_init(&attr)) != 0) { + (void) pthread_mutex_unlock(&mx); + return (NNG_ENOMEM); + } + rv = pthread_condattr_setclock(&attr, NNG_USE_CLOCKID); + if (rv != 0) { + nni_panic("condattr_setclock: %s", strerror(rv)); + } + init = 1; + } + (void) pthread_mutex_unlock(&mx); + *attrpp = &attr; + return (0); +#endif +} + int nni_cond_create(nni_cond_t *cvp, nni_mutex_t mx) { + /* + * By preference, we use a CLOCK_MONOTONIC version of condition + * variables, which insulates us from changes to the system time. + */ struct nni_cond *c; + pthread_condattr_t *attrp; + int rv; + + if ((rv = cond_attr(&attrp)) != 0) { + return (rv); + } if ((c = nni_alloc(sizeof (*c))) == NULL) { return (NNG_ENOMEM); } c->mx = &mx->mx; - if (pthread_cond_init(&c->cv, NULL) != 0) { + if (pthread_cond_init(&c->cv, attrp) != 0) { /* In theory could be EAGAIN, but handle like ENOMEM */ nni_free(c, sizeof (*c)); return (NNG_ENOMEM); } + *cvp = c; return (0); } @@ -168,30 +223,15 @@ nni_cond_wait(nni_cond_t c) } int -nni_cond_timedwait(nni_cond_t c, int msec) +nni_cond_timedwait(nni_cond_t c, uint64_t usec) { struct timespec ts; int rv; - /* POSIX says clock_gettime exists since SUSv2 at least. */ - - rv = clock_gettime(CLOCK_REALTIME, &ts); - if (rv != 0) { - /* - * If the clock_gettime() is not working, its a problem with - * the platform. Arguably we could use gettimeofday instead, - * but for now we just panic(). We can fix this when someone - * finds a platform that returns ENOSYS here. - */ - nni_panic("clock_gettime failed: %s", strerror(errno)); - } - ts.tv_nsec += (msec * 1000000); + usec += nni_clock(); - /* Normalize -- its not clear if this is strictly necessary. */ - while (ts.tv_nsec > 1000000000) { - ts.tv_nsec -= 1000000000; - ts.tv_sec++; - } + ts.tv_sec = usec / 1000000; + ts.tv_nsec = (usec % 10000) * 1000; if ((rv = pthread_cond_timedwait(&c->cv, c->mx, &ts)) != 0) { if (rv == ETIMEDOUT) { |
