
/*
 * $Id: comm_generic.c,v 1.13 2008/08/02 11:40:15 adrian Exp $
 *
 * DEBUG: section 5     Socket Functions
 *
 * SQUID Web Proxy Cache          http://www.squid-cache.org/
 * ----------------------------------------------------------
 *
 *  Squid is the result of efforts by numerous individuals from
 *  the Internet community; see the CONTRIBUTORS file for full
 *  details.   Many organizations have provided support for Squid's
 *  development; see the SPONSORS file for full details.  Squid is
 *  Copyrighted (C) 2001 by the Regents of the University of
 *  California; see the COPYRIGHT file for full details.  Squid
 *  incorporates software developed and/or copyrighted by other
 *  sources; see the CREDITS file for full details.
 *
 *  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; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  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, USA.
 *
 */


/* This file is not compiled separately. Instead it's included into
 * the comm loops using it
 */

static int MAX_POLL_TIME = 1000;	/* see also comm_quick_poll_required() */

#if DELAY_POOLS
static int *slow_fds = NULL;
static int n_slow_fds = 0;
#endif

static void do_select_init(void);

void
comm_select_init(void)
{
#if DELAY_POOLS
    slow_fds = xmalloc(sizeof(int) * Squid_MaxFD);
#endif
    do_select_init();
}

static void do_select_shutdown(void);

void
comm_select_shutdown(void)
{
    do_select_shutdown();
#if DELAY_POOLS
    safe_free(slow_fds);
#endif
}

/* Defer reads from this fd */
void
commDeferFD(int fd)
{
    fde *F = &fd_table[fd];

    assert(fd >= 0);
    assert(F->flags.open);

    if (F->flags.backoff)
	return;

    F->flags.backoff = 1;
    commUpdateEvents(fd);
}

/* Resume reading from the given fd */
void
commResumeFD(int fd)
{
    fde *F = &fd_table[fd];

    assert(fd >= 0);

    if (!F->flags.open) {
	debug(5, 1) ("commResumeFD: fd %d is closed. Ignoring\n", fd);
	F->flags.backoff = 0;
	return;
    }
    if (!F->flags.backoff)
	return;

    F->flags.backoff = 0;
    commUpdateEvents(fd);
}

static int
commDeferRead(int fd)
{
    fde *F = &fd_table[fd];
    if (F->defer_check == NULL)
	return 0;
    return F->defer_check(fd, F->defer_data);
}

#if DELAY_POOLS
static void
commAddSlow(int fd)
{
    fde *F = &fd_table[fd];
    if (F->slow_id)
	return;
    F->slow_id = ++n_slow_fds;
    assert(n_slow_fds < Squid_MaxFD);
    slow_fds[n_slow_fds] = fd;
}

void
commRemoveSlow(int fd)
{
    int fd2;
    fde *F = &fd_table[fd];
    if (!F->slow_id)
	return;
    fd2 = slow_fds[n_slow_fds--];
    if (F->slow_id <= n_slow_fds) {
	fde *F2;
	slow_fds[F->slow_id] = fd2;
	F2 = &fd_table[fd2];
	F2->slow_id = F->slow_id;
    }
    F->slow_id = 0;
}
#endif

static int comm_select_handled;

static inline int do_comm_select(int msec);

static inline void comm_call_handlers(int fd, int read_event, int write_event);

static inline void
do_call_incoming(int fd)
{
    fde *F = &fd_table[fd];
    if (!F->flags.backoff)
	comm_call_handlers(fd, -1, -1);
}

static void
do_check_incoming(void)
{
    int i;
    for (i = 0; i < NHttpSockets; i++)
	do_call_incoming(HttpSockets[i]);
    if (theInIcpConnection >= 0)
	do_call_incoming(theInIcpConnection);
    if (theOutIcpConnection != theInIcpConnection)
	do_call_incoming(theOutIcpConnection);
}

static inline void
check_incoming(void)
{
    comm_select_handled++;
    if (comm_select_handled > Config.incoming_rate) {
	comm_select_handled = 0;
	do_check_incoming();
    }
}

#if DELAY_POOLS
static void
comm_call_slowfds(void)
{
    while (n_slow_fds) {
	int i = (squid_random() % n_slow_fds) + 1;
	int fd = slow_fds[i];
	fde *F = &fd_table[fd];
	commRemoveSlow(fd);
	if (F->read_handler) {
	    PF *hdl = F->read_handler;
	    void *hdl_data = F->read_data;
	    debug(5, 8) ("comm_call_handlers(): Calling read handler on fd=%d\n", fd);
#if SIMPLE_COMM_HANDLER
	    commUpdateReadHandler(fd, NULL, NULL);
	    commResumeFD(fd);
	    hdl(fd, hdl_data);
#else
	    /* Optimized version to avoid the fd bouncing in/out of the waited set */
	    F->read_handler = NULL;
	    F->read_data = NULL;
	    F->read_pending = COMM_PENDING_NORMAL;
	    hdl(fd, hdl_data);
	    /* backoff check is for delayed connections kicked alive from checkTimeouts */
	    if (F->flags.open && (!F->read_handler || F->flags.backoff)) {
		if (F->flags.backoff && commDeferRead(fd) != 1)
		    F->flags.backoff = 0;
		commUpdateEvents(fd);
	    }
#endif
	    statCounter.select_fds++;
	    check_incoming();
	}
    }
}
#endif

