/*
 * nasd_threadstuff.c
 *
 * Generic threading stuff
 *
 * Authors: Jim Zelenka, Marc Unangst, Nat Lanza
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1995,1996,1997,1998,1999,2000.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */


#include <nasd/nasd_options.h>
#include <nasd/nasd_itypes.h>
#include <nasd/nasd_threadstuff.h>
#include <nasd/nasd_general.h>
#include <nasd/nasd_common.h>

/*
 * Shared stuff
 */

NASD_DECLARE_ONCE(nasd_threads_init_once)
NASD_SYS_DECLARE_MUTEX(nasd_threads_use_counter_lock)
int nasd_threads_use_counter;

#if defined(LINUX) || defined(FREEBSD) || defined(SOLARIS) || defined(IRIX) || defined(NETBSD)
#define NASD_ATOMIC_GLOBAL_MUTEX 1
NASD_SYS_DECLARE_MUTEX(nasd_atomic_global_mutex)
#endif /* LINUX || FREEBSD || SOLARIS || IRIX */

int nasd_disable_threads_try_lock_test = 0;

/*
 * nasd_threads_real_init
 *
 * Called when the system comes into use after not being
 * used (start-of-day call to nasd_threads_init(), or first
 * such call after the last call to nasd_threads_shutdown()
 * which actually deactivated the system).
 *
 * I got really sick of different implementations screwing
 * up the return value from NASD_SYS_TRY_LOCK_MUTEX, so
 * I added some checks here.
 */
nasd_status_t nasd_threads_real_init(void) {
  NASD_DECLARE_MUTEX(m)
  nasd_status_t rc;
  int ret;

  NASD_ASSERT(nasd_threads_use_counter == 1);

  rc = nasd_sys_threads_init();
  if (rc) { return(rc); }

#if NASD_DEBUG_ATOMIC == 0
  /* these checks break when we're debugging mutexes. */
  if (nasd_disable_threads_try_lock_test == 0) {
    rc = nasd_mutex_init(&m);
    if (rc) { return(rc); }

    ret = NASD_TRY_LOCK_MUTEX(m);
    if (ret == 0) {
      nasd_mutex_destroy(&m);
      return(NASD_TRY_LOCK_BAD_IMPL);
    }

    ret = NASD_TRY_LOCK_MUTEX(m);
    if (ret == 1) {
      nasd_mutex_destroy(&m);
      return(NASD_TRY_LOCK_BAD_IMPL);
    }

    NASD_UNLOCK_MUTEX(m);

    rc = nasd_mutex_destroy(&m);
    if (rc) { return(rc); }
  }
#endif /* NASD_DEBUG_ATOMIC == 0 */

  return(NASD_SUCCESS);
}


/*
 * nasd_threads_real_shutdown
 *
 * Called when last user of the threads system calls nasd_threads_shutdown().
 * Clean up and deallocate resources.
 */
void nasd_threads_real_shutdown(void) {
  NASD_ASSERT(nasd_threads_use_counter == 0);
  nasd_sys_threads_shutdown();
  nasd_sys_shutdown();
}


/*
 * nasd_threadsys_init
 *
 * Executed exactly once, the first time nasd_threads_init() is
 * called. Initialize counter tracking number of times system
 * is initted.
 */
void nasd_threadsys_init(void) {
  nasd_status_t rc;

  rc = nasd_sys_init();
  if (rc) { NASD_PANIC(); }

  rc = nasd_sys_threads_init_once();
  if (rc) { NASD_PANIC(); }

  rc = nasd_sys_mutex_init(&nasd_threads_use_counter_lock);
  if (rc) { NASD_PANIC(); }

#if NASD_ATOMIC_GLOBAL_MUTEX > 0
  rc = nasd_sys_mutex_init(&nasd_atomic_global_mutex);
  if (rc) { NASD_PANIC(); }
#endif /* NASD_ATOMIC_GLOBAL_MUTEX > 0 */

  nasd_threads_use_counter = 0;
}


/*
 * nasd_threads_init
 *
 * Keep a counter of the number of times we're initted and
 * shutdown. When the last shutdown arrives, really deallocate.
 * This lets multiple subsystems use us without knowing about
 * one another.
 */
nasd_status_t nasd_threads_init(void) {
  nasd_status_t rc;

  nasd_once(&nasd_threads_init_once, nasd_threadsys_init);
  NASD_SYS_LOCK_MUTEX(nasd_threads_use_counter_lock);
  nasd_threads_use_counter++;

  if (nasd_threads_use_counter == 1) {
    rc = nasd_threads_real_init();
    if (rc) {
      nasd_threads_use_counter = 0;
      nasd_threads_real_shutdown();
    }
  } else {
    rc = NASD_SUCCESS;
  }

  NASD_SYS_UNLOCK_MUTEX(nasd_threads_use_counter_lock);

  return(rc);
}


