text: /*
   * Copyright (c) 2013 The Native Client Authors. All rights reserved.
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  #include <errno.h>
  #include <pthread.h>
  #include <stdio.h>
  #include <time.h>
  
  #include "native_client/src/include/nacl_assert.h"
  
  
  static pthread_mutex_t mutex;
  volatile int thread_has_lock = 0;
  volatile int thread_should_acquire_lock = 0;
  volatile int thread_should_release_lock = 0;
  
  void *locking_thread(void *unused) {
    for (;;) {
      while (!thread_should_acquire_lock) { /* Spin. */ }
  
      ASSERT_EQ(thread_has_lock, 0);
      int rc = pthread_mutex_lock(&mutex);
      ASSERT_EQ(rc, 0);
      __sync_fetch_and_add(&thread_has_lock, 1);
  
      while (!thread_should_release_lock) { /* Spin. */ }
  
      ASSERT_EQ(thread_has_lock, 1);
      rc = pthread_mutex_unlock(&mutex);
      ASSERT_EQ(rc, 0);
      __sync_fetch_and_sub(&thread_has_lock, 1);
    }
  
    return NULL;
  }
  
  void tell_thread_to_acquire_lock(void) {
    fprintf(stderr, "Thread acquiring lock.\n");
  
    ASSERT_EQ(thread_has_lock, 0);
    ASSERT_EQ(thread_should_acquire_lock, 0);
    __sync_fetch_and_add(&thread_should_acquire_lock, 1);
  
    while (!thread_has_lock) { /* Spin. */ }
  
    __sync_fetch_and_sub(&thread_should_acquire_lock, 1);
    ASSERT_EQ(thread_should_acquire_lock, 0);
  
    fprintf(stderr, "Thread acquired lock.\n");
  }
  
  void tell_thread_to_release_lock(void) {
    fprintf(stderr, "Thread releasing lock.\n");
  
    ASSERT_EQ(thread_has_lock, 1);
    ASSERT_EQ(thread_should_release_lock, 0);
    __sync_fetch_and_add(&thread_should_release_lock, 1);
  
    while (thread_has_lock) { /* Spin. */ }
  
    __sync_fetch_and_sub(&thread_should_release_lock, 1);
    ASSERT_EQ(thread_should_release_lock, 0);
  
    fprintf(stderr, "Thread released lock.\n");
  }
  
  void add_nanoseconds(struct timespec *time, unsigned int nanoseconds) {
    ASSERT_LE(nanoseconds, 1000000000);
    time->tv_nsec += nanoseconds;
    if (time->tv_nsec > 1000000000) {
      time->tv_nsec -= 1000000000;
      time->tv_sec += 1;
    }
  }
  
  void test_already_locked_with_zero_timestamp(void) {
    int rc;
    struct timespec abstime = { 0, 0 };
    tell_thread_to_acquire_lock();
    fprintf(stderr, "Trying to lock the already-locked mutex for a valid "
            "zero absolute timestamp. "
            "Expected to expire since the lock is taken.\n");
    rc = pthread_mutex_timedlock(&mutex, &abstime);
    ASSERT_EQ(rc, ETIMEDOUT);
    tell_thread_to_release_lock();
  }
  
  void test_already_locked_with_non_zero_timestamp(void) {
    int rc;
    struct timespec abstime = { 0, 0 };
    tell_thread_to_acquire_lock();
    fprintf(stderr, "Trying to lock the already-locked mutex for a valid "
            "non-zero absolute timestamp. "
            "Expected to expire since the lock is taken.\n");
    rc = clock_gettime(CLOCK_REALTIME, &abstime);
    ASSERT_EQ(rc, 0);
    add_nanoseconds(&abstime, 10000);
    rc = pthread_mutex_timedlock(&mutex, &abstime);
    ASSERT_EQ(rc, ETIMEDOUT);
    tell_thread_to_release_lock();
  }
  
  void test_already_locked_with_negative_timestamp(void) {
    int rc;
    struct timespec abstime = { 0, -10000 };
    tell_thread_to_acquire_lock();
    fprintf(stderr, "Trying to lock the already-locked mutex for an invalid "
            "negative absolute timestamp. "
            "Expected to be invalid.\n");
    rc = pthread_mutex_timedlock(&mutex, &abstime);
    ASSERT_EQ(rc, EINVAL);
    tell_thread_to_release_lock();
  }
  
  void test_already_locked_with_too_large_timestamp(void) {
    int rc;
    struct timespec abstime = { 0, 2000000000 };
    tell_thread_to_acquire_lock();
    fprintf(stderr, "Trying to lock the already-locked mutex for an invalid "
            "too-large absolute timestamp. "
            "Expected to be invalid.\n");
    rc = pthread_mutex_timedlock(&mutex, &abstime);
    ASSERT_EQ(rc, EINVAL);
    tell_thread_to_release_lock();
  }
  
  void test_unlocked_with_zero_timestamp(void) {
    int rc;
    struct timespec abstime = { 0, 0 };
    ASSERT_EQ(thread_has_lock, 0);
    fprintf(stderr, "Trying to lock the unlocked mutex with a valid "
            "zero absolute timestamp. "
            "Expected to succeed instantly since the lock is free.\n");
    rc = pthread_mutex_timedlock(&mutex, &abstime);
    ASSERT_EQ(rc, 0);
    rc = pthread_mutex_unlock(&mutex);
    ASSERT_EQ(rc, 0);
  }
  
  void test_unlocked_with_non_zero_timestamp(void) {
    int rc;
    struct timespec abstime = { 0, 0 };
    ASSERT_EQ(thread_has_lock, 0);
    fprintf(stderr, "Trying to lock the unlocked mutex with a valid "
            "non-zero absolute timestamp. "
            "Expected to succeed instantly since the lock is free.\n");
    rc = clock_gettime(CLOCK_REALTIME, &abstime);
    ASSERT_EQ(rc, 0);
    add_nanoseconds(&abstime, 10000);
    rc = pthread_mutex_timedlock(&mutex, &abstime);
    ASSERT_EQ(rc, 0);
    rc = pthread_mutex_unlock(&mutex);
    ASSERT_EQ(rc, 0);
  }
  
  int main(int argc, char **argv) {
    int rc;
    fprintf(stderr, "Running...\n");
  
    pthread_mutexattr_t mta;
    rc = pthread_mutexattr_init(&mta);
    ASSERT_EQ(rc, 0);
    rc = pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_ERRORCHECK);
    ASSERT_EQ(rc, 0);
    rc = pthread_mutex_init(&mutex, &mta);
    ASSERT_EQ(rc, 0);
    rc = pthread_mutexattr_destroy(&mta);
    ASSERT_EQ(rc, 0);
  
    pthread_t thread;
    rc = pthread_create(&thread, NULL, locking_thread, NULL);
    ASSERT_EQ(rc, 0);
    fprintf(stderr, "Thread started.\n");
  
    test_already_locked_with_zero_timestamp();
    test_already_locked_with_non_zero_timestamp();
    test_already_locked_with_negative_timestamp();
    test_already_locked_with_too_large_timestamp();
    test_unlocked_with_zero_timestamp();
    test_unlocked_with_non_zero_timestamp();
  
    fprintf(stderr, "Done.\n");
    return 0;
  }
  