/*
 *  Schedule the next runnable fiber.
 *  Assumes the _runq is NOT empty
 */
void
_fiber_schedule_next (void)
{
  thread_t *thr;
  int i;

  if (_current_fiber->thr_status == RUNNING)
    _fiber_status (_current_fiber, RUNNABLE);

#ifdef EXPIRIMENTAL
  while (_thread_num_runnable == 0)
    _fiber_event_loop ();
#endif

  thr = NULL;
  for (i = MAX_PRIORITY; --i >= 0; )
    if (_runq[i].thq_count)
      {
	thr = (thread_t *) _runq[i].thq_head.thr_next;
	break;
      }
  assert (thr != NULL);
  _fiber_status (thr, RUNNING);
  if (_current_fiber != thr)
    _fiber_switch (thr);
}
void
semaphore_free (semaphore_t *sem)
{
  thread_t *thr;

  while ((thr = thread_queue_from (&sem->sem_waiting)) != NULL)
    _fiber_status (thr, RUNNABLE);

  dk_free (sem, sizeof (semaphore_t));
}
DK_INLINE int
semaphore_enter (semaphore_t * sem)
{
  if (sem->sem_entry_count)
    sem->sem_entry_count--;
  else
    {
      thread_queue_to (&sem->sem_waiting, _current_fiber);
      _fiber_status (_current_fiber, WAITSEM);
      _fiber_schedule_next ();
    }
  return 0;
}
thread_t *
thread_create (
    thread_init_func initial_function,
    unsigned long stack_size,
    void *init_arg)
{
  thread_t *thr;

  assert (_main_thread != NULL);

  if (stack_size == 0)
    stack_size = THREAD_STACK_SIZE;

#if (SIZEOF_VOID_P == 8)
  stack_size *= 2;
#endif
#if defined (__x86_64 ) && defined (SOLARIS)
  /*GK: the LDAP on that platform requires that */
  stack_size *= 2;
#endif

  /* Any free threads with the right stack size? */
  for (thr = (thread_t *) _deadq.thq_head.thr_next;
       thr != (thread_t *) &_deadq.thq_head;
       thr = (thread_t *) thr->thr_hdr.thr_next)
    {
      if (thr->thr_stack_size >= stack_size)
	break;
    }

  if (thr == (thread_t *) &_deadq.thq_head)
    {
      /* No free fiber, create a new one */
      thr = thread_allocate ();
      _fiber_for_thread (thr, stack_size);
      _thread_num_total++;
    }
  else
    {
      /* Set new context for the thread */
      memcpy (thr->thr_context, thr->thr_init_context, sizeof (jmp_buf));
    }

  thr->thr_initial_function = initial_function;
  thr->thr_initial_argument = init_arg;
  thread_set_priority (thr, NORMAL_PRIORITY);
  _thread_init_attributes (thr);
  _fiber_status (thr, RUNNABLE);

  return thr;
}
void
thread_exit (int n)
{
  if (_current_fiber->thr_attached)
    return;

  _fiber_status (_current_fiber, DEAD);
  _thread_free_attributes (_current_fiber);
  if (_current_fiber == _main_thread)
    exit (n);
  _fiber_schedule_next ();
  /* could come here using win fibers */
  longjmp (_current_fiber->thr_init_context, 1);
}
static void
_fiber_timeout (void *arg)
{
  thread_t *thr = (thread_t *) arg;

  thr->thr_retcode = -1;
#ifdef WIN32
  thr->thr_err = WSAETIMEDOUT;
#else
  thr->thr_err = ETIMEDOUT;
#endif

  _fiber_status (thr, RUNNABLE);
}
int
mutex_enter (dk_mutex_t *mtx)
#endif
{
#ifndef MTX_DEBUG
  return semaphore_enter (mtx->mtx_handle);
#else
  semaphore_t *sem = (semaphore_t *) mtx->mtx_handle;
#ifdef MALLOC_DEBUG
  if (_current_fiber == NULL)
    {
      assert (mtx == _dbgmal_mtx);
      return semaphore_enter (sem);
    }
#endif
  assert (_current_fiber != NULL);
  if (sem->sem_entry_count)
    {
      assert (sem->sem_entry_count == 1);
      assert (mtx->mtx_owner == NULL);
      sem->sem_entry_count--;
    }
  else
    {
      assert (mtx->mtx_owner != _current_fiber);
      thread_queue_to (&sem->sem_waiting, _current_fiber);
      _fiber_status (_current_fiber, WAITSEM);
      _fiber_schedule_next ();
      assert (sem->sem_entry_count == 0);
    }
  assert (mtx->mtx_owner == NULL);
  if (mtx->mtx_entry_check
      && !mtx->mtx_entry_check (mtx, THREAD_CURRENT_THREAD, mtx->mtx_entry_check_cd))
    GPF_T1 ("Mtx entry check fail");

  mtx->mtx_owner = _current_fiber;
  mtx->mtx_entry_file = (char *) file;
  mtx->mtx_entry_line = line;

  return 0;
#endif
}
DK_INLINE void
semaphore_leave (semaphore_t *sem)
#endif
{
  thread_t *thr;

  if (sem->sem_entry_count)
    sem->sem_entry_count++;
  else
    {
      thr = thread_queue_from (&sem->sem_waiting);
      if (thr)
	{
	  assert (thr->thr_status == WAITSEM);
	  _fiber_status (thr, RUNNABLE);
	}
      else
	sem->sem_entry_count++;
    }
}
/*
 *  The main thread must call this function to convert itself into a fiber.
 */