/*
 * nasd_threads_shutdown
 *
 * Previous caller of nasd_threads_init() not using the threads
 * subsystem any more. Deallocate and cleanup iff necessary.
 */
void nasd_threads_shutdown(void) {
  NASD_ASSERT(nasd_threads_use_counter != 0);

  NASD_SYS_LOCK_MUTEX(nasd_threads_use_counter_lock);

  nasd_threads_use_counter--;
  if (nasd_threads_use_counter == 0) { nasd_threads_real_shutdown(); }

  NASD_SYS_UNLOCK_MUTEX(nasd_threads_use_counter_lock);
}


nasd_status_t _nasd_destroy_threadgroup(nasd_threadgroup_t  *g,
                                        char                *file,
                                        int                  line) {
  nasd_status_t rc1, rc2;

  rc1 = nasd_mutex_destroy(&g->mutex);
  rc2 = nasd_cond_destroy(&g->cond);

  if (rc1) { return(rc1); }
  return(rc2);
}


nasd_status_t _nasd_init_threadgroup(nasd_threadgroup_t  *g,
                                     char                *file,
                                     int                  line) {
  nasd_status_t rc;

  rc = nasd_mutex_init(&g->mutex);
  if (rc) { return(rc); }

  rc = nasd_cond_init(&g->cond);
  if (rc) {
    nasd_mutex_destroy(&g->mutex);
    return(rc);
  }

#if NASD_DEBUG_ATOMIC > 0
  /* we want better trace information */
  g->mutex->file = file;
  g->mutex->line = line;
  g->cond->file = file;
  g->cond->line = line;
#endif /* NASD_DEBUG_ATOMIC > 0 */

  g->created = g->running = g->shutdown = g->should_shutdown = 0;
  return(NASD_SUCCESS);
}


#if NASD_DEBUG_ATOMIC > 0

static NASD_ATEnt_t nasd_atent_list;
static NASD_ATEnt_t *nasd_atent_done_list = NULL;

NASD_SYS_DECLARE_MUTEX(nasd_atent_mutex)
NASD_DECLARE_ONCE(nasd_atent_once)

void  nasd_atent_init(void) {
  nasd_status_t rc;

  rc = nasd_sys_mutex_init(&nasd_atent_mutex);
  if (rc) {
    /* Don't translate error code- translation system may want mutexes! */
    nasd_printf("NASD ERROR: got 0x%x creating nasd_atent_mutex\n", rc);
    NASD_PANIC();
  }

	nasd_atent_list.next = nasd_atent_list.prev = &nasd_atent_list;
}

#define ATENT_TYPE(_e_) ((((_e_)->type == 0)||((_e_)->type > 2)) ? 0 : (_e_)->type)
#define ATENT_OTYPE(_e_) ((((_e_)->otype == 0)||((_e_)->otype > 2)) ? 0 : (_e_)->otype)


void nasd_atent_shutdown(void) {
	int rc, num_freed[3], num_not_freed[3];
	NASD_ATEnt_t *r, *n;

  nasd_once(&nasd_atent_once, nasd_atent_init);

	num_freed[0] = num_freed[1] = num_freed[2] = 0;
	num_not_freed[0] = num_not_freed[1] = num_not_freed[2] = 0;
	nasd_printf("nasd_atent_shutdown:\n");

	for (r = nasd_atent_list.next; r != &nasd_atent_list; r = r->next) {
		nasd_printf("r=%lx type=%d file=%s line=%d\n",
                r, r->type, r->file, r->line);
		num_not_freed[ATENT_TYPE(r)]++;
	}

  rc = nasd_sys_mutex_destroy(&nasd_atent_mutex);
	if (rc) {
		nasd_printf("ERROR: rc=0x%x destroying nasd_atent_mutex\n", rc);
		NASD_PANIC();
	}

	for (r = nasd_atent_done_list; r ; r = n) {
		n = r->next;
		num_freed[ATENT_OTYPE(r)]++;
		free(r);
	}

	nasd_printf("%d mutexes not freed %d conditions not freed %d bogus not freed\n",
              num_not_freed[1], num_not_freed[2], num_not_freed[0]);
	nasd_printf("%d mutexes freed %d conditions freed %d bogus freed\n",
              num_freed[1], num_freed[2], num_freed[0]);
}


