Logo Search packages:      
Sourcecode: pan version File versions  Download package

socket-pool.c

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Pan - A Newsreader for Gtk+
 * Copyright (C) 2003  Charles Kerr <charles@rebelbase.com>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*********************
**********************  Includes
*********************/

#include <config.h>

#include <stdio.h>

#include <glib.h>

#include <pan/base/debug.h>
#include <pan/base/pan-i18n.h>
#include <pan/base/log.h>

#include <pan/nntp.h>
#include <pan/queue.h>
#include <pan/sockets.h>
#include <pan/socket-pool.h>
#include <pan/task.h>

/*********************
**********************  Structs
*********************/

struct _SocketPool
{
      Server * server;
      gboolean connection_pending;
      int socket_qty;
      gboolean online;
      GQueue * checked_in;
      PanCallback * available_cb;
      GMutex * mutex;
};

/*********************
**********************  Variables
*********************/

/*********************
**********************  BEGINNING OF SOURCE
*********************/

/***
****  Life Cycle
***/

SocketPool*
socket_pool_new (Server * server)
{
      SocketPool * pool;

      /* sanity clause */
      g_return_val_if_fail (server_is_valid (server), NULL);
      
       /* build the pool */   
      pool = g_new0 (SocketPool, 1);
      pool->server = server;
      pool->connection_pending = FALSE;
      pool->socket_qty = 0;
      pool->online = TRUE;
      pool->checked_in = g_queue_new ();
      pool->available_cb = pan_callback_new ();
      pool->mutex = g_mutex_new ();
      return pool;
}

void
socket_pool_free (SocketPool * pool)
{
      /* sanity clause */
      g_return_if_fail (pool != NULL);

      g_mutex_free (pool->mutex);
      pool->mutex = NULL;
      pool->server = NULL;
      pool->connection_pending = FALSE;
      pool->socket_qty = 0;
      g_queue_free (pool->checked_in);
      pool->checked_in = NULL;
      pan_callback_free (pool->available_cb);
      pool->available_cb = NULL;
      g_free (pool);
}

/***
****
***/

static void
fire_connection_available_change (SocketPool * pool)
{
      pan_callback_call (pool->available_cb, pool, pool->server);
}

static gpointer
connect_thread_func (gpointer data)
{
      SocketPool * pool = (SocketPool*) data;
      Server * server = pool->server;
      int retry_delay_secs = 3;
      const int retry_delay_inc = 2;

      for (;;)
      {
            PanSocket * sock;

            /* create the socket */
            debug1 (DEBUG_QUEUE, "connecting to server %s", server->name);
            sock = pan_socket_new (&server->name, &server->address, server->port);
            pan_socket_set_nntp_auth (sock, server->need_auth, &server->username, &server->password);

            /* handshake */
            debug2 (DEBUG_QUEUE, "handshake to server %s sock %p", server->name, sock);
            if (sock != NULL)
            {
                  gboolean posting_ok = FALSE;
                  int val;
                  StatusItem * status = status_item_new_with_description (_("Connecting"));
                  status_item_set_active (status, TRUE);

                  val = nntp_handshake (status, sock, &posting_ok);
                  if (val != TASK_OK) {
                        pan_object_unref (PAN_OBJECT(sock));
                        sock = NULL;
                  }

                  if (val == TASK_FAIL) { /* the news server doesn't love us anymore! */
                        queue_set_online (FALSE);
                        break;
                  }

                  status_item_set_active (status, FALSE);
                  pan_object_unref (PAN_OBJECT(status));
            }

            /* if success ... */
            if (sock != NULL)
            {
                  /* update the pool */
                  debug2 (DEBUG_QUEUE, "success with handshake %s, sock %p", server->name, sock);
                  g_mutex_lock (pool->mutex);
                  {
                        pool->connection_pending = FALSE;
                        ++pool->socket_qty;
                        g_queue_push_tail (pool->checked_in, sock);
                  }
                  g_mutex_unlock (pool->mutex);

                  /* let clients know */
                  fire_connection_available_change (pool);

                  break;
            }

            /* sleep awhile and try again */
            debug1 (DEBUG_QUEUE, "failure; sleeping %d secs before retrying", retry_delay_secs);
            retry_delay_secs += retry_delay_inc;
            g_usleep (G_USEC_PER_SEC * retry_delay_secs);
      }

      return NULL;
}

void
socket_pool_request_connection (SocketPool * pool)
{
      g_mutex_lock (pool->mutex);
      {
            /**
             * If we're online,
             * and we don't have any sockets handy,
             * and there aren't any pending connections,
             * and we haven't maxed out the number of connections,
             * then try to make a new connection. */
            if ((pool->online) &&
                (pool->checked_in->length == 0) &&
                (pool->connection_pending == FALSE) &&
                (pool->server->max_connections - pool->socket_qty > 0))
            {
                  g_thread_create (connect_thread_func, pool, FALSE, NULL);
                  pool->connection_pending = TRUE;
            }
      }
      g_mutex_unlock (pool->mutex);
}