thread_t *
thread_initial (unsigned long stack_size)
{
  static unsigned int marker = THREAD_STACK_MARKER;

  if (_current_fiber)
    return _current_fiber;
  else
    {
      NEW_VARZ (thread_t, thr);

      assert (_current_fiber == NULL);
      _main_thread = _current_fiber = thr;

      _sched_init ();

      if (stack_size == 0)
	stack_size = MAIN_STACK_SIZE;

#if (SIZEOF_VOID_P == 8)
      stack_size *= 2;
#endif
#if defined (__x86_64 ) && defined (SOLARIS)
  /*GK: the LDAP on that platform requires that */
  stack_size *= 2;
#endif

      thr->thr_stack_marker = ▮
      thr->thr_sem = semaphore_allocate (0);
      thr->thr_schedule_sem = semaphore_allocate (0);
      thread_set_priority (thr, NORMAL_PRIORITY);
      _thread_init_attributes (thr);
      _fiber_for_thread (thr, stack_size);

      _fiber_status (thr, RUNNING);

      return thr;
    }
}
int
_fiber_sleep (void *event, TVAL timeout)
{
  thread_t *thr = _current_fiber;

  assert (thr->thr_status == RUNNING);

  thr->thr_err = 0;
  thr->thr_retcode = 0;

  /* set a timer */
  assert (thr->thr_timer == NULL);

  if (timeout != TV_INFINITE)
    thr->thr_timer = timer_queue_new_timer (_timerq, timeout, 0,
	_fiber_timeout, thr);

  if (event == NULL)
    event = &_ev_never;
  thr->thr_event = event;

  do
    {
      _fiber_status (thr, WAITEVENT);
      _fiber_schedule_next ();
    }
  while (thr->thr_event == event && thr->thr_err == 0);

  thr->thr_event = NULL;

  if (timeout != TV_INFINITE)
    {
      timer_deactivate (thr->thr_timer);
      timer_unref (thr->thr_timer);
      thr->thr_timer = NULL;
    }

  return thr->thr_retcode;
}
int
thread_signal_cond (void *event)
{
  thread_t *thr;
  thread_t *next;
  int count = 0;

  /* Wake up waiting threads for which event occurred */
  for (thr = (thread_t *) _waitq.thq_head.thr_next;
      thr != (thread_t *) &_waitq.thq_head;
      thr = next)
    {
      next = (thread_t *) thr->thr_hdr.thr_next;
      if (thr->thr_event == event)
	{
	  thr->thr_event = NULL;
	  thr->thr_retcode = 0;
	  _fiber_status (thr, RUNNABLE);
	  count++;
	}
    }
  return count;
}
void
mutex_leave (dk_mutex_t *mtx)
#endif
{
#ifndef MTX_DEBUG
  semaphore_leave (mtx->mtx_handle);
#else
  semaphore_t *sem = (semaphore_t *) mtx->mtx_handle;
  thread_t *thr;
#ifdef MALLOC_DEBUG
  if (_current_fiber == NULL)
    {
      assert (mtx == _dbgmal_mtx);
      semaphore_leave (sem);
      return;
    }
#endif
  assert (mtx->mtx_owner == _current_fiber);
  assert (sem->sem_entry_count == 0);
  mtx->mtx_owner = NULL;

  if (sem->sem_entry_count)
    sem->sem_entry_count++;
  else
    {
      thr = thread_queue_from (&sem->sem_waiting);
      if (thr)
	{
	  assert (thr->thr_status == WAITSEM);
	  _fiber_status (thr, RUNNABLE);
	}
      else
	sem->sem_entry_count++;
    }
#endif
}
/*
 *  This is the idle task, that runs at the lowest possible priority.
 *  It's only called when there are no other fibers ready for scheduling.
 */