static inline void
comm_call_handlers(int fd, int read_event, int write_event)
{
    fde *F = &fd_table[fd];
    const int do_incoming = read_event == 1 || write_event == 1;
    debug(5, 8) ("comm_call_handlers(): got fd=%d read_event=%x write_event=%x F->read_handler=%p F->write_handler=%p\n"
	,fd, read_event, write_event, F->read_handler, F->write_handler);
    if (F->read_handler) {
	int do_read = 0;
	switch (F->read_pending) {
	case COMM_PENDING_NORMAL:
	case COMM_PENDING_WANTS_READ:
	    do_read = read_event;
	    break;
	case COMM_PENDING_WANTS_WRITE:
	    do_read = write_event;
	    break;
	case COMM_PENDING_NOW:
	    do_read = 1;
	    break;
	}
	if (do_read) {
	    PF *hdl = F->read_handler;
	    void *hdl_data = F->read_data;
	    /* If the descriptor is meant to be deferred, don't handle */
	    switch (commDeferRead(fd)) {
#if DELAY_POOLS
	    case -1:
		commAddSlow(fd);
		break;
#endif
	    default:
		if (!(F->flags.backoff)) {
		    debug(5, 1) ("comm_call_handlers(): WARNING defer handler for fd=%d (desc=%s) does not call commDeferFD() - backing off manually\n", fd, F->desc);
		    commDeferFD(fd);
		}
		break;
	    case 0:
		debug(5, 8) ("comm_call_handlers(): Calling read handler on fd=%d\n", fd);
#if SIMPLE_COMM_HANDLER
		commUpdateReadHandler(fd, NULL, NULL);
		hdl(fd, hdl_data);
#else
		/* Optimized version to avoid the fd bouncing in/out of the waited set */
		F->read_handler = NULL;
		F->read_data = NULL;
		F->read_pending = COMM_PENDING_NORMAL;
		hdl(fd, hdl_data);
		if (F->flags.open && !F->read_handler)
		    commUpdateEvents(fd);
#endif
		statCounter.select_fds++;
		if (do_incoming)
		    check_incoming();
		break;
	    }
	}
    }
    if (F->write_handler) {
	int do_write = 0;
	switch (F->write_pending) {
	case COMM_PENDING_WANTS_READ:
	    do_write = read_event;
	    break;
	case COMM_PENDING_NORMAL:
	case COMM_PENDING_WANTS_WRITE:
	    do_write = write_event;
	    break;
	case COMM_PENDING_NOW:
	    do_write = 1;
	    break;
	}
	if (do_write) {
	    PF *hdl = F->write_handler;
	    void *hdl_data = F->write_data;
#if SIMPLE_COMM_HANDLER
	    commUpdateWriteHandler(fd, NULL, NULL);
	    hdl(fd, hdl_data);
#else
	    /* Optimized version to avoid the fd bouncing in/out of the waited set */
	    F->write_handler = NULL;
	    F->write_data = NULL;
	    F->write_pending = COMM_PENDING_NORMAL;
	    hdl(fd, hdl_data);
	    if (F->flags.open)
		commUpdateEvents(fd);
#endif
	    statCounter.select_fds++;
	    if (do_incoming)
		check_incoming();
	}
    }
}

static void
checkTimeouts(void)
{
    int fd;
    fde *F = NULL;
    PF *callback;
    for (fd = 0; fd <= Biggest_FD; fd++) {
	F = &fd_table[fd];
	if (!F->flags.open)
	    continue;
	if (F->flags.backoff) {
	    switch (commDeferRead(fd)) {
	    case 0:
		commResumeFD(fd);
		break;
#if DELAY_POOLS
	    case -1:
		commAddSlow(fd);
		break;
#endif
	    }
	}
	if (F->timeout == 0)
	    continue;
	if (F->timeout > squid_curtime)
	    continue;
	debug(5, 5) ("checkTimeouts: FD %d Expired\n", fd);
	if (F->flags.backoff)
	    commResumeFD(fd);
	if (F->timeout_handler) {
	    debug(5, 5) ("checkTimeouts: FD %d: Call timeout handler\n", fd);
	    callback = F->timeout_handler;
	    F->timeout_handler = NULL;
	    callback(fd, F->timeout_data);
	} else {
	    debug(5, 5) ("checkTimeouts: FD %d: Forcing comm_close()\n", fd);
	    comm_close(fd);
	}
    }
}


int
comm_select(int msec)
{
    static double last_timeout = 0.0;
    int rc;
    double start = current_dtime;

    debug(5, 3) ("comm_select: timeout %d\n", msec);

    if (msec > MAX_POLL_TIME)
	msec = MAX_POLL_TIME;

#if DELAY_POOLS
    /* We have delayed fds in queue? */
    if (n_slow_fds)
	msec = 0;
#endif

    statCounter.select_loops++;

    /* Check for disk io callbacks */
    storeDirCallback();

    /* Check timeouts once per second */
    if (last_timeout + 0.999 < current_dtime) {
	last_timeout = current_dtime;
	checkTimeouts();
    } else {
	int max_timeout = (last_timeout + 1.0 - current_dtime) * 1000;
	if (max_timeout < msec)
	    msec = max_timeout;
    }
    comm_select_handled = 0;

    rc = do_comm_select(msec);

#if DELAY_POOLS
    comm_call_slowfds();
#endif
    getCurrentTime();
    statCounter.select_time += (current_dtime - start);

    if (rc == COMM_TIMEOUT)
	debug(5, 8) ("comm_select: time out\n");

    return rc;
}

/* Called by async-io or diskd to speed up the polling */
void
comm_quick_poll_required(void)
{
    MAX_POLL_TIME = 10;
}
