]> git.saurik.com Git - apple/libpthread.git/blobdiff - tests/cond_timed.c
libpthread-330.230.1.tar.gz
[apple/libpthread.git] / tests / cond_timed.c
index 9e4249d41baeb4ea108c947b8f15d24ce09b5a19..3f3c4e7a1d6ced1e7566802e86bafc1eeaf4baba 100644 (file)
@@ -10,7 +10,7 @@
 #include <libkern/OSAtomic.h>
 #include <dispatch/dispatch.h>
 
-#include <darwintest.h>
+#include "darwintest_defaults.h"
 
 #define NUM_THREADS 8
 
@@ -88,12 +88,12 @@ T_DECL(cond_timedwait_timeout, "pthread_cond_timedwait() timeout")
        usleep((useconds_t) uexpected);
        bool loop = true;
        while (loop) {
-               T_ASSERT_POSIX_ZERO(pthread_mutex_lock(&context.mutex),
+               T_QUIET; T_ASSERT_POSIX_ZERO(pthread_mutex_lock(&context.mutex),
                                                        "pthread_mutex_lock");
                if (context.count <= 0) {
                        loop = false;
                }
-               T_ASSERT_POSIX_ZERO(pthread_mutex_unlock(&context.mutex),
+               T_QUIET; T_ASSERT_POSIX_ZERO(pthread_mutex_unlock(&context.mutex),
                                                        "pthread_mutex_unlock");
        }
 
@@ -107,3 +107,181 @@ T_DECL(cond_timedwait_timeout, "pthread_cond_timedwait() timeout")
                        ((uint64_t) start.tv_sec * USEC_PER_SEC + (uint64_t) start.tv_usec);
        T_LOG("waittime actual:   %llu us", uelapsed);
 }
+
+struct prodcons_context {
+       pthread_cond_t cond;
+       pthread_mutex_t mutex;
+       bool consumer_ready;
+       bool workitem_available;
+       bool padding[6];
+};
+
+static void *consumer_thread(void *ptr) {
+       struct prodcons_context *context = ptr;
+
+       // tell producer thread that we are ready
+       T_ASSERT_POSIX_ZERO(pthread_mutex_lock(&context->mutex), "pthread_mutex_lock");
+
+       context->consumer_ready = true;
+       T_ASSERT_POSIX_ZERO(pthread_cond_signal(&context->cond), "pthread_cond_signal");
+
+       // wait for a work item to become available
+       do {
+               // mutex will be dropped and allow producer thread to acquire
+               T_ASSERT_POSIX_ZERO(pthread_cond_wait(&context->cond, &context->mutex), "pthread_cond_wait");
+
+               // loop in case of spurious wakeups
+       } while (context->workitem_available == false);
+
+       // work item has been sent, so dequeue it and tell producer
+       context->workitem_available = false;
+       T_ASSERT_POSIX_ZERO(pthread_cond_signal(&context->cond), "pthread_cond_signal");
+
+       // unlock mutex, we are done here
+       T_ASSERT_POSIX_ZERO(pthread_mutex_unlock(&context->mutex), "pthread_mutex_unlock");
+
+       T_PASS("Consumer thread exiting");
+
+       return NULL;
+}
+
+#define TESTCASE_TIMEOUT (10) /* seconds */
+typedef enum {
+       eNullTimeout,
+       eZeroTimeout,
+       eBeforeEpochTimeout,
+       eRecentPastTimeout
+} TimeOutType;
+
+static DT_TEST_RETURN cond_timedwait_timeouts_internal(TimeOutType timeout, bool relative);
+
+T_DECL(cond_timedwait_nulltimeout, "pthread_cond_timedwait() with NULL timeout, ensure mutex is unlocked")
+{
+       cond_timedwait_timeouts_internal(eNullTimeout, false);
+}
+
+T_DECL(cond_timedwait_zerotimeout, "pthread_cond_timedwait() with zero timeout, ensure mutex is unlocked")
+{
+       cond_timedwait_timeouts_internal(eZeroTimeout, false);
+}
+
+T_DECL(cond_timedwait_beforeepochtimeout, "pthread_cond_timedwait() with timeout before the epoch, ensure mutex is unlocked")
+{
+       cond_timedwait_timeouts_internal(eBeforeEpochTimeout, false);
+}
+
+T_DECL(cond_timedwait_pasttimeout, "pthread_cond_timedwait() with timeout in the past, ensure mutex is unlocked")
+{
+       cond_timedwait_timeouts_internal(eRecentPastTimeout, false);
+}
+
+T_DECL(cond_timedwait_relative_nulltimeout, "pthread_cond_timedwait_relative_np() with relative NULL timeout, ensure mutex is unlocked")
+{
+       cond_timedwait_timeouts_internal(eNullTimeout, true);
+}
+
+T_DECL(cond_timedwait_relative_pasttimeout, "pthread_cond_timedwait_relative_np() with relative timeout in the past, ensure mutex is unlocked")
+{
+       cond_timedwait_timeouts_internal(eRecentPastTimeout, true);
+}
+
+static DT_TEST_RETURN cond_timedwait_timeouts_internal(TimeOutType timeout, bool relative)
+{
+       // This testcase mimics a producer-consumer model where the consumer checks
+       // in and waits until work becomes available. The producer then waits until
+       // the work has been consumed and the consumer quiesces. Since condition
+       // variables may have spurious wakeups, the timeout should not matter,
+       // but there have been functional issues where the mutex would not be unlocked
+       // for a timeout in the past.
+       struct prodcons_context context = {
+               .cond = PTHREAD_COND_INITIALIZER,
+               .mutex = PTHREAD_MUTEX_INITIALIZER,
+               .consumer_ready = false,
+               .workitem_available = false
+       };
+
+       struct timeval test_timeout;
+       gettimeofday(&test_timeout, NULL);
+       test_timeout.tv_sec += TESTCASE_TIMEOUT;
+
+       T_ASSERT_POSIX_ZERO(pthread_mutex_lock(&context.mutex), "pthread_mutex_lock");
+
+       pthread_t p;
+       T_ASSERT_POSIX_ZERO(pthread_create(&p, NULL, consumer_thread, &context),
+                                                       "pthread_create");
+
+       // Wait until consumer thread is able to acquire the mutex, check in, and block
+       // in its own condition variable. We do not want to start generating work before
+       // the consumer thread is available
+       do {
+               // mutex will be dropped and allow consumer thread to acquire
+               T_ASSERT_POSIX_ZERO(pthread_cond_wait(&context.cond, &context.mutex), "pthread_cond_wait");
+
+               // loop in case of spurious wakeups
+       } while (context.consumer_ready == false);
+
+       // consumer is ready and blocked in its own condition variable, and
+       // producer has mutex acquired. Send a work item and wait for it
+       // to be dequeued
+
+       context.workitem_available = true;
+       T_ASSERT_POSIX_ZERO(pthread_cond_signal(&context.cond), "pthread_cond_signal");
+
+       do {
+               struct timeval now;
+
+               gettimeofday(&now, NULL);
+               T_QUIET; T_ASSERT_TRUE(timercmp(&now, &test_timeout, <), "timeout reached waiting for consumer thread to consume");
+
+               struct timespec ts;
+
+               if (relative) {
+                       switch (timeout) {
+                               case eNullTimeout:
+                                       break;
+                               case eRecentPastTimeout:
+                                       ts.tv_sec = -1;
+                                       ts.tv_nsec = 0;
+                                       break;
+                               case eZeroTimeout:
+                               case eBeforeEpochTimeout:
+                                       break;
+                       }
+               } else {
+                       switch (timeout) {
+                               case eNullTimeout:
+                                       break;
+                               case eZeroTimeout:
+                                       ts.tv_sec = 0;
+                                       ts.tv_nsec = 0;
+                                       break;
+                               case eBeforeEpochTimeout:
+                                       ts.tv_sec = -1;
+                                       ts.tv_nsec = 0;
+                                       break;
+                               case eRecentPastTimeout:
+                                       ts.tv_sec = now.tv_sec - 1;
+                                       ts.tv_nsec = now.tv_usec / 1000;
+                                       break;
+                       }
+               }
+
+               int ret;
+               if (relative) {
+                       ret = pthread_cond_timedwait_relative_np(&context.cond, &context.mutex, timeout == eNullTimeout ? NULL : &ts);
+               } else {
+                       ret = pthread_cond_timedwait(&context.cond, &context.mutex, timeout == eNullTimeout ? NULL : &ts);
+               }
+               if (ret != 0 && ret != EINTR && ret != ETIMEDOUT) T_ASSERT_POSIX_ZERO(ret, "timedwait returned error");
+
+               usleep(10*1000); // avoid spinning in a CPU-bound loop
+
+               // loop in case of spurious wakeups
+       } while (context.workitem_available == true);
+
+       T_ASSERT_POSIX_ZERO(pthread_mutex_unlock(&context.mutex), "pthread_mutex_unlock");
+
+       T_ASSERT_POSIX_ZERO(pthread_join(p, NULL), "pthread_join");
+
+       T_PASS("Consumer completed work");
+}