#define _MULTI_THREADED
#include <pthread.h>
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/time.h>
#include <unistd.h>

float tdiff (struct timeval *start, struct timeval *end) {
    return 1e6*(end->tv_sec-start->tv_sec) +(end->tv_usec - start->tv_usec);
}

/* Simple function to check the return code and exit the program
   if the function call failed
   */
static void compResults(char *string, int rc) {
  if (rc) {
    printf("Error on : %s, rc=%d",
           string, rc);
    exit(EXIT_FAILURE);
  }
  return;
}

pthread_rwlock_t       rwlock = PTHREAD_RWLOCK_INITIALIZER;

void *rdlockThread(void *arg __attribute__((unused)))
{
  int             rc;
  int             count=0;

  struct timeval start, end;

  printf("Entered thread, getting read lock with mp wait\n");
  Retry:

  gettimeofday(&start, 0);
  rc = pthread_rwlock_tryrdlock(&rwlock);
  gettimeofday(&end, 0);
  printf("pthread_rwlock_tryrdlock took %9.3fus\n", tdiff(&start,&end));
  if (rc == EBUSY) {
    if (count >= 10) {
      printf("Retried too many times, failure!\n");

      exit(EXIT_FAILURE);
    }
    ++count;
    printf("Could not get lock, do other work, then RETRY...\n");
    sleep(1);
    goto Retry;
  }
  compResults("pthread_rwlock_tryrdlock() 1\n", rc);

  sleep(2);

  printf("unlock the read lock\n");
  gettimeofday(&start, 0);
  rc = pthread_rwlock_unlock(&rwlock);
  gettimeofday(&end, 0);
  compResults("pthread_rwlock_unlock()\n", rc);
  printf("%lu.%6lu to %lu.%6lu is %9.2f\n", start.tv_sec, start.tv_usec, end.tv_sec, end.tv_usec, tdiff(&start, &end));

  printf("Secondary thread complete\n");
  return NULL;
}

int main(int argc __attribute__((unused)), char **argv)
{
  int                   rc=0;
  pthread_t             thread;
  struct timeval start, end;

  printf("Enter Testcase - %s\n", argv[0]);

  gettimeofday(&start, 0);
  gettimeofday(&end, 0);
  printf("nop Took %9.2f\n", tdiff(&start, &end));

  {
      int N=1000;
      int i;
      printf("Main, get and release the write lock %d times\n", N);
      gettimeofday(&start, 0);
      for (i=0; i<N; i++) {
	  rc = pthread_rwlock_wrlock(&rwlock);
	  rc = pthread_rwlock_unlock(&rwlock);
      }
      gettimeofday(&end, 0);
      compResults("pthread_rwlock_wrlock()\n", rc);
      printf("Took %9.2fns/op\n", 1000*tdiff(&start, &end)/N);
  }

  printf("Main, get the write lock\n");
  gettimeofday(&start, 0);
  rc = pthread_rwlock_wrlock(&rwlock);
  gettimeofday(&end, 0);
  compResults("pthread_rwlock_wrlock()\n", rc);
  printf("Took %9.2f\n", tdiff(&start, &end));

  printf("Main, create the try read lock thread\n");
  rc = pthread_create(&thread, NULL, rdlockThread, NULL);
  compResults("pthread_create\n", rc);

  printf("Main, wait a bit holding the write lock\n");
  sleep(5);

  printf("Main, Now unlock the write lock\n");
  gettimeofday(&start, 0);
  rc = pthread_rwlock_unlock(&rwlock);
  gettimeofday(&end, 0);
  compResults("pthread_rwlock_unlock()\n", rc);
  printf("Took %9.2f\n", tdiff(&start, &end));

  printf("Main, wait for the thread to end\n");
  rc = pthread_join(thread, NULL);
  compResults("pthread_join\n", rc);

  rc = pthread_rwlock_destroy(&rwlock);
  compResults("pthread_rwlock_destroy()\n", rc);
  printf("Main completed\n");
  return 0;
}