PanCallback*
socket_pool_get_available_callback (SocketPool     * pool)
{
      return pool->available_cb;
}

PanSocket*
socket_pool_checkout (SocketPool * pool)
{
      PanSocket * sock = NULL;
      
      if (pool->online)
            sock = (PanSocket*) g_queue_pop_head (pool->checked_in);

      if (sock != NULL)
            debug2 (DEBUG_QUEUE, "checkout pool %p sock %p", pool, sock);

      return sock;
}

void
socket_pool_checkin (SocketPool * pool, PanSocket * socket)
{
      g_return_if_fail (pool!=NULL);
      g_return_if_fail (socket!=NULL);
      debug2 (DEBUG_QUEUE, "checkin pool %p socket %p", pool, socket);

      if (!pan_socket_get_error_flag(socket)
            && pool->socket_qty <= pool->server->max_connections
            && pool->online)

      {
            debug0 (DEBUG_QUEUE, "adding back to list");
            g_queue_push_tail (pool->checked_in, socket);
      }
      else
      {
            debug0 (DEBUG_QUEUE, "error or offline or too many connections -- closing");
            if (!pan_socket_get_error_flag(socket))
                  nntp_disconnect (NULL, socket);
            pan_object_unref (PAN_OBJECT(socket));
            --pool->socket_qty;
      }

      fire_connection_available_change (pool);
}

int
socket_pool_get_connection_qty (const SocketPool  * pool)
{
      g_return_val_if_fail (pool!=NULL, 0);

      return pool->socket_qty;
}

void
socket_pool_refresh (SocketPool * pool)
{
      int old_socket_qty;
      int new_socket_qty;
      debug_enter ("socket_pool_refresh");

      g_return_if_fail (pool!=NULL);

      g_mutex_lock (pool->mutex);
      {
            GQueue * new_queue = g_queue_new ();
            PanSocket * sock;

            old_socket_qty = pool->socket_qty;

            while ((sock = g_queue_pop_head (pool->checked_in)))
            {
                  const time_t age = time(NULL) - pan_socket_get_last_action_time (sock);

                  const gboolean too_old = age > pool->server->idle_secs_before_timeout;
                  const gboolean too_many = pool->socket_qty > pool->server->max_connections;
                  const gboolean offline = !pool->online;
                  gboolean destroy = too_old || too_many || offline;
                  debug4 (DEBUG_QUEUE, "too_old: %d too_many %d offline %d destroy %d", too_old, too_many, offline, destroy);

                  if (!destroy)
                  {
                        const time_t last_action_time = pan_socket_get_last_action_time (sock);
                        StatusItem * status = status_item_new_with_description (_("Sending Keepalive"));
                        status_item_set_active (status, TRUE);

                        debug2 (DEBUG_QUEUE, "sending keepalive pool %p sock %p", pool, sock);
                        destroy = nntp_noop (status, sock) != TASK_OK;
                        pan_socket_set_last_action_time (sock, last_action_time);

                        status_item_set_active (status, FALSE);
                        pan_object_unref (PAN_OBJECT(status));
                  }

                  if (!destroy)
                  {
                        debug2 (DEBUG_QUEUE, "pool %p keeping sock %p after keepalive", pool, sock);
                        g_queue_push_tail (new_queue, sock);
                  }
                  else
                  {
                        StatusItem * status = status_item_new_with_description (_("Disconnecting Idle"));
                        status_item_set_active (status, TRUE);

                        debug2 (DEBUG_QUEUE, "pool %p destroying sock %p", pool, sock);
                        log_add_va (LOG_INFO, _("Disconnecting one connection from `%s' after %d seconds idle"), pool->server->name, age);
                        --pool->socket_qty;
                        nntp_disconnect (status, sock);
                        pan_object_unref (PAN_OBJECT(sock));

                        status_item_set_active (status, FALSE);
                        pan_object_unref (PAN_OBJECT(status));
                  }
            }
            
            new_socket_qty = pool->socket_qty;

            g_queue_free (pool->checked_in);
            pool->checked_in = new_queue;
      }
      g_mutex_unlock (pool->mutex);

      if (new_socket_qty != old_socket_qty)
            fire_connection_available_change (pool);

      debug_exit ("socket_pool_refresh");
}

gboolean
socket_pool_is_online (const SocketPool * pool)
{
      g_return_val_if_fail (pool!=NULL, FALSE);

      return pool->online;
}

void
socket_pool_set_online (SocketPool * pool, gboolean online)
{
      g_return_if_fail (pool!=NULL);

      if (online != pool->online)
      {
            pool->online = online;

            fire_connection_available_change (pool);
      }
}

Generated by  Doxygen 1.6.0   Back to index