/*
   Copyright (c) 2007, 2008, Sun Microsystems, Inc,
   Copyright (c) 2011, 2012, Monty Program Ab

   This program 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; version 2 of the License.

   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */

#include <my_global.h>
#include <wqueue.h>

#define STRUCT_PTR(TYPE, MEMBER, a)                                           \
          (TYPE *) ((char *) (a) - offsetof(TYPE, MEMBER))
/*
  Link a thread into double-linked queue of waiting threads.

  SYNOPSIS
    wqueue_link_into_queue()
      wqueue              pointer to the queue structure
      thread              pointer to the thread to be added to the queue

  RETURN VALUE
    none

  NOTES.
    Queue is represented by a circular list of the thread structures
    The list is double-linked of the type (**prev,*next), accessed by
    a pointer to the last element.
*/

void wqueue_link_into_queue(WQUEUE *wqueue, struct st_my_thread_var *thread)
{
  struct st_my_thread_var *last;
  if (!(last= wqueue->last_thread))
  {
    /* Queue is empty */
    thread->next= thread;
    thread->prev= &thread->next;
  }
  else
  {
    thread->prev= last->next->prev;
    last->next->prev= &thread->next;
    thread->next= last->next;
    last->next= thread;
  }
  wqueue->last_thread= thread;
}


/*
  Add a thread to single-linked queue of waiting threads

  SYNOPSIS
    wqueue_add_to_queue()
      wqueue              pointer to the queue structure
      thread              pointer to the thread to be added to the queue

  RETURN VALUE
    none

  NOTES.
    Queue is represented by a circular list of the thread structures
    The list is single-linked of the type (*next), accessed by a pointer
    to the last element.
*/

void wqueue_add_to_queue(WQUEUE *wqueue, struct st_my_thread_var *thread)
{
  struct st_my_thread_var *last;
  if (!(last= wqueue->last_thread))
    thread->next= thread;
  else
  {
    thread->next= last->next;
    last->next= thread;
  }
#ifndef DBUG_OFF
  thread->prev= NULL; /* force segfault if used */
#endif
  wqueue->last_thread= thread;
}

/*
  Unlink a thread from double-linked queue of waiting threads

  SYNOPSIS
    wqueue_unlink_from_queue()
      wqueue              pointer to the queue structure
      thread              pointer to the thread to be removed from the queue

  RETURN VALUE
    none

  NOTES.
    See NOTES for link_into_queue
*/

void wqueue_unlink_from_queue(WQUEUE *wqueue, struct st_my_thread_var *thread)
{
  if (thread->next == thread)
    /* The queue contains only one member */
    wqueue->last_thread= NULL;
  else
  {
    thread->next->prev= thread->prev;
    *thread->prev= thread->next;
    if (wqueue->last_thread == thread)
      wqueue->last_thread= STRUCT_PTR(struct st_my_thread_var, next,
                                      thread->prev);
  }
  thread->next= NULL;
}


/*
  Remove all threads from queue signaling them to proceed

  SYNOPSIS
    wqueue_realease_queue()
      wqueue              pointer to the queue structure
      thread              pointer to the thread to be added to the queue

  RETURN VALUE
    none

  NOTES.
    See notes for add_to_queue
    When removed from the queue each thread is signaled via condition
    variable thread->suspend.
*/

void wqueue_release_queue(WQUEUE *wqueue)
{
  struct st_my_thread_var *last= wqueue->last_thread;
  struct st_my_thread_var *next= last->next;
  struct st_my_thread_var *thread;
  do
  {
    thread= next;
    mysql_cond_signal(&thread->suspend);
    next= thread->next;
    thread->next= NULL;
  }
  while (thread != last);
  wqueue->last_thread= NULL;
}


/**
  @brief Removes all threads waiting for read or first one waiting for write.

  @param wqueue          pointer to the queue structure
  @param thread          pointer to the thread to be added to the queue

  @note This function is applicable only to single linked lists.
*/

void wqueue_release_one_locktype_from_queue(WQUEUE *wqueue)
{
  struct st_my_thread_var *last= wqueue->last_thread;
  struct st_my_thread_var *next= last->next;
  struct st_my_thread_var *thread;
  struct st_my_thread_var *new_list= NULL;
  uint first_type= next->lock_type;
  if (first_type == MY_PTHREAD_LOCK_WRITE)
  {
    /* release first waiting for write lock */
    mysql_cond_signal(&next->suspend);
    if (next == last)
      wqueue->last_thread= NULL;
    else
      last->next= next->next;
    next->next= NULL;
    return;
  }
  do
  {
    thread= next;
    next= thread->next;
    if (thread->lock_type == MY_PTHREAD_LOCK_WRITE)
    {
      /* skip waiting for write lock */
      if (new_list)
      {
        thread->next= new_list->next;
        new_list= new_list->next= thread;
      }
      else
        new_list= thread->next= thread;
    }
    else
    {
      /* release waiting for read lock */
      mysql_cond_signal(&thread->suspend);
      thread->next= NULL;
    }
  } while (thread != last);
  wqueue->last_thread= new_list;
}


/*
  Add thread and wait

  SYNOPSIS
    wqueue_add_and_wait()
    wqueue               queue to add to
    thread               thread which is waiting
    lock                 mutex need for the operation
*/

void wqueue_add_and_wait(WQUEUE *wqueue,
                         struct st_my_thread_var *thread,
                         mysql_mutex_t *lock)
{
  DBUG_ENTER("wqueue_add_and_wait");
  DBUG_PRINT("enter",
             ("thread: %p cond: %p mutex: %p",
              thread, &thread->suspend, lock));
  wqueue_add_to_queue(wqueue, thread);
  do
  {
    DBUG_PRINT("info", ("wait... cond:  %p  mutex:  %p",
                        &thread->suspend, lock));
    mysql_cond_wait(&thread->suspend, lock);
    DBUG_PRINT("info", ("wait done cond: %p mutex: %p next: %p",
                        &thread->suspend, lock,
                        thread->next));
  }
  while (thread->next);
  DBUG_VOID_RETURN;
}
