aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/msqueue.c111
-rw-r--r--src/core/nng_impl.h41
-rw-r--r--src/core/panic.c2
-rw-r--r--src/core/socket.c40
-rw-r--r--src/platform/platform.h31
-rw-r--r--src/platform/posix/posix_clock.h149
-rw-r--r--src/platform/posix/posix_config.h60
-rw-r--r--src/platform/posix/posix_impl.h4
-rw-r--r--src/platform/posix/posix_synch.h82
9 files changed, 489 insertions, 31 deletions
diff --git a/src/core/msqueue.c b/src/core/msqueue.c
new file mode 100644
index 00000000..3e4ade79
--- /dev/null
+++ b/src/core/msqueue.c
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+#include "../nng.h"
+
+#include "nng_impl.h"
+
+/*
+ * Message queue. These operate in some respects like Go channels,
+ * but as we have access to the internals, we have made some fundamental
+ * differences and improvements. For example, these can grow, and either
+ * side can close, and they may be closed more than once.
+ */
+
+struct nni_msgqueue {
+ nni_mutex_t mq_lock;
+ nni_cond_t mq_readable;
+ nni_cond_t mq_writeable;
+ int mq_cap;
+ int mq_len;
+ int mq_get;
+ int mq_put;
+ int mq_closed;
+ nng_msg_t *mq_msgs;
+};
+
+int
+nni_msgqueue_create(nni_msgqueue_t *mqp, int cap)
+{
+ struct nni_msgqueue *mq;
+ int rv;
+
+ if (cap < 1) {
+ return (NNG_EINVAL);
+ }
+ if ((mq = nni_alloc(sizeof (*mq))) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ if ((rv = nni_mutex_create(&mq->mq_lock)) != 0) {
+ nni_free(mq, sizeof (*mq));
+ return (rv);
+ }
+ if ((rv = nni_cond_create(&mq->mq_readable, mq->mq_lock)) != 0) {
+ nni_mutex_destroy(mq->mq_lock);
+ nni_free(mq, sizeof (*mq));
+ return (NNG_ENOMEM);
+ }
+ if ((rv = nni_cond_create(&mq->mq_writeable, mq->mq_lock)) != 0) {
+ nni_cond_destroy(mq->mq_readable);
+ nni_mutex_destroy(mq->mq_lock);
+ return (NNG_ENOMEM);
+ }
+ if ((mq->mq_msgs = nni_alloc(sizeof (nng_msg_t) * cap)) == NULL) {
+ nni_cond_destroy(mq->mq_writeable);
+ nni_cond_destroy(mq->mq_readable);
+ nni_mutex_destroy(mq->mq_lock);
+ return (NNG_ENOMEM);
+ }
+
+ mq->mq_cap = cap;
+ mq->mq_len = 0;
+ mq->mq_get = 0;
+ mq->mq_put = 0;
+ mq->mq_closed = 0;
+ *mqp = mq;
+
+ return (0);
+}
+
+void
+nni_msgqueue_destroy(nni_msgqueue_t mq)
+{
+ nng_msg_t msg;
+
+ nni_cond_destroy(mq->mq_writeable);
+ nni_cond_destroy(mq->mq_readable);
+ nni_mutex_destroy(mq->mq_lock);
+
+ /* Free any orphaned messages. */
+ while (mq->mq_len > 0) {
+ msg = mq->mq_msgs[mq->mq_get];
+ mq->mq_get++;
+ if (mq->mq_get > mq->mq_cap) {
+ mq->mq_get = 0;
+ }
+ mq->mq_len--;
+ nng_msg_free(msg);
+ }
+
+ nni_free(mq->mq_msgs, mq->mq_cap * sizeof (nng_msg_t));
+ nni_free(mq, sizeof (*mq));
+}
diff --git a/src/core/nng_impl.h b/src/core/nng_impl.h
index a25b87a4..a62ac101 100644
--- a/src/core/nng_impl.h
+++ b/src/core/nng_impl.h
@@ -61,4 +61,45 @@ extern void nni_snprintf(char *, size_t, const char *, ...);
*/
extern void nni_panic(const char *, ...);
+/*
+ * Message queues. Message queues work in some ways like Go channels;
+ * they are a thread-safe way to pass messages between subsystems.
+ */
+typedef struct nni_msgqueue *nni_msgqueue_t;
+
+/*
+ * nni_msgqueue_create creates a message queue with the given capacity,
+ * which must be a positive number. It returns NNG_EINVAL if the capacity
+ * is invalid, or NNG_ENOMEM if resources cannot be allocated.
+ */
+extern int nni_msgqueue_create(nni_msgqueue_t *, int);
+
+/*
+ * nni_msgqueue_destroy destroys a message queue. It will also free any
+ * messages that may be in the queue.
+ */
+extern void nni_msgqueue_destroy(nni_msgqueue_t);
+
+extern int nni_msgqueue_len(nni_msgqueue_t);
+extern int nni_msgqueue_cap(nni_msgqueue_t);
+
+/*
+ * nni_msgqueue_put attempts to put a message to the queue. It will wait
+ * for the timeout (us), if the value is positive. If the value is negative
+ * then it will wait forever. If the value is zero, it will just check, and
+ * return immediately whether a message can be put or not. Valid returns are
+ * NNG_ECLOSED if the queue is closed or NNG_ETIMEDOUT if the message cannot
+ * be placed after a time, or NNG_EAGAIN if the operation cannot succeed
+ * immediately and a zero timeout is specified. Note that timeout granularity
+ * may be limited -- for example Windows systems have a millisecond resolution
+ * timeout capability.
+ */
+extern int nni_msgqueue_put(nni_msgqueue_t, nng_msg_t, int);
+
+/*
+ * nni_msgqueue_get gets the message from the queue, using a timeout just
+ * like nni_msgqueue_put.
+ */
+extern int nni_msgqueue_get(nni_msgqueue_t, nng_msg_t *, int);
+
#endif /* NNG_IMPL_H */
diff --git a/src/core/panic.c b/src/core/panic.c
index e993ddb9..60c790ac 100644
--- a/src/core/panic.c
+++ b/src/core/panic.c
@@ -28,8 +28,6 @@
#include <execinfo.h>
#endif
-#include "nng.h"
-
#include "nng_impl.h"
/*
diff --git a/src/core/socket.c b/src/core/socket.c
new file mode 100644
index 00000000..247c5640
--- /dev/null
+++ b/src/core/socket.c
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#include "../nng.h"
+
+#include "nng_impl.h"
+
+/*
+ * Socket implementation.
+ */
+
+struct nng_socket {
+ int s_proto;
+ nni_mutex_t s_mx;
+
+ /* uwq */
+ /* urq */
+ /* options */
+ /* pipes */
+ /* endpoints */
+};
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) {