/*
    GSK - a library to write servers
    Copyright (C) 1999-2000 Dave Benson

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

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA

    Contact:
        daveb@ffem.org <Dave Benson>
*/

/* GskThreadPool.
 *
 * A way to queue tasks on recycled native threads,
 * using pipes to communicated.
 *
 * This is primarily intended to be used as an
 * implementation of `run_background' in GskMainLoops that
 * don't have a convenient mechanism.
 */

#ifndef __GSK_THREAD_POOL_H_
#define __GSK_THREAD_POOL_H_

#include "gskmainloop.h"

/* --- Manager-worker thread protocol. --- */
typedef struct _GskThreadPoolRequest GskThreadPoolRequest;
struct _GskThreadPoolRequest
{
  GskMainLoopRunFunc run_func;
  gpointer run_data;
  GskMainLoopResultFunc result_func;
  gpointer result_data;
};
typedef struct _GskThreadPoolResponse GskThreadPoolResponse;
struct _GskThreadPoolResponse
{
  guint thread_id;
  gpointer run_result_data;
  GskMainLoopResultFunc result_func;
  gpointer result_data;
};

typedef struct _GskThreadPoolPendingTask GskThreadPoolPendingTask;

/* --- Thread pool implementation. --- */
typedef struct _GskThreadPoolThread GskThreadPoolThread;
typedef struct _GskThreadPool GskThreadPool;

struct _GskThreadPoolThread
{
  /* The system identifier for this thread. */
  guint                  thread_id;

  /* The main loop that launched this thread. */
  GskThreadPool         *thread_pool;

  /* Is this thread available to run a new task? */
  gboolean               is_idle;

  /* If is_idle, when should this thread be destroyed? */
  gulong                 expire_time;

  /* Communicating with this thread.  (NOTE: b/c there is at most a 
   * GskThreadPoolRequest "on-the-wire",
   * so we assuming that I/O on these guys doesn't
   * block -- maybe a bad assumption). */
  int                    write_fd;	/* used by gsk_thread_poll_run */
  int                    read_fd;	/* used by the background thread */

  /* if the thread has a problem ... */
  guint                  is_dead : 1;

  /* Next thread. */
  GskThreadPoolThread   *next;
  GskThreadPoolThread   *prev;

};

/* A thread pool that operates on a main loop. */
struct _GskThreadPool
{
  GskMainLoop              *main_loop;

  GskThreadPoolThread      *first_idle_thread;
  GskThreadPoolThread      *last_idle_thread;
  GskThreadPoolThread      *first_occupied_thread;
  GskThreadPoolThread      *last_occupied_thread;

  /* Map from thread_id to GskThreadPoolThread */
  GHashTable               *thread_id_to_thread;

  /* Number of commands we've doled out, but haven't gotten a result. */
  int                       num_pending;

  /* To be used be background threads holding `write_mutex' only. */
  int                       write_fd;
  GMutex                   *write_mutex;

  /* To be used for reading return values from the pooled threads. */
  int                       read_fd;
  GskSource                *source;	/* main loop handle for read_fd */

  /* Used to store partial return values. */
  int                       run_return_byte_used;
  gchar                     run_return_buffer[sizeof(GskThreadPoolResponse)];

  /* Tasks that we ran out of threads to handle (so idle threads
   * must take these tasks on...)
   */
  GskThreadPoolPendingTask *first_pending;
  GskThreadPoolPendingTask *last_pending;

  /* Maximum number of working threads to create (or 0 for no maximum) */
  int                       max_workers;

  /* This flag is raised if gsk_thread_pool_destroy is called
   * while threads are still being processed.
   */
  guint                     deferred_destroy : 1;

  /* This flag is raised if the main loop is never going to be run
   * again, so we must *block* until all i/o is handled.
   * (We do this when the main loop is destroyed).
   */
  guint                     destroy_immediately : 1;

  /* signal id for main_loop destroy */

  guint                     main_loop_destroy_signal_id;
};

/* --- Public methods --- */
GskThreadPool *gsk_thread_pool_new      (GskMainLoop           *main_loop,
                                         int                    max_workers);
void           gsk_thread_pool_add      (GskThreadPool         *thread_pool,
                                         GskMainLoopRunFunc     run_func,
                                         gpointer               run_data,
			                 GskMainLoopResultFunc  result_func,
			                 gpointer               result_data);

/* NB: the thread pool will only be destroyed once all the current
 *     worker threads have terminated!  Furthermore, once a `run_func'
 *     has been started, your `result_func's will certainly be run,
 *     even after gsk_thread_pool_destroy has returned.
 *
 * XXX: we need to return the tasks that were queued, now are being abandoned!
 */
void           gsk_thread_pool_destroy  (GskThreadPool         *thread_pool);

void           gsk_thread_pool_set_max_workers 
                                        (GskThreadPool         *thread_pool,
                                         int                    max_workers);

/* private */
/* NOTE: this assumes the main_loop is destroy()d.  */
void           gsk_thread_pool_destroy_now(GskThreadPool       *thread_pool);
#endif
