/*  -
   Copyright (C) 2006 Weongyo Jeong (weongyo@gmail.com)

This file is part of ROVM.

ROVM is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2, or (at your option) any later
version.

ROVM is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING.  If not, write to the Free
Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.  */

#include "types.h"
#include "mpool.h"
#include "tktree.h"
#include "clstree.h"
#include "sha1.h"
#include "listen.h"
#include "rovm.h"

#include "thread.h"
#include "thread_mutex.h"
#include "thread_cond.h"
#include "mpm_worker_fdqueue.h"

#define RC_DEBUG_ASSERT(EXPN)           /* nothing */

typedef struct recycled_pool 
{
  rc_pool_t *pool;
  struct recycled_pool *next;
} recycled_pool;

struct fd_queue_info_t 
{
  rc_uint32_t idlers;
  rc_thread_mutex_t *idlers_mutex;
  rc_thread_cond_t *wait_for_idler;
  int terminated;
  int max_idlers;
  recycled_pool  *recycled_pools;
};

static rc_status_t
queue_info_cleanup(void *data_)
{
  fd_queue_info_t *qi = data_;
  rc_thread_cond_destroy (qi->wait_for_idler);
  rc_thread_mutex_destroy (qi->idlers_mutex);
  
  /* Clean up any pools in the recycled list */
  for (;;) 
    {
      struct recycled_pool *first_pool = qi->recycled_pools;
      if (first_pool == NULL)
        break;

      if (rc_atomic_casptr((volatile void**)&(qi->recycled_pools), first_pool->next,
                           first_pool) == first_pool) 
        mp_destroy (first_pool->pool);
    }
  
  return RC_SUCCESS;
}

rc_status_t
rc_queue_info_create(fd_queue_info_t **queue_info,
                     rc_pool_t *pool, int max_idlers)
{
  rc_status_t rv;
  fd_queue_info_t *qi;
  
  qi = mp_alloc (pool, sizeof(*qi));
  memset(qi, 0, sizeof(*qi));
  
  rv = rc_thread_mutex_create(&qi->idlers_mutex, RC_THREAD_MUTEX_DEFAULT,
                              pool);
  if (rv != RC_SUCCESS)
    return rv;

  rv = rc_thread_cond_create(&qi->wait_for_idler, pool);
  if (rv != RC_SUCCESS)
    return rv;

  qi->recycled_pools = NULL;
  qi->max_idlers = max_idlers;
  mp_cleanup_register (pool, qi, queue_info_cleanup, rc_pool_cleanup_null);
  
  *queue_info = qi;
  
  return RC_SUCCESS;
}

rc_status_t 
rc_queue_info_set_idle (fd_queue_info_t *queue_info,
                        rc_pool_t *pool_to_recycle)
{
  rc_status_t rv;
  int prev_idlers;
  
  /* If we have been given a pool to recycle, atomically link
   * it into the queue_info's list of recycled pools
   */
  if (pool_to_recycle) 
    {
      struct recycled_pool *new_recycle;
      new_recycle = (struct recycled_pool *) mp_alloc (pool_to_recycle,
                                                       sizeof(*new_recycle));
      new_recycle->pool = pool_to_recycle;
      for (;;) 
        {
          new_recycle->next = queue_info->recycled_pools;
          if (rc_atomic_casptr((volatile void**)&(queue_info->recycled_pools),
                               new_recycle, new_recycle->next) ==
              new_recycle->next) 
            break;
        }
    }
  
  /* Atomically increment the count of idle workers */
  for (;;) 
    {
      prev_idlers = queue_info->idlers;
      if (rc_atomic_cas32(&(queue_info->idlers), prev_idlers + 1,
                          prev_idlers) == prev_idlers) 
        break;
    }
  
  /* If this thread just made the idle worker count nonzero,
   * wake up the listener. */
  if (prev_idlers == 0) 
    {
      rv = rc_thread_mutex_lock(queue_info->idlers_mutex);
      if (rv != RC_SUCCESS) 
        return rv;
      rv = rc_thread_cond_signal(queue_info->wait_for_idler);
      if (rv != RC_SUCCESS) 
        {
          rc_thread_mutex_unlock(queue_info->idlers_mutex);
          return rv;
        }
      rv = rc_thread_mutex_unlock(queue_info->idlers_mutex);
      if (rv != RC_SUCCESS) 
        return rv;
    }

    return RC_SUCCESS;
}

