/*
 * Copyright (c) 2012 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.
 */

/*
 * NaCl Server Runtime mutex and condition variable abstraction layer.
 * This is the host-OS-independent interface.
 */
#ifndef NATIVE_CLIENT_SRC_SHARED_PLATFORM_NACL_SYNC_H_
#define NATIVE_CLIENT_SRC_SHARED_PLATFORM_NACL_SYNC_H_

#if defined(__native_client__) || NACL_LINUX || NACL_OSX
# include <pthread.h>
# include "native_client/src/shared/platform/posix/nacl_fast_mutex.h"
#elif NACL_WINDOWS
# include "native_client/src/shared/platform/win/nacl_fast_mutex.h"
#else
# error "What OS?"
#endif
#include "native_client/src/include/nacl_compiler_annotations.h"
#include "native_client/src/include/nacl_base.h"
#include "native_client/src/trusted/service_runtime/include/machine/_types.h"

EXTERN_C_BEGIN

// It is very difficult to get a definition of nacl_abi_timespec inside of a
// native client compile.  Sidestep this issue for now.
// TODO(bsy,sehr): change the include guards on time.h to allow both defines.
#ifdef __native_client__
// In a native client module timespec and nacl_abi_timespec are the same.
typedef struct timespec NACL_TIMESPEC_T;
#else
// In trusted code time.h is not derived from
// src/trusted/service_runtime/include/sys/time.h, so there is no conflict.
typedef struct nacl_abi_timespec NACL_TIMESPEC_T;
#endif

struct NaClMutex {
#if defined(__native_client__) || NACL_LINUX || NACL_OSX
  pthread_mutex_t mu;
  int held;
#elif NACL_WINDOWS
  void* lock;
  int held;
  /*
   * Windows lock implementation is recursive -- use a boolean to
   * convert to a binary mutex.
   */
#else
# error "What OS?"
#endif
};

struct NaClCondVar {
#if defined(__native_client__) || NACL_LINUX || NACL_OSX
  pthread_cond_t cv;
#elif NACL_WINDOWS
  void* cv;
#else
# error "What OS?"
#endif
};

struct nacl_abi_timespec;

typedef enum NaClSyncStatus {
  NACL_SYNC_OK,
  NACL_SYNC_INTERNAL_ERROR,
  NACL_SYNC_BUSY,
  NACL_SYNC_MUTEX_INVALID,
  NACL_SYNC_MUTEX_DEADLOCK,
  NACL_SYNC_MUTEX_PERMISSION,
  NACL_SYNC_MUTEX_INTERRUPTED,
  NACL_SYNC_CONDVAR_TIMEDOUT,
  NACL_SYNC_CONDVAR_INTR,
  NACL_SYNC_SEM_INTERRUPTED,
  NACL_SYNC_SEM_RANGE_ERROR
} NaClSyncStatus;

int NaClMutexCtor(struct NaClMutex *mp) NACL_WUR;  /* bool success/fail */

void NaClMutexDtor(struct NaClMutex *mp);

NaClSyncStatus NaClMutexLock(struct NaClMutex *mp) NACL_WUR;

NaClSyncStatus NaClMutexTryLock(struct NaClMutex *mp) NACL_WUR;

NaClSyncStatus NaClMutexUnlock(struct NaClMutex *mp) NACL_WUR;


int NaClCondVarCtor(struct NaClCondVar *cvp) NACL_WUR;

void NaClCondVarDtor(struct NaClCondVar *cvp);

NaClSyncStatus NaClCondVarSignal(struct NaClCondVar *cvp) NACL_WUR;

NaClSyncStatus NaClCondVarBroadcast(struct NaClCondVar *cvp) NACL_WUR;

NaClSyncStatus NaClCondVarWait(struct NaClCondVar *cvp,
                               struct NaClMutex   *mp) NACL_WUR;

NaClSyncStatus NaClCondVarTimedWaitRelative(
    struct NaClCondVar              *cvp,
    struct NaClMutex                *mp,
    NACL_TIMESPEC_T const           *reltime) NACL_WUR;

NaClSyncStatus NaClCondVarTimedWaitAbsolute(
    struct NaClCondVar              *cvp,
    struct NaClMutex                *mp,
    NACL_TIMESPEC_T const           *abstime) NACL_WUR;


/*
 * "Fast" binary mutex lock that does not work with condition
 * variables.  This may be implemented using spinlocks, normal POSIX
 * mutexes (where futex is used so uncontended locks are fast), or
 * CRITICAL_SECTION (on Windows).
 *
 * NOT FOR USE WHEN THE LOCK MIGHT BE HELD FOR AN EXTENDED PERIOD.
 *
 * Consider what might happen if this were implemented with a spinlock
 * and the lock is held for a large fraction of a scheduling quanta.
 * The probability of the thread holding the lock getting interrupted
 * by the scheduler becomes non-negligible.  If there are other
 * runnable threads, then the interrupted thread might be descheduled
 * while holding the spinlock to let some other thread run.  What
 * would happen -- especially if there is lock contention -- when
 * another thread (possibly the very one that was just scheduled to
 * run) tries to take the spinlock?  That the second (and any other
 * subsequent) thread will spin for its entire scheduling quantum,
 * wasting all the CPU cycles in the time quantum.  On a multicore /
 * multiprocessor machine, if the ratio of the number of runnable
 * threads to the number of available cores is greater than 1.0 -- so
 * that scheduling quanta expiration often leads to thread switch -- a
 * contended lock that is held for a long time would cause noticeable
 * performance degradation due to cores spinning doing essentially
 * nothing -- waiting for the spinlock to be free.  In that case, a
 * blocking lock should be used instead.
 *
 * The probability of a thread being descheduled while holding a
 * spinlock depends on many factors, such as the size of the
 * scheduling quantum.  Performing a syscall while holding a spinlock
 * is generally a bad idea, since syscalls often block, and even if
 * they are guaranteed to be nonblocking, some kernel designs like to
 * context switch to other threads since most of the context switch
 * overhead has already been paid (e.g., if the thread is close to the
 * end of its scheduling quantum).
 */
struct NaClFastMutex;

int NaClFastMutexCtor(struct NaClFastMutex *flp) NACL_WUR;

void NaClFastMutexDtor(struct NaClFastMutex *flp);

void NaClFastMutexLock(struct NaClFastMutex *flp);

int NaClFastMutexTryLock(struct NaClFastMutex *flp) NACL_WUR;

void NaClFastMutexUnlock(struct NaClFastMutex *flp);

EXTERN_C_END


#endif  /* NATIVE_CLIENT_SRC_SHARED_PLATFORM_NACL_SYNC_H_ */