static NASD_ATEnt_t *AllocATEnt(char  *file,
                                int    line) {
  NASD_ATEnt_t *t;

  t = (NASD_ATEnt_t *) malloc(sizeof(NASD_ATEnt_t));
  if (t == NULL) { NASD_PANIC(); }

  t->file = file;
  t->line = line;
  t->type = 0;
  t->lk_file = NULL;
  t->lk_line = 0;
  t->lk_thread = 0;

  return(t);
}


static void FreeATEnt(NASD_ATEnt_t  *t) {
  t->otype = t->type;
  t->type = 0;
  t->next = nasd_atent_done_list;
  nasd_atent_done_list = t;
}


nasd_status_t _nasd_mutex_init(NASD_ATEnt_t  **m,
                               char           *file,
                               int             line) {
	NASD_ATEnt_t *a;
	int rc;

  nasd_once(&nasd_atent_once, nasd_atent_init);

	a = AllocATEnt(file,line);

  rc = nasd_sys_mutex_init(&a->m);
  if (rc) {
    nasd_printf("NASD ERROR: got 0x%x allocating mutex %s:%d\n",
                rc, file, line);
    NASD_PANIC();
  }

  NASD_SYS_LOCK_MUTEX(nasd_atent_mutex);
  a->next = nasd_atent_list.next;
  a->prev = &nasd_atent_list;
  a->type = NASD_ATENT_M;
  a->next->prev = a;
  a->prev->next = a;
  NASD_SYS_UNLOCK_MUTEX(nasd_atent_mutex);

	*m = a;
	return(NASD_SUCCESS);
}


nasd_status_t _nasd_mutex_destroy(NASD_ATEnt_t  **m,
                                  char           *file,
                                  int             line) {
	NASD_ATEnt_t *r;
	int rc;

  nasd_once(&nasd_atent_once, nasd_atent_init);

	r = *m;
  rc = nasd_sys_mutex_destroy(&r->m);
	if (rc) {
		nasd_printf("ERROR: rc=0x%x destroying mutex %s:%d\n", rc, file, line);
		NASD_PANIC();
	}

  NASD_SYS_LOCK_MUTEX(nasd_atent_mutex);
	r->next->prev = r->prev;
	r->prev->next = r->next;
	FreeATEnt(r);
  NASD_SYS_UNLOCK_MUTEX(nasd_atent_mutex);

	*m = NULL;
	return(NASD_SUCCESS);
}


nasd_status_t _nasd_cond_init(NASD_ATEnt_t  **c,
                              char           *file,
                              int             line) {
	NASD_ATEnt_t *a;
	int rc;

  nasd_once(&nasd_atent_once, nasd_atent_init);

  a = AllocATEnt(file,line);
  rc = nasd_sys_cond_init(&a->c);
  if (rc) {
    nasd_printf("NASD ERROR: got 0x%x allocating cond %s:%d\n",
                rc, file, line);
    NASD_PANIC();
  }

  NASD_SYS_LOCK_MUTEX(nasd_atent_mutex);
  a->next = nasd_atent_list.next;
  a->prev = &nasd_atent_list;
  a->type = NASD_ATENT_C;
  a->next->prev = a;
  a->prev->next = a;
  NASD_SYS_UNLOCK_MUTEX(nasd_atent_mutex);

  *c = a;
  return(NASD_SUCCESS);
}


nasd_status_t _nasd_cond_destroy(NASD_ATEnt_t  **c,
                                 char           *file,
                                 int             line) {
  NASD_ATEnt_t *r;
  int rc;
  
  nasd_once(&nasd_atent_once, nasd_atent_init);
  
  r = *c;
  rc = nasd_sys_cond_destroy(&r->c);
	if (rc) {
		nasd_printf("ERROR: rc=0x%x destroying cond %s:%d\n", rc, file, line);
		NASD_PANIC();
	}

  NASD_SYS_LOCK_MUTEX(nasd_atent_mutex);
  r->next->prev = r->prev;
  r->prev->next = r->next;
  FreeATEnt(r);
  NASD_SYS_UNLOCK_MUTEX(nasd_atent_mutex);

  *c = NULL;
  return(NASD_SUCCESS);
}


int _nasd_debugging_trylock_mutex(NASD_ATEnt_t *atent, char *file, int line) {
  int rc;
  NASD_ASSERT(atent->type == NASD_ATENT_M);
  if (NASD_MUTEX_OWNED(atent)) {
    nasd_printf("NASD DEBUG ATOMIC: double-lock of mutex (%s:%d) at %s:%d\n",
                atent->file, atent->line, file, line);
    NASD_PANIC();
  }
  rc = NASD_SYS_TRY_LOCK_MUTEX(atent->m);
  if (rc) {
    atent->lk_file = file;
    atent->lk_line = line;
    atent->lk_thread = nasd_thread_self();
  }
  return rc;
}