rc_status_t 
rc_queue_info_wait_for_idler(fd_queue_info_t *queue_info,
                             rc_pool_t **recycled_pool)
{
  rc_status_t rv;
  
  *recycled_pool = NULL;
  
  /* Block if the count of idle workers is zero */
  if (queue_info->idlers == 0) 
    {
      rv = rc_thread_mutex_lock(queue_info->idlers_mutex);
      if (rv != RC_SUCCESS) 
        return rv;

      /* Re-check the idle worker count to guard against a
       * race condition.  Now that we're in the mutex-protected
       * region, one of two things may have happened:
       *   - If the idle worker count is still zero, the
       *     workers are all still busy, so it's safe to
       *     block on a condition variable.
       *   - If the idle worker count is nonzero, then a
       *     worker has become idle since the first check
       *     of queue_info->idlers above.  It's possible
       *     that the worker has also signaled the condition
       *     variable--and if so, the listener missed it
       *     because it wasn't yet blocked on the condition
       *     variable.  But if the idle worker count is
       *     now nonzero, it's safe for this function to
       *     return immediately.
       */
      if (queue_info->idlers == 0) 
        {
          rv = rc_thread_cond_wait(queue_info->wait_for_idler,
                                   queue_info->idlers_mutex);
          if (rv != RC_SUCCESS) 
            {
              rc_status_t rv2;
              rv2 = rc_thread_mutex_unlock(queue_info->idlers_mutex);
              if (rv2 != RC_SUCCESS) 
                return rv2;
              return rv;
            }
        }
      rv = rc_thread_mutex_unlock(queue_info->idlers_mutex);
      if (rv != RC_SUCCESS)
        return rv;
    }
  
  /* Atomically decrement the idle worker count */
  rc_atomic_dec32(&(queue_info->idlers));
  
  /* Atomically pop a pool from the recycled list */
  for (;;) 
    {
      struct recycled_pool *first_pool = queue_info->recycled_pools;
      if (first_pool == NULL) 
        break;
      if (rc_atomic_casptr((volatile void**)&(queue_info->recycled_pools), first_pool->next,
                           first_pool) == first_pool) 
        {
          *recycled_pool = first_pool->pool;
          break;
        }
    }
  
  if (queue_info->terminated) 
    return RC_EOF;

  return RC_SUCCESS;
}

rc_status_t 
rc_queue_info_term (fd_queue_info_t *queue_info)
{
  rc_status_t rv;
  rv = rc_thread_mutex_lock(queue_info->idlers_mutex);
  if (rv != RC_SUCCESS) 
    return rv;

  queue_info->terminated = 1;
  rc_thread_cond_broadcast (queue_info->wait_for_idler);

  return rc_thread_mutex_unlock (queue_info->idlers_mutex);
}

/**
  Detects when the fd_queue_t is full. This utility function is expected
  to be called from within critical sections, and is not threadsafe.
*/
#define rc_queue_full(queue) ((queue)->nelts == (queue)->bounds)

/**
   Detects when the fd_queue_t is empty. This utility function is expected
   to be called from within critical sections, and is not threadsafe.
*/
#define rc_queue_empty(queue) ((queue)->nelts == 0)

/**
   Callback routine that is called to destroy this
   fd_queue_t when its pool is destroyed.
*/
static rc_status_t
rc_queue_destroy (void *data)
{
  fd_queue_t *queue = data;
  
  /* Ignore errors here, we can't do anything about them anyway.
   * XXX: We should at least try to signal an error here, it is
   * indicative of a programmer error. -aaron */
  rc_thread_cond_destroy (queue->not_empty);
  rc_thread_mutex_destroy (queue->one_big_mutex);

  return RC_SUCCESS;
}