void
_fiber_event_loop (void)
{
  /* These are all static, to minimize stack usage */
  static fd_set readfds;
  static fd_set writefds;
  static int nreadfds;
  static int nwritefds;
  static int nfds;
  static thread_t *thr, *next;
  static TVAL timeout;
  static struct timeval tv, *ptv;
  static int rc;

  for (;;)
    {
      timeout = timer_queue_update (_timerq,
	  timer_queue_time_elapsed (_timerq));
      if (_thread_num_runnable)
	return;

      FD_ZERO (&readfds);
      FD_ZERO (&writefds);

      for (thr = (thread_t *) _waitq.thq_head.thr_next;
	  thr != (thread_t *) &_waitq.thq_head;
	  thr = (thread_t *) thr->thr_hdr.thr_next)
	{
	  if (thr->thr_nfds)
	    {
	      nreadfds = _fd_set_or (&readfds, &thr->thr_rfds);
	      nwritefds = _fd_set_or (&writefds, &thr->thr_wfds);
	    }
	}
      nfds = MAX (nreadfds, nwritefds);

      if (timeout == 0)
	ptv = NULL;
      else
	{
	  tv.tv_sec = timeout / 1000;
	  tv.tv_usec = (timeout % 1000) * 1000;
	  ptv = &tv;
	}
      rc = select (nfds, &readfds, &writefds, NULL, ptv);
      if (rc == -1)
	{
	  if (errno != EINTR)
	    GPF_T1 ("select() returned -1");
	  continue;
	}
      timer_queue_update (_timerq, timer_queue_time_elapsed (_timerq));

      if (rc > 0)
	{
	  /* Wake up waiting fibers for which an event occurred */
	  for (thr = (thread_t *) _waitq.thq_head.thr_next;
	      thr != (thread_t *) &_waitq.thq_head;
	      thr = next)
	    {
	      next = (thread_t *) thr->thr_hdr.thr_next;
	      if (thr->thr_nfds == 0)
		continue;
	      if (_fd_set_intersect (&thr->thr_rfds, &readfds) ||
		  _fd_set_intersect (&thr->thr_wfds, &writefds))
		{
		  _fd_set_and (&thr->thr_rfds, &readfds);
		  _fd_set_and (&thr->thr_wfds, &writefds);
		  thr->thr_event = NULL;
		  thr->thr_retcode = 1;
		  _fiber_status (thr, RUNNABLE);
		}
	    }
	}
      break;
    }
}