#endif /* NASD_DEBUG_ATOMIC > 0 */

/*
 * And now... cheezy readers-writers locks
 *
 * Where applicable, use system-provided, otherwise, use our
 * own cheezy ones
 */

#if NASD_TS_CHEESY_RWLOCKS > 0
/*
 * These are some really simple rw locks. Writers
 * always get priority (potentially starving readers).
 * These should only be used if there's nothing better
 * provided by the local platform. --jimz
 */
nasd_status_t
_nasd_rwlock_init(l, file, line)
  NASD_DECLARE_RWLOCK(*l)
  char  *file;
  int    line;
{
  nasd_status_t rc;

  rc = nasd_mutex_init(&l->lock);
  if (rc)
    return(rc);
  rc = nasd_cond_init(&l->cond);
  if (rc) {
    nasd_mutex_destroy(&l->lock);
    return(rc);
  }
  rc = nasd_cond_init(&l->wcond);
  if (rc) {
    nasd_mutex_destroy(&l->lock);
    nasd_cond_destroy(&l->cond);
    return(rc);
  }
  l->nreaders = 0;
  l->nwriters_waiting = 0;
  l->nreaders_waiting = 0;
  l->writing = 0;
  l->rd_file = NULL;
  l->rd_line = 0;
  l->wr_file = NULL;
  l->wr_line = 0;
  return(NASD_SUCCESS);
}

nasd_status_t
_nasd_rwlock_destroy(l, file, line)
  NASD_DECLARE_RWLOCK(*l)
  char  *file;
  int    line;
{
  nasd_status_t rc1, rc2, rc3;

  rc1 = nasd_mutex_destroy(&l->lock);
  rc2 = nasd_cond_destroy(&l->cond);
  rc3 = nasd_cond_destroy(&l->wcond);
  if (rc1)
    return(rc1);
  if (rc2)
    return(rc2);
  return(rc3);
}

void
nasd_rwlock_read(l, file, line)
  NASD_DECLARE_RWLOCK(*l)
  char  *file;
  int    line;
{
  NASD_LOCK_MUTEX(l->lock);
  if (l->writing || l->nwriters_waiting) {
    l->nreaders_waiting++;
    do {
      NASD_WAIT_COND(l->cond,l->lock);
    } while(l->writing || l->nwriters_waiting);
    l->nreaders_waiting--;
    NASD_ASSERT(l->nreaders_waiting >= 0);
  }
  l->nreaders++;
  l->rd_file = file;
  l->rd_line = line;
  NASD_UNLOCK_MUTEX(l->lock);
}

void
nasd_rwlock_read_done(l, file, line)
  NASD_DECLARE_RWLOCK(*l)
  char  *file;
  int    line;
{
  int dobc;

  dobc = 0;
  NASD_LOCK_MUTEX(l->lock);
  l->nreaders--;
  NASD_ASSERT(l->nreaders >= 0);
  if ((l->nreaders == 0) && (l->nwriters_waiting)) {
    dobc = 1;
  }
  l->rd_file = NULL;
  l->rd_line = 0;
  NASD_UNLOCK_MUTEX(l->lock);
  if (dobc) {
    NASD_BROADCAST_COND(l->wcond);
  }
}

void
nasd_rwlock_write(l, file, line)
  NASD_DECLARE_RWLOCK(*l)
  char  *file;
  int    line;
{
  NASD_LOCK_MUTEX(l->lock);
  if (l->nreaders || l->writing) {
    l->nwriters_waiting++;
    do {
      NASD_WAIT_COND(l->wcond,l->lock);
    } while (l->nreaders || l->writing);
    l->nwriters_waiting--;
    NASD_ASSERT(l->nwriters_waiting >= 0);
  }
  l->writing = 1;
  l->wr_file = file;
  l->wr_line = line;
  NASD_UNLOCK_MUTEX(l->lock);
}

void
nasd_rwlock_write_done(l, file, line)
  NASD_DECLARE_RWLOCK(*l)
  char  *file;
  int    line;
{
  int dobc;

  dobc = 0;
  NASD_LOCK_MUTEX(l->lock);
  l->writing = 0;
  if (l->nwriters_waiting) {
    NASD_SIGNAL_COND(l->wcond);
  }
  else if (l->nreaders_waiting) {
    dobc = 1;
  }
  l->wr_file = NULL;
  l->wr_line = 0;
  NASD_UNLOCK_MUTEX(l->lock);
  if (dobc) {
    NASD_BROADCAST_COND(l->cond);
  }
}

#endif /* NASD_TS_CHEESY_RWLOCKS > 0 */

/* Local Variables:  */
/* indent-tabs-mode: nil */
/* tab-width: 2 */
/* End: */