/**
   Initialize the fd_queue_t.
*/
rc_status_t 
rc_queue_init (fd_queue_t *queue, int queue_capacity, rc_pool_t *a)
{
  int i;
  rc_status_t rv;
  
  if ((rv = rc_thread_mutex_create (&queue->one_big_mutex,
                                    RC_THREAD_MUTEX_DEFAULT, a)) != RC_SUCCESS)
    return rv;

  if ((rv = rc_thread_cond_create(&queue->not_empty, a)) != RC_SUCCESS)
    return rv;

  queue->data = mp_alloc (a, queue_capacity * sizeof (fd_queue_elem_t));
  queue->bounds = queue_capacity;
  queue->nelts = 0;
  
  /* Set all the sockets in the queue to NULL */
  for (i = 0; i < queue_capacity; ++i)
    queue->data[i].sd = NULL;
  
  mp_cleanup_register (a, queue, rc_queue_destroy, rc_pool_cleanup_null);
  
  return RC_SUCCESS;
}

/**
   Push a new socket onto the queue.
   
   precondition: rc_queue_info_wait_for_idler has already been called
                 to reserve an idle worker thread
*/
rc_status_t 
rc_queue_push (fd_queue_t *queue, rc_socket_t *sd, rc_pool_t *p)
{
  fd_queue_elem_t *elem;
  rc_status_t rv;
  
  if ((rv = rc_thread_mutex_lock (queue->one_big_mutex)) != RC_SUCCESS)
    return rv;

  RC_DEBUG_ASSERT (!queue->terminated);
  RC_DEBUG_ASSERT (!rc_queue_full(queue));

  elem = &queue->data[queue->nelts];
  elem->sd = sd;
  elem->p = p;
  queue->nelts++;
  
  rc_thread_cond_signal (queue->not_empty);
  
  if ((rv = rc_thread_mutex_unlock(queue->one_big_mutex)) != RC_SUCCESS)
    return rv;

  return RC_SUCCESS;
}

/**
 * Retrieves the next available socket from the queue. If there are no
 * sockets available, it will block until one becomes available.
 * Once retrieved, the socket is placed into the address specified by
 * 'sd'.
 */
rc_status_t 
rc_queue_pop (fd_queue_t *queue, rc_socket_t **sd, rc_pool_t **p)
{
  fd_queue_elem_t *elem;
  rc_status_t rv;
  
  if ((rv = rc_thread_mutex_lock(queue->one_big_mutex)) != RC_SUCCESS)
    return rv;

  /* Keep waiting until we wake up and find that the queue is not empty. */
  if (rc_queue_empty (queue)) 
    {
      if (!queue->terminated)
        rc_thread_cond_wait(queue->not_empty, queue->one_big_mutex);

      /* If we wake up and it's still empty, then we were interrupted */
      if (rc_queue_empty(queue)) 
        {
          rv = rc_thread_mutex_unlock (queue->one_big_mutex);
          if (rv != RC_SUCCESS)
            return rv;
          if (queue->terminated)
            return RC_EOF; /* no more elements ever again */
          else
            return RC_EINTR;
        }
    }
  
  elem = &queue->data[--queue->nelts];
  *sd = elem->sd;
  *p = elem->p;
#ifdef RC_DEBUG
  elem->sd = NULL;
  elem->p = NULL;
#endif /* RC_DEBUG */
  
  rv = rc_thread_mutex_unlock (queue->one_big_mutex);

  return rv;
}

rc_status_t 
rc_queue_interrupt_all (fd_queue_t *queue)
{
  rc_status_t rv;
  
  if ((rv = rc_thread_mutex_lock(queue->one_big_mutex)) != RC_SUCCESS)
    return rv;

  rc_thread_cond_broadcast (queue->not_empty);

  return rc_thread_mutex_unlock (queue->one_big_mutex);
}

rc_status_t 
rc_queue_term (fd_queue_t *queue)
{
  rc_status_t rv;
  
  if ((rv = rc_thread_mutex_lock (queue->one_big_mutex)) != RC_SUCCESS)
    return rv;

  /* we must hold one_big_mutex when setting this... otherwise,
   * we could end up setting it and waking everybody up just after a
   * would-be popper checks it but right before they block
   */
  queue->terminated = 1;
  if ((rv = rc_thread_mutex_unlock (queue->one_big_mutex)) != RC_SUCCESS)
    return rv;

  return rc_queue_interrupt_all (queue);
}
