From: Willy Tarreau Date: Sun, 30 Nov 2008 17:47:21 +0000 (+0100) Subject: [CLEANUP] move the session-related functions to session.c X-Git-Tag: v1.3.16-rc1~137 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=55a8d0e1bb1507c7c80e812dff6e516e29f3c507;p=thirdparty%2Fhaproxy.git [CLEANUP] move the session-related functions to session.c proto_http.c was not suitable for session-related processing, it was just convenient for the tranformation. Some more splitting must occur: process_request/response in proto_http.c must be split again per protocol, and the caller must run a list. Some functions should be directly attached to the session or the buffer (eg: perform_http_redirect, return_srv_error, http_sess_log). --- diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index 17f302dc5c..0fcbea65c8 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -3,7 +3,7 @@ This file contains HTTP protocol definitions. Copyright (C) 2000-2008 Willy Tarreau - w@1wt.eu - + 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, version 2.1 @@ -87,6 +87,9 @@ void init_proto_http(); int http_find_header2(const char *name, int len, const char *sol, struct hdr_idx *idx, struct hdr_ctx *ctx); +void http_sess_log(struct session *s); +void perform_http_redirect(struct session *s, struct stream_interface *si); +void return_srv_error(struct session *s, int err_type); #endif /* _PROTO_PROTO_HTTP_H */ diff --git a/include/types/protocols.h b/include/types/protocols.h index 745b440a71..0816351f6f 100644 --- a/include/types/protocols.h +++ b/include/types/protocols.h @@ -3,7 +3,7 @@ This file defines the structures used by generic network protocols. Copyright (C) 2000-2008 Willy Tarreau - w@1wt.eu - + 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, version 2.1 @@ -30,6 +30,8 @@ #include #include +#include + /* max length of a protcol name, including trailing zero */ #define PROTO_NAME_LEN 16 diff --git a/src/proto_http.c b/src/proto_http.c index fa83c88d90..7bc9fa6d5f 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -206,11 +206,6 @@ fd_set url_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set) #error "Check if your OS uses bitfields for fd_sets" #endif -int sess_update_st_con_tcp(struct session *s, struct stream_interface *si); -int sess_update_st_cer(struct session *s, struct stream_interface *si); -void sess_establish(struct session *s, struct stream_interface *si); -void return_srv_error(struct session *s, int err_type); - void init_proto_http() { int i; @@ -375,8 +370,6 @@ const char http_is_ver_token[256] = { static char *cli_stnames[4] = { "DAT", "SHR", "SHW", "CLS" }; #endif -static void http_sess_log(struct session *s); - /* * Adds a header and its CRLF at the tail of buffer , just before the last * CRLF. Text length is measured first, so it cannot be NULL. @@ -650,166 +643,11 @@ http_get_path(struct http_txn *txn) return ptr; } - -/* Update stream interface status for input states SI_ST_ASS, SI_ST_QUE, SI_ST_TAR. - * Other input states are simply ignored. - * Possible output states are SI_ST_CLO, SI_ST_TAR, SI_ST_ASS, SI_ST_REQ, SI_ST_CON. - * Flags must have previously been updated for timeouts and other conditions. - */ -void sess_update_stream_int(struct session *s, struct stream_interface *si) -{ - DPRINTF(stderr,"[%u] %s: sess=%p rq=%p, rp=%p, exp(r,w)=%u,%u rqf=%08x rpf=%08x rql=%d rpl=%d cs=%d ss=%d\n", - now_ms, __FUNCTION__, - s, - s->req, s->rep, - s->req->rex, s->rep->wex, - s->req->flags, s->rep->flags, - s->req->l, s->rep->l, s->rep->cons->state, s->req->cons->state); - - if (si->state == SI_ST_ASS) { - /* Server assigned to connection request, we have to try to connect now */ - int conn_err; - - conn_err = connect_server(s); - if (conn_err == SN_ERR_NONE) { - /* state = SI_ST_CON now */ - return; - } - - /* We have received a synchronous error. We might have to - * abort, retry immediately or redispatch. - */ - if (conn_err == SN_ERR_INTERNAL) { - if (!si->err_type) { - si->err_type = SI_ET_CONN_OTHER; - si->err_loc = s->srv; - } - - if (s->srv) - s->srv->cum_sess++; - if (s->srv) - s->srv->failed_conns++; - s->be->failed_conns++; - - /* release other sessions waiting for this server */ - if (may_dequeue_tasks(s->srv, s->be)) - process_srv_queue(s->srv); - - /* Failed and not retryable. */ - si->shutr(si); - si->shutw(si); - si->ob->flags |= BF_WRITE_ERROR; - - s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now); - - /* no session was ever accounted for this server */ - si->state = SI_ST_CLO; - return_srv_error(s, si->err_type); - return; - } - - /* We are facing a retryable error, but we don't want to run a - * turn-around now, as the problem is likely a source port - * allocation problem, so we want to retry now. - */ - si->state = SI_ST_CER; - si->flags &= ~SI_FL_ERR; - sess_update_st_cer(s, si); - /* now si->state is one of SI_ST_CLO, SI_ST_TAR, SI_ST_ASS, SI_ST_REQ */ - return; - } - else if (si->state == SI_ST_QUE) { - /* connection request was queued, check for any update */ - if (!s->pend_pos) { - /* The connection is not in the queue anymore. Either - * we have a server connection slot available and we - * go directly to the assigned state, or we need to - * load-balance first and go to the INI state. - */ - si->exp = TICK_ETERNITY; - if (unlikely(!(s->flags & SN_ASSIGNED))) - si->state = SI_ST_REQ; - else { - s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now); - si->state = SI_ST_ASS; - } - return; - } - - /* Connection request still in queue... */ - if (si->flags & SI_FL_EXP) { - /* ... and timeout expired */ - si->exp = TICK_ETERNITY; - s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now); - if (s->srv) - s->srv->failed_conns++; - s->be->failed_conns++; - si->shutr(si); - si->shutw(si); - si->ob->flags |= BF_WRITE_TIMEOUT; - if (!si->err_type) - si->err_type = SI_ET_QUEUE_TO; - si->state = SI_ST_CLO; - return_srv_error(s, si->err_type); - return; - } - - /* Connection remains in queue, check if we have to abort it */ - if ((si->ob->flags & (BF_READ_ERROR|BF_SHUTW_NOW)) || /* abort requested */ - ((si->ob->flags & BF_SHUTR) && /* empty and client stopped */ - (si->ob->flags & BF_EMPTY || s->be->options & PR_O_ABRT_CLOSE))) { - /* give up */ - si->exp = TICK_ETERNITY; - s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now); - si->shutr(si); - si->shutw(si); - si->err_type |= SI_ET_QUEUE_ABRT; - si->state = SI_ST_CLO; - return_srv_error(s, si->err_type); - return; - } - - /* Nothing changed */ - return; - } - else if (si->state == SI_ST_TAR) { - /* Connection request might be aborted */ - if ((si->ob->flags & (BF_READ_ERROR|BF_SHUTW_NOW)) || /* abort requested */ - ((si->ob->flags & BF_SHUTR) && /* empty and client stopped */ - (si->ob->flags & BF_EMPTY || s->be->options & PR_O_ABRT_CLOSE))) { - /* give up */ - si->exp = TICK_ETERNITY; - si->shutr(si); - si->shutw(si); - si->err_type |= SI_ET_CONN_ABRT; - si->state = SI_ST_CLO; - return_srv_error(s, si->err_type); - return; - } - - if (!(si->flags & SI_FL_EXP)) - return; /* still in turn-around */ - - si->exp = TICK_ETERNITY; - - /* we keep trying on the same server as long as the session is - * marked "assigned". - * FIXME: Should we force a redispatch attempt when the server is down ? - */ - if (s->flags & SN_ASSIGNED) - si->state = SI_ST_ASS; - else - si->state = SI_ST_REQ; - return; - } -} - - /* Returns a 302 for a redirectable request. This may only be called just after * the stream interface has moved to SI_ST_ASS. Unprocessable requests are * left unchanged and will follow normal proxy processing. */ -static void perform_http_redirect(struct session *s, struct stream_interface *si) +void perform_http_redirect(struct session *s, struct stream_interface *si) { struct http_txn *txn; struct chunk rdr; @@ -858,49 +696,6 @@ static void perform_http_redirect(struct session *s, struct stream_interface *si s->srv->cum_sess++; } - -/* This function initiates a server connection request on a stream interface - * already in SI_ST_REQ state. Upon success, the state goes to SI_ST_ASS, - * indicating that a server has been assigned. It may also return SI_ST_QUE, - * or SI_ST_CLO upon error. - */ -static void sess_prepare_conn_req(struct session *s, struct stream_interface *si) { - DPRINTF(stderr,"[%u] %s: sess=%p rq=%p, rp=%p, exp(r,w)=%u,%u rqf=%08x rpf=%08x rql=%d rpl=%d cs=%d ss=%d\n", - now_ms, __FUNCTION__, - s, - s->req, s->rep, - s->req->rex, s->rep->wex, - s->req->flags, s->rep->flags, - s->req->l, s->rep->l, s->rep->cons->state, s->req->cons->state); - - if (si->state != SI_ST_REQ) - return; - - /* Try to assign a server */ - if (srv_redispatch_connect(s) != 0) { - /* We did not get a server. Either we queued the - * connection request, or we encountered an error. - */ - if (si->state == SI_ST_QUE) - return; - - /* we did not get any server, let's check the cause */ - si->shutr(si); - si->shutw(si); - si->ob->flags |= BF_WRITE_ERROR; - if (!si->err_type) - si->err_type = SI_ET_CONN_OTHER; - si->state = SI_ST_CLO; - return_srv_error(s, si->err_type); - return; - } - - /* The server is assigned */ - s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now); - si->state = SI_ST_ASS; -} - - /* Return the error message corresponding to err_type. It is assumed * that the server side is closed. Note that err_type is actually a * bitmask, where almost only aborts may be cumulated with other @@ -937,447 +732,6 @@ void return_srv_error(struct session *s, int err_type) 500, error_message(s, HTTP_ERR_500)); } -/* Processes the client, server, request and response jobs of a session task, - * then puts it back to the wait queue in a clean state, or cleans up its - * resources if it must be deleted. Returns in the date the task wants - * to be woken up, or TICK_ETERNITY. In order not to call all functions for - * nothing too many times, the request and response buffers flags are monitored - * and each function is called only if at least another function has changed at - * least one flag it is interested in. - */ -void process_session(struct task *t, int *next) -{ - struct session *s = t->context; - int resync; - unsigned int rqf_last, rpf_last; - - //DPRINTF(stderr, "%s:%d: cs=%d ss=%d(%d) rqf=0x%08x rpf=0x%08x\n", __FUNCTION__, __LINE__, - // s->si[0].state, s->si[1].state, s->si[1].err_type, s->req->flags, s->rep->flags); - - /* 1a: Check for low level timeouts if needed. We just set a flag on - * stream interfaces when their timeouts have expired. - */ - if (unlikely(t->state & TASK_WOKEN_TIMER)) { - stream_int_check_timeouts(&s->si[0]); - stream_int_check_timeouts(&s->si[1]); - buffer_check_timeouts(s->req); - buffer_check_timeouts(s->rep); - } - - /* copy req/rep flags so that we can detect shutdowns */ - rqf_last = s->req->flags; - rpf_last = s->rep->flags; - - /* 1b: check for low-level errors reported at the stream interface. - * First we check if it's a retryable error (in which case we don't - * want to tell the buffer). Otherwise we report the error one level - * upper by setting flags into the buffers. Note that the side towards - * the client cannot have connect (hence retryable) errors. Also, the - * connection setup code must be able to deal with any type of abort. - */ - if (unlikely(s->si[0].flags & SI_FL_ERR)) { - if (s->si[0].state == SI_ST_EST || s->si[0].state == SI_ST_DIS) { - s->si[0].shutr(&s->si[0]); - s->si[0].shutw(&s->si[0]); - stream_int_report_error(&s->si[0]); - } - } - - if (unlikely(s->si[1].flags & SI_FL_ERR)) { - if (s->si[1].state == SI_ST_EST || s->si[1].state == SI_ST_DIS) { - s->si[1].shutr(&s->si[1]); - s->si[1].shutw(&s->si[1]); - stream_int_report_error(&s->si[1]); - s->be->failed_resp++; - if (s->srv) - s->srv->failed_resp++; - } - /* note: maybe we should process connection errors here ? */ - } - - if (s->si[1].state == SI_ST_CON) { - /* we were trying to establish a connection on the server side, - * maybe it succeeded, maybe it failed, maybe we timed out, ... - */ - if (unlikely(!sess_update_st_con_tcp(s, &s->si[1]))) - sess_update_st_cer(s, &s->si[1]); - else if (s->si[1].state == SI_ST_EST) - sess_establish(s, &s->si[1]); - - /* state is now one of SI_ST_CON (still in progress), SI_ST_EST - * (established), SI_ST_DIS (abort), SI_ST_CLO (last error), - * SI_ST_ASS/SI_ST_TAR/SI_ST_REQ for retryable errors. - */ - } - - /* check buffer timeouts, and close the corresponding stream interfaces - * for future reads or writes. Note: this will also concern upper layers - * but we do not touch any other flag. We must be careful and correctly - * detect state changes when calling them. - */ - if (unlikely(s->req->flags & (BF_READ_TIMEOUT|BF_WRITE_TIMEOUT))) { - if (s->req->flags & BF_READ_TIMEOUT) - s->req->prod->shutr(s->req->prod); - if (s->req->flags & BF_WRITE_TIMEOUT) - s->req->cons->shutw(s->req->cons); - DPRINTF(stderr, - "[%u] %s:%d: task=%p s=%p, sfl=0x%08x, rq=%p, rp=%p, exp(r,w)=%u,%u rqf=%08x rpf=%08x rql=%d rpl=%d cs=%d ss=%d, cet=0x%x set=0x%x retr=%d\n", - now_ms, __FUNCTION__, __LINE__, - t, - s, s->flags, - s->req, s->rep, - s->req->rex, s->rep->wex, - s->req->flags, s->rep->flags, - s->req->l, s->rep->l, s->rep->cons->state, s->req->cons->state, - s->rep->cons->err_type, s->req->cons->err_type, - s->conn_retries); - } - - if (unlikely(s->rep->flags & (BF_READ_TIMEOUT|BF_WRITE_TIMEOUT))) { - if (s->rep->flags & BF_READ_TIMEOUT) - s->rep->prod->shutr(s->rep->prod); - if (s->rep->flags & BF_WRITE_TIMEOUT) - s->rep->cons->shutw(s->rep->cons); - DPRINTF(stderr, - "[%u] %s:%d: task=%p s=%p, sfl=0x%08x, rq=%p, rp=%p, exp(r,w)=%u,%u rqf=%08x rpf=%08x rql=%d rpl=%d cs=%d ss=%d, cet=0x%x set=0x%x retr=%d\n", - now_ms, __FUNCTION__, __LINE__, - t, - s, s->flags, - s->req, s->rep, - s->req->rex, s->rep->wex, - s->req->flags, s->rep->flags, - s->req->l, s->rep->l, s->rep->cons->state, s->req->cons->state, - s->rep->cons->err_type, s->req->cons->err_type, - s->conn_retries); - } - - /* Check for connection closure */ - -resync_stream_interface: - DPRINTF(stderr, - "[%u] %s:%d: task=%p s=%p, sfl=0x%08x, rq=%p, rp=%p, exp(r,w)=%u,%u rqf=%08x rpf=%08x rql=%d rpl=%d cs=%d ss=%d, cet=0x%x set=0x%x retr=%d\n", - now_ms, __FUNCTION__, __LINE__, - t, - s, s->flags, - s->req, s->rep, - s->req->rex, s->rep->wex, - s->req->flags, s->rep->flags, - s->req->l, s->rep->l, s->rep->cons->state, s->req->cons->state, - s->rep->cons->err_type, s->req->cons->err_type, - s->conn_retries); - - /* nothing special to be done on client side */ - if (unlikely(s->req->prod->state == SI_ST_DIS)) - s->req->prod->state = SI_ST_CLO; - - /* When a server-side connection is released, we have to count it and - * check for pending connections on this server. - */ - if (unlikely(s->req->cons->state == SI_ST_DIS)) { - s->req->cons->state = SI_ST_CLO; - if (s->srv) { - if (s->flags & SN_CURR_SESS) { - s->flags &= ~SN_CURR_SESS; - s->srv->cur_sess--; - } - sess_change_server(s, NULL); - if (may_dequeue_tasks(s->srv, s->be)) - process_srv_queue(s->srv); - } - } - - /* - * Note: of the transient states (REQ, CER, DIS), only REQ may remain - * at this point. - */ - - /**** Process layer 7 below ****/ - - resync = 0; - - /* Analyse request */ - if ((s->req->flags & BF_MASK_ANALYSER) || - (s->req->flags ^ rqf_last) & BF_MASK_STATIC) { - unsigned int flags = s->req->flags; - - if (s->req->prod->state >= SI_ST_EST) { - /* it's up to the analysers to reset write_ena */ - buffer_write_ena(s->req); - if (s->req->analysers) - process_request(s); - } - s->req->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; - flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; - if (s->req->flags != flags) - resync = 1; - } - - /* reflect what the L7 analysers have seen last */ - rqf_last = s->req->flags; - - /* - * Now forward all shutdown requests between both sides of the buffer - */ - - /* first, let's check if the request buffer needs to shutdown(write) */ - if (unlikely((s->req->flags & (BF_SHUTW|BF_SHUTW_NOW|BF_EMPTY|BF_HIJACK|BF_WRITE_ENA|BF_SHUTR)) == - (BF_EMPTY|BF_WRITE_ENA|BF_SHUTR))) - buffer_shutw_now(s->req); - else if ((s->req->flags & (BF_SHUTW|BF_SHUTW_NOW|BF_EMPTY|BF_WRITE_ENA)) == (BF_EMPTY|BF_WRITE_ENA) && - (s->req->cons->state == SI_ST_EST) && - s->be->options & PR_O_FORCE_CLO && - s->rep->flags & BF_READ_ACTIVITY) { - /* We want to force the connection to the server to close, - * and the server has begun to respond. That's the right - * time. - */ - buffer_shutw_now(s->req); - } - - /* shutdown(write) pending */ - if (unlikely((s->req->flags & (BF_SHUTW|BF_SHUTW_NOW)) == BF_SHUTW_NOW)) - s->req->cons->shutw(s->req->cons); - - /* shutdown(write) done on server side, we must stop the client too */ - if (unlikely((s->req->flags & (BF_SHUTW|BF_SHUTR|BF_SHUTR_NOW)) == BF_SHUTW)) - buffer_shutr_now(s->req); - - /* shutdown(read) pending */ - if (unlikely((s->req->flags & (BF_SHUTR|BF_SHUTR_NOW)) == BF_SHUTR_NOW)) - s->req->prod->shutr(s->req->prod); - - /* it's possible that an upper layer has requested a connection setup */ - if (s->req->cons->state == SI_ST_INI && - (s->req->flags & (BF_WRITE_ENA|BF_SHUTW|BF_SHUTW_NOW)) == BF_WRITE_ENA) - s->req->cons->state = SI_ST_REQ; - - /* we may have a pending connection request, or a connection waiting - * for completion. - */ - if (s->si[1].state >= SI_ST_REQ && s->si[1].state < SI_ST_CON) { - do { - /* nb: step 1 might switch from QUE to ASS, but we first want - * to give a chance to step 2 to perform a redirect if needed. - */ - if (s->si[1].state != SI_ST_REQ) - sess_update_stream_int(s, &s->si[1]); - if (s->si[1].state == SI_ST_REQ) - sess_prepare_conn_req(s, &s->si[1]); - - if (s->si[1].state == SI_ST_ASS && s->srv && - s->srv->rdr_len && (s->flags & SN_REDIRECTABLE)) - perform_http_redirect(s, &s->si[1]); - } while (s->si[1].state == SI_ST_ASS); - } - - /* - * Here we want to check if we need to resync or not. - */ - if ((s->req->flags ^ rqf_last) & BF_MASK_STATIC) - resync = 1; - - s->req->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; - - /* according to benchmarks, it makes sense to resync now */ - if (resync) - goto resync_stream_interface; - - - /* Analyse response */ - - if (unlikely(s->rep->flags & BF_HIJACK)) { - /* In inject mode, we wake up everytime something has - * happened on the write side of the buffer. - */ - unsigned int flags = s->rep->flags; - - if ((s->rep->flags & (BF_WRITE_PARTIAL|BF_WRITE_ERROR|BF_SHUTW)) && - !(s->rep->flags & BF_FULL)) { - produce_content(s); - } - s->rep->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; - flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; - if (s->rep->flags != flags) - resync = 1; - } - else if ((s->rep->flags & BF_MASK_ANALYSER) || - (s->rep->flags ^ rpf_last) & BF_MASK_STATIC) { - unsigned int flags = s->rep->flags; - - if (s->rep->prod->state >= SI_ST_EST) { - /* it's up to the analysers to reset write_ena */ - buffer_write_ena(s->rep); - if (s->rep->analysers) - process_response(s); - } - s->rep->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; - flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; - if (s->rep->flags != flags) - resync = 1; - } - - /* reflect what the L7 analysers have seen last */ - rpf_last = s->rep->flags; - - /* - * Now forward all shutdown requests between both sides of the buffer - */ - - /* - * FIXME: this is probably where we should produce error responses. - */ - - /* first, let's check if the request buffer needs to shutdown(write) */ - if (unlikely((s->rep->flags & (BF_SHUTW|BF_SHUTW_NOW|BF_EMPTY|BF_HIJACK|BF_WRITE_ENA|BF_SHUTR)) == - (BF_EMPTY|BF_WRITE_ENA|BF_SHUTR))) - buffer_shutw_now(s->rep); - - /* shutdown(write) pending */ - if (unlikely((s->rep->flags & (BF_SHUTW|BF_SHUTW_NOW)) == BF_SHUTW_NOW)) - s->rep->cons->shutw(s->rep->cons); - - /* shutdown(write) done on the client side, we must stop the server too */ - if (unlikely((s->rep->flags & (BF_SHUTW|BF_SHUTR|BF_SHUTR_NOW)) == BF_SHUTW)) - buffer_shutr_now(s->rep); - - /* shutdown(read) pending */ - if (unlikely((s->rep->flags & (BF_SHUTR|BF_SHUTR_NOW)) == BF_SHUTR_NOW)) - s->rep->prod->shutr(s->rep->prod); - - /* - * Here we want to check if we need to resync or not. - */ - if ((s->rep->flags ^ rpf_last) & BF_MASK_STATIC) - resync = 1; - - s->rep->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; - - if (resync) - goto resync_stream_interface; - - - /* This is needed only when debugging is enabled, to indicate - * client-side or server-side close. Please note that in the unlikely - * event where both sides would close at once, the sequence is reported - * on the server side first. - */ - if (unlikely((global.mode & MODE_DEBUG) && - (!(global.mode & MODE_QUIET) || - (global.mode & MODE_VERBOSE)))) { - int len; - - if (s->si[1].state == SI_ST_CLO && - s->si[1].prev_state == SI_ST_EST) { - len = sprintf(trash, "%08x:%s.srvcls[%04x:%04x]\n", - s->uniq_id, s->be->id, - (unsigned short)s->si[0].fd, - (unsigned short)s->si[1].fd); - write(1, trash, len); - } - - if (s->si[0].state == SI_ST_CLO && - s->si[0].prev_state == SI_ST_EST) { - len = sprintf(trash, "%08x:%s.clicls[%04x:%04x]\n", - s->uniq_id, s->be->id, - (unsigned short)s->si[0].fd, - (unsigned short)s->si[1].fd); - write(1, trash, len); - } - } - - if (likely((s->rep->cons->state != SI_ST_CLO) || - (s->req->cons->state > SI_ST_INI && s->req->cons->state < SI_ST_CLO))) { - - if ((s->fe->options & PR_O_CONTSTATS) && (s->flags & SN_BE_ASSIGNED)) - session_process_counters(s); - - if (s->rep->cons->state == SI_ST_EST) - stream_sock_data_finish(s->rep->cons->fd); - - if (s->req->cons->state == SI_ST_EST) - stream_sock_data_finish(s->req->cons->fd); - - s->req->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; - s->rep->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; - s->si[0].prev_state = s->si[0].state; - s->si[1].prev_state = s->si[1].state; - s->si[0].flags = s->si[1].flags = SI_FL_NONE; - - /* Trick: if a request is being waiting for the server to respond, - * and if we know the server can timeout, we don't want the timeout - * to expire on the client side first, but we're still interested - * in passing data from the client to the server (eg: POST). Thus, - * we can cancel the client's request timeout if the server's - * request timeout is set and the server has not yet sent a response. - */ - - if ((s->rep->flags & (BF_WRITE_ENA|BF_SHUTR)) == 0 && - (tick_isset(s->req->wex) || tick_isset(s->rep->rex))) - s->req->rex = TICK_ETERNITY; - - t->expire = tick_first(tick_first(s->req->rex, s->req->wex), - tick_first(s->rep->rex, s->rep->wex)); - if (s->req->analysers) - t->expire = tick_first(t->expire, s->req->analyse_exp); - - if (s->si[0].exp) - t->expire = tick_first(t->expire, s->si[0].exp); - - if (s->si[1].exp) - t->expire = tick_first(t->expire, s->si[1].exp); - -#ifdef DEBUG_FULL - fprintf(stderr, "[%u] queuing with exp=%u req->rex=%u req->wex=%u req->ana_exp=%u rep->rex=%u rep->wex=%u, cs=%d, ss=%d\n", - now_ms, t->expire, s->req->rex, s->req->wex, s->req->analyse_exp, s->rep->rex, s->rep->wex, s->si[0].state, s->si[1].state); -#endif - /* restore t to its place in the task list */ - task_queue(t); - -#ifdef DEBUG_DEV - /* this may only happen when no timeout is set or in case of an FSM bug */ - if (!t->expire) - ABORT_NOW(); -#endif - *next = t->expire; - return; /* nothing more to do */ - } - - s->fe->feconn--; - if (s->flags & SN_BE_ASSIGNED) - s->be->beconn--; - actconn--; - - if (unlikely((global.mode & MODE_DEBUG) && - (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)))) { - int len; - len = sprintf(trash, "%08x:%s.closed[%04x:%04x] (term_trace=0x%08x)\n", - s->uniq_id, s->be->id, - (unsigned short)s->req->prod->fd, (unsigned short)s->req->cons->fd, - s->term_trace); - write(1, trash, len); - } - - s->logs.t_close = tv_ms_elapsed(&s->logs.tv_accept, &now); - session_process_counters(s); - - /* let's do a final log if we need it */ - if (s->logs.logwait && - !(s->flags & SN_MONITOR) && - (!(s->fe->options & PR_O_NULLNOLOG) || s->req->total)) { - if (s->fe->to_log & LW_REQ) - http_sess_log(s); - else - tcp_sess_log(s); - } - - /* the task MUST not be in the run queue anymore */ - task_delete(t); - session_free(s); - task_free(t); - *next = TICK_ETERNITY; -} - - extern const char sess_term_cond[8]; extern const char sess_fin_state[8]; extern const char *monthname[12]; @@ -1392,7 +746,7 @@ struct pool_head *pool2_capture; * send a log for the session when we have enough info about it. * Will not log if the frontend has no log defined. */ -static void http_sess_log(struct session *s) +void http_sess_log(struct session *s) { char pn[INET6_ADDRSTRLEN + strlen(":65535")]; struct proxy *fe = s->fe; @@ -3732,215 +3086,6 @@ int process_response(struct session *t) return 0; } - -/* This function is called with (si->state == SI_ST_CON) meaning that a - * connection was attempted and that the file descriptor is already allocated. - * We must check for establishment, error and abort. Possible output states - * are SI_ST_EST (established), SI_ST_CER (error), SI_ST_DIS (abort), and - * SI_ST_CON (no change). The function returns 0 if it switches to SI_ST_CER, - * otherwise 1. - */ -int sess_update_st_con_tcp(struct session *s, struct stream_interface *si) -{ - struct buffer *req = si->ob; - struct buffer *rep = si->ib; - - DPRINTF(stderr,"[%u] %s: c=%s exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d, fds=%d\n", - now_ms, __FUNCTION__, - cli_stnames[s->cli_state], - rep->rex, req->wex, - req->flags, rep->flags, - req->l, rep->l, - fdtab[si->fd].state); - - - /* If we got an error, or if nothing happened and the connection timed - * out, we must give up. The CER state handler will take care of retry - * attempts and error reports. - */ - if (unlikely(si->flags & (SI_FL_EXP|SI_FL_ERR))) { - si->state = SI_ST_CER; - fd_delete(si->fd); - - if (si->err_type) - return 0; - - si->err_loc = s->srv; - if (si->flags & SI_FL_ERR) - si->err_type = SI_ET_CONN_ERR; - else - si->err_type = SI_ET_CONN_TO; - return 0; - } - - /* OK, maybe we want to abort */ - if (unlikely((req->flags & BF_SHUTW_NOW) || - (rep->flags & BF_SHUTW) || - ((req->flags & BF_SHUTR) && /* FIXME: this should not prevent a connection from establishing */ - ((req->flags & BF_EMPTY && !(req->flags & BF_WRITE_ACTIVITY)) || - s->be->options & PR_O_ABRT_CLOSE)))) { - /* give up */ - si->shutw(si); - si->err_type |= SI_ET_CONN_ABRT; - si->err_loc = s->srv; - return 1; - } - - /* we need to wait a bit more if there was no activity either */ - if (!(req->flags & BF_WRITE_ACTIVITY)) - return 1; - - /* OK, this means that a connection succeeded. The caller will be - * responsible for handling the transition from CON to EST. - */ - s->logs.t_connect = tv_ms_elapsed(&s->logs.tv_accept, &now); - si->state = SI_ST_EST; - si->err_type = SI_ET_NONE; - si->err_loc = NULL; - return 1; -} - - -/* - * This function handles the transition between the SI_ST_CON state and the - * SI_ST_EST state. It must only be called after switching from SI_ST_CON to - * SI_ST_EST. - */ -void sess_establish(struct session *s, struct stream_interface *si) -{ - struct buffer *req = si->ob; - struct buffer *rep = si->ib; - - if (req->flags & BF_EMPTY) { - EV_FD_CLR(si->fd, DIR_WR); - req->wex = TICK_ETERNITY; - } else { - EV_FD_SET(si->fd, DIR_WR); - req->wex = tick_add_ifset(now_ms, s->be->timeout.server); - if (tick_isset(req->wex)) { - /* FIXME: to prevent the server from expiring read - * timeouts during writes, we refresh it. */ - rep->rex = req->wex; - } - } - - if (s->be->mode == PR_MODE_TCP) { /* let's allow immediate data connection in this case */ - if (!(rep->flags & BF_HIJACK)) { - EV_FD_SET(si->fd, DIR_RD); - rep->rex = tick_add_ifset(now_ms, s->be->timeout.server); - } - buffer_set_rlim(rep, BUFSIZE); /* no rewrite needed */ - - /* if the user wants to log as soon as possible, without counting - * bytes from the server, then this is the right moment. */ - if (s->fe->to_log && !(s->logs.logwait & LW_BYTES)) { - s->logs.t_close = s->logs.t_connect; /* to get a valid end date */ - tcp_sess_log(s); - } -#ifdef CONFIG_HAP_TCPSPLICE - if ((s->fe->options & s->be->options) & PR_O_TCPSPLICE) { - /* TCP splicing supported by both FE and BE */ - tcp_splice_splicefd(req->prod->fd, si->fd, 0); - } -#endif - } - else { - rep->analysers |= AN_RTR_HTTP_HDR; - buffer_set_rlim(rep, BUFSIZE - MAXREWRITE); /* rewrite needed */ - s->txn.rsp.msg_state = HTTP_MSG_RPBEFORE; - /* reset hdr_idx which was already initialized by the request. - * right now, the http parser does it. - * hdr_idx_init(&s->txn.hdr_idx); - */ - } - - rep->flags |= BF_READ_ATTACHED; /* producer is now attached */ - req->wex = TICK_ETERNITY; -} - - -/* This function is called with (si->state == SI_ST_CER) meaning that a - * previous connection attempt has failed and that the file descriptor - * has already been released. Possible causes include asynchronous error - * notification and time out. Possible output states are SI_ST_CLO when - * retries are exhausted, SI_ST_TAR when a delay is wanted before a new - * connection attempt, SI_ST_ASS when it's wise to retry on the same server, - * and SI_ST_REQ when an immediate redispatch is wanted. The buffers are - * marked as in error state. It returns 0. - */ -int sess_update_st_cer(struct session *s, struct stream_interface *si) -{ - /* we probably have to release last session from the server */ - if (s->srv) { - if (s->flags & SN_CURR_SESS) { - s->flags &= ~SN_CURR_SESS; - s->srv->cur_sess--; - } - sess_change_server(s, NULL); - } - - /* ensure that we have enough retries left */ - s->conn_retries--; - if (s->conn_retries < 0) { - if (!si->err_type) { - si->err_type = SI_ET_CONN_ERR; - si->err_loc = s->srv; - } - - if (s->srv) - s->srv->failed_conns++; - s->be->failed_conns++; - if (may_dequeue_tasks(s->srv, s->be)) - process_srv_queue(s->srv); - - /* shutw is enough so stop a connecting socket */ - si->shutw(si); - si->ob->flags |= BF_WRITE_ERROR; - si->ib->flags |= BF_READ_ERROR; - - si->state = SI_ST_CLO; - return_srv_error(s, si->err_type); - return 0; - } - - /* If the "redispatch" option is set on the backend, we are allowed to - * retry on another server for the last retry. In order to achieve this, - * we must mark the session unassigned, and eventually clear the DIRECT - * bit to ignore any persistence cookie. We won't count a retry nor a - * redispatch yet, because this will depend on what server is selected. - */ - if (s->srv && s->conn_retries == 0 && s->be->options & PR_O_REDISP) { - if (may_dequeue_tasks(s->srv, s->be)) - process_srv_queue(s->srv); - - s->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET); - s->prev_srv = s->srv; - si->state = SI_ST_REQ; - } else { - if (s->srv) - s->srv->retries++; - s->be->retries++; - si->state = SI_ST_ASS; - } - - if (si->flags & SI_FL_ERR) { - /* The error was an asynchronous connection error, and we will - * likely have to retry connecting to the same server, most - * likely leading to the same result. To avoid this, we wait - * one second before retrying. - */ - - if (!si->err_type) - si->err_type = SI_ET_CONN_ERR; - - si->state = SI_ST_TAR; - si->exp = tick_add(now_ms, MS_TO_TICKS(1000)); - return 0; - } - return 0; -} - - /* * Produces data for the session depending on its source. Expects to be * called with client socket shut down on input. Right now, only statistics can diff --git a/src/session.c b/src/session.c index 8a46962974..75cc3f3895 100644 --- a/src/session.c +++ b/src/session.c @@ -17,12 +17,19 @@ #include #include +#include +#include #include #include #include #include +#include +#include #include +#include +#include +#include struct pool_head *pool2_session; @@ -132,6 +139,845 @@ void session_process_counters(struct session *s) } } +/* This function is called with (si->state == SI_ST_CON) meaning that a + * connection was attempted and that the file descriptor is already allocated. + * We must check for establishment, error and abort. Possible output states + * are SI_ST_EST (established), SI_ST_CER (error), SI_ST_DIS (abort), and + * SI_ST_CON (no change). The function returns 0 if it switches to SI_ST_CER, + * otherwise 1. + */ +int sess_update_st_con_tcp(struct session *s, struct stream_interface *si) +{ + struct buffer *req = si->ob; + struct buffer *rep = si->ib; + + DPRINTF(stderr,"[%u] %s: c=%s exp(r,w)=%u,%u req=%08x rep=%08x rql=%d rpl=%d, fds=%d\n", + now_ms, __FUNCTION__, + cli_stnames[s->cli_state], + rep->rex, req->wex, + req->flags, rep->flags, + req->l, rep->l, + fdtab[si->fd].state); + + + /* If we got an error, or if nothing happened and the connection timed + * out, we must give up. The CER state handler will take care of retry + * attempts and error reports. + */ + if (unlikely(si->flags & (SI_FL_EXP|SI_FL_ERR))) { + si->state = SI_ST_CER; + fd_delete(si->fd); + + if (si->err_type) + return 0; + + si->err_loc = s->srv; + if (si->flags & SI_FL_ERR) + si->err_type = SI_ET_CONN_ERR; + else + si->err_type = SI_ET_CONN_TO; + return 0; + } + + /* OK, maybe we want to abort */ + if (unlikely((req->flags & BF_SHUTW_NOW) || + (rep->flags & BF_SHUTW) || + ((req->flags & BF_SHUTR) && /* FIXME: this should not prevent a connection from establishing */ + ((req->flags & BF_EMPTY && !(req->flags & BF_WRITE_ACTIVITY)) || + s->be->options & PR_O_ABRT_CLOSE)))) { + /* give up */ + si->shutw(si); + si->err_type |= SI_ET_CONN_ABRT; + si->err_loc = s->srv; + return 1; + } + + /* we need to wait a bit more if there was no activity either */ + if (!(req->flags & BF_WRITE_ACTIVITY)) + return 1; + + /* OK, this means that a connection succeeded. The caller will be + * responsible for handling the transition from CON to EST. + */ + s->logs.t_connect = tv_ms_elapsed(&s->logs.tv_accept, &now); + si->state = SI_ST_EST; + si->err_type = SI_ET_NONE; + si->err_loc = NULL; + return 1; +} + +/* This function is called with (si->state == SI_ST_CER) meaning that a + * previous connection attempt has failed and that the file descriptor + * has already been released. Possible causes include asynchronous error + * notification and time out. Possible output states are SI_ST_CLO when + * retries are exhausted, SI_ST_TAR when a delay is wanted before a new + * connection attempt, SI_ST_ASS when it's wise to retry on the same server, + * and SI_ST_REQ when an immediate redispatch is wanted. The buffers are + * marked as in error state. It returns 0. + */ +int sess_update_st_cer(struct session *s, struct stream_interface *si) +{ + /* we probably have to release last session from the server */ + if (s->srv) { + if (s->flags & SN_CURR_SESS) { + s->flags &= ~SN_CURR_SESS; + s->srv->cur_sess--; + } + sess_change_server(s, NULL); + } + + /* ensure that we have enough retries left */ + s->conn_retries--; + if (s->conn_retries < 0) { + if (!si->err_type) { + si->err_type = SI_ET_CONN_ERR; + si->err_loc = s->srv; + } + + if (s->srv) + s->srv->failed_conns++; + s->be->failed_conns++; + if (may_dequeue_tasks(s->srv, s->be)) + process_srv_queue(s->srv); + + /* shutw is enough so stop a connecting socket */ + si->shutw(si); + si->ob->flags |= BF_WRITE_ERROR; + si->ib->flags |= BF_READ_ERROR; + + si->state = SI_ST_CLO; + return_srv_error(s, si->err_type); + return 0; + } + + /* If the "redispatch" option is set on the backend, we are allowed to + * retry on another server for the last retry. In order to achieve this, + * we must mark the session unassigned, and eventually clear the DIRECT + * bit to ignore any persistence cookie. We won't count a retry nor a + * redispatch yet, because this will depend on what server is selected. + */ + if (s->srv && s->conn_retries == 0 && s->be->options & PR_O_REDISP) { + if (may_dequeue_tasks(s->srv, s->be)) + process_srv_queue(s->srv); + + s->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET); + s->prev_srv = s->srv; + si->state = SI_ST_REQ; + } else { + if (s->srv) + s->srv->retries++; + s->be->retries++; + si->state = SI_ST_ASS; + } + + if (si->flags & SI_FL_ERR) { + /* The error was an asynchronous connection error, and we will + * likely have to retry connecting to the same server, most + * likely leading to the same result. To avoid this, we wait + * one second before retrying. + */ + + if (!si->err_type) + si->err_type = SI_ET_CONN_ERR; + + si->state = SI_ST_TAR; + si->exp = tick_add(now_ms, MS_TO_TICKS(1000)); + return 0; + } + return 0; +} + +/* + * This function handles the transition between the SI_ST_CON state and the + * SI_ST_EST state. It must only be called after switching from SI_ST_CON to + * SI_ST_EST. + */ +void sess_establish(struct session *s, struct stream_interface *si) +{ + struct buffer *req = si->ob; + struct buffer *rep = si->ib; + + if (req->flags & BF_EMPTY) { + EV_FD_CLR(si->fd, DIR_WR); + req->wex = TICK_ETERNITY; + } else { + EV_FD_SET(si->fd, DIR_WR); + req->wex = tick_add_ifset(now_ms, s->be->timeout.server); + if (tick_isset(req->wex)) { + /* FIXME: to prevent the server from expiring read + * timeouts during writes, we refresh it. */ + rep->rex = req->wex; + } + } + + if (s->be->mode == PR_MODE_TCP) { /* let's allow immediate data connection in this case */ + if (!(rep->flags & BF_HIJACK)) { + EV_FD_SET(si->fd, DIR_RD); + rep->rex = tick_add_ifset(now_ms, s->be->timeout.server); + } + buffer_set_rlim(rep, BUFSIZE); /* no rewrite needed */ + + /* if the user wants to log as soon as possible, without counting + * bytes from the server, then this is the right moment. */ + if (s->fe->to_log && !(s->logs.logwait & LW_BYTES)) { + s->logs.t_close = s->logs.t_connect; /* to get a valid end date */ + tcp_sess_log(s); + } +#ifdef CONFIG_HAP_TCPSPLICE + if ((s->fe->options & s->be->options) & PR_O_TCPSPLICE) { + /* TCP splicing supported by both FE and BE */ + tcp_splice_splicefd(req->prod->fd, si->fd, 0); + } +#endif + } + else { + rep->analysers |= AN_RTR_HTTP_HDR; + buffer_set_rlim(rep, BUFSIZE - MAXREWRITE); /* rewrite needed */ + s->txn.rsp.msg_state = HTTP_MSG_RPBEFORE; + /* reset hdr_idx which was already initialized by the request. + * right now, the http parser does it. + * hdr_idx_init(&s->txn.hdr_idx); + */ + } + + rep->flags |= BF_READ_ATTACHED; /* producer is now attached */ + req->wex = TICK_ETERNITY; +} + +/* Update stream interface status for input states SI_ST_ASS, SI_ST_QUE, SI_ST_TAR. + * Other input states are simply ignored. + * Possible output states are SI_ST_CLO, SI_ST_TAR, SI_ST_ASS, SI_ST_REQ, SI_ST_CON. + * Flags must have previously been updated for timeouts and other conditions. + */ +void sess_update_stream_int(struct session *s, struct stream_interface *si) +{ + DPRINTF(stderr,"[%u] %s: sess=%p rq=%p, rp=%p, exp(r,w)=%u,%u rqf=%08x rpf=%08x rql=%d rpl=%d cs=%d ss=%d\n", + now_ms, __FUNCTION__, + s, + s->req, s->rep, + s->req->rex, s->rep->wex, + s->req->flags, s->rep->flags, + s->req->l, s->rep->l, s->rep->cons->state, s->req->cons->state); + + if (si->state == SI_ST_ASS) { + /* Server assigned to connection request, we have to try to connect now */ + int conn_err; + + conn_err = connect_server(s); + if (conn_err == SN_ERR_NONE) { + /* state = SI_ST_CON now */ + return; + } + + /* We have received a synchronous error. We might have to + * abort, retry immediately or redispatch. + */ + if (conn_err == SN_ERR_INTERNAL) { + if (!si->err_type) { + si->err_type = SI_ET_CONN_OTHER; + si->err_loc = s->srv; + } + + if (s->srv) + s->srv->cum_sess++; + if (s->srv) + s->srv->failed_conns++; + s->be->failed_conns++; + + /* release other sessions waiting for this server */ + if (may_dequeue_tasks(s->srv, s->be)) + process_srv_queue(s->srv); + + /* Failed and not retryable. */ + si->shutr(si); + si->shutw(si); + si->ob->flags |= BF_WRITE_ERROR; + + s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now); + + /* no session was ever accounted for this server */ + si->state = SI_ST_CLO; + return_srv_error(s, si->err_type); + return; + } + + /* We are facing a retryable error, but we don't want to run a + * turn-around now, as the problem is likely a source port + * allocation problem, so we want to retry now. + */ + si->state = SI_ST_CER; + si->flags &= ~SI_FL_ERR; + sess_update_st_cer(s, si); + /* now si->state is one of SI_ST_CLO, SI_ST_TAR, SI_ST_ASS, SI_ST_REQ */ + return; + } + else if (si->state == SI_ST_QUE) { + /* connection request was queued, check for any update */ + if (!s->pend_pos) { + /* The connection is not in the queue anymore. Either + * we have a server connection slot available and we + * go directly to the assigned state, or we need to + * load-balance first and go to the INI state. + */ + si->exp = TICK_ETERNITY; + if (unlikely(!(s->flags & SN_ASSIGNED))) + si->state = SI_ST_REQ; + else { + s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now); + si->state = SI_ST_ASS; + } + return; + } + + /* Connection request still in queue... */ + if (si->flags & SI_FL_EXP) { + /* ... and timeout expired */ + si->exp = TICK_ETERNITY; + s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now); + if (s->srv) + s->srv->failed_conns++; + s->be->failed_conns++; + si->shutr(si); + si->shutw(si); + si->ob->flags |= BF_WRITE_TIMEOUT; + if (!si->err_type) + si->err_type = SI_ET_QUEUE_TO; + si->state = SI_ST_CLO; + return_srv_error(s, si->err_type); + return; + } + + /* Connection remains in queue, check if we have to abort it */ + if ((si->ob->flags & (BF_READ_ERROR|BF_SHUTW_NOW)) || /* abort requested */ + ((si->ob->flags & BF_SHUTR) && /* empty and client stopped */ + (si->ob->flags & BF_EMPTY || s->be->options & PR_O_ABRT_CLOSE))) { + /* give up */ + si->exp = TICK_ETERNITY; + s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now); + si->shutr(si); + si->shutw(si); + si->err_type |= SI_ET_QUEUE_ABRT; + si->state = SI_ST_CLO; + return_srv_error(s, si->err_type); + return; + } + + /* Nothing changed */ + return; + } + else if (si->state == SI_ST_TAR) { + /* Connection request might be aborted */ + if ((si->ob->flags & (BF_READ_ERROR|BF_SHUTW_NOW)) || /* abort requested */ + ((si->ob->flags & BF_SHUTR) && /* empty and client stopped */ + (si->ob->flags & BF_EMPTY || s->be->options & PR_O_ABRT_CLOSE))) { + /* give up */ + si->exp = TICK_ETERNITY; + si->shutr(si); + si->shutw(si); + si->err_type |= SI_ET_CONN_ABRT; + si->state = SI_ST_CLO; + return_srv_error(s, si->err_type); + return; + } + + if (!(si->flags & SI_FL_EXP)) + return; /* still in turn-around */ + + si->exp = TICK_ETERNITY; + + /* we keep trying on the same server as long as the session is + * marked "assigned". + * FIXME: Should we force a redispatch attempt when the server is down ? + */ + if (s->flags & SN_ASSIGNED) + si->state = SI_ST_ASS; + else + si->state = SI_ST_REQ; + return; + } +} + +/* This function initiates a server connection request on a stream interface + * already in SI_ST_REQ state. Upon success, the state goes to SI_ST_ASS, + * indicating that a server has been assigned. It may also return SI_ST_QUE, + * or SI_ST_CLO upon error. + */ +static void sess_prepare_conn_req(struct session *s, struct stream_interface *si) { + DPRINTF(stderr,"[%u] %s: sess=%p rq=%p, rp=%p, exp(r,w)=%u,%u rqf=%08x rpf=%08x rql=%d rpl=%d cs=%d ss=%d\n", + now_ms, __FUNCTION__, + s, + s->req, s->rep, + s->req->rex, s->rep->wex, + s->req->flags, s->rep->flags, + s->req->l, s->rep->l, s->rep->cons->state, s->req->cons->state); + + if (si->state != SI_ST_REQ) + return; + + /* Try to assign a server */ + if (srv_redispatch_connect(s) != 0) { + /* We did not get a server. Either we queued the + * connection request, or we encountered an error. + */ + if (si->state == SI_ST_QUE) + return; + + /* we did not get any server, let's check the cause */ + si->shutr(si); + si->shutw(si); + si->ob->flags |= BF_WRITE_ERROR; + if (!si->err_type) + si->err_type = SI_ET_CONN_OTHER; + si->state = SI_ST_CLO; + return_srv_error(s, si->err_type); + return; + } + + /* The server is assigned */ + s->logs.t_queue = tv_ms_elapsed(&s->logs.tv_accept, &now); + si->state = SI_ST_ASS; +} + +/* Processes the client, server, request and response jobs of a session task, + * then puts it back to the wait queue in a clean state, or cleans up its + * resources if it must be deleted. Returns in the date the task wants + * to be woken up, or TICK_ETERNITY. In order not to call all functions for + * nothing too many times, the request and response buffers flags are monitored + * and each function is called only if at least another function has changed at + * least one flag it is interested in. + */ +void process_session(struct task *t, int *next) +{ + struct session *s = t->context; + int resync; + unsigned int rqf_last, rpf_last; + + //DPRINTF(stderr, "%s:%d: cs=%d ss=%d(%d) rqf=0x%08x rpf=0x%08x\n", __FUNCTION__, __LINE__, + // s->si[0].state, s->si[1].state, s->si[1].err_type, s->req->flags, s->rep->flags); + + /* 1a: Check for low level timeouts if needed. We just set a flag on + * stream interfaces when their timeouts have expired. + */ + if (unlikely(t->state & TASK_WOKEN_TIMER)) { + stream_int_check_timeouts(&s->si[0]); + stream_int_check_timeouts(&s->si[1]); + buffer_check_timeouts(s->req); + buffer_check_timeouts(s->rep); + } + + /* copy req/rep flags so that we can detect shutdowns */ + rqf_last = s->req->flags; + rpf_last = s->rep->flags; + + /* 1b: check for low-level errors reported at the stream interface. + * First we check if it's a retryable error (in which case we don't + * want to tell the buffer). Otherwise we report the error one level + * upper by setting flags into the buffers. Note that the side towards + * the client cannot have connect (hence retryable) errors. Also, the + * connection setup code must be able to deal with any type of abort. + */ + if (unlikely(s->si[0].flags & SI_FL_ERR)) { + if (s->si[0].state == SI_ST_EST || s->si[0].state == SI_ST_DIS) { + s->si[0].shutr(&s->si[0]); + s->si[0].shutw(&s->si[0]); + stream_int_report_error(&s->si[0]); + } + } + + if (unlikely(s->si[1].flags & SI_FL_ERR)) { + if (s->si[1].state == SI_ST_EST || s->si[1].state == SI_ST_DIS) { + s->si[1].shutr(&s->si[1]); + s->si[1].shutw(&s->si[1]); + stream_int_report_error(&s->si[1]); + s->be->failed_resp++; + if (s->srv) + s->srv->failed_resp++; + } + /* note: maybe we should process connection errors here ? */ + } + + if (s->si[1].state == SI_ST_CON) { + /* we were trying to establish a connection on the server side, + * maybe it succeeded, maybe it failed, maybe we timed out, ... + */ + if (unlikely(!sess_update_st_con_tcp(s, &s->si[1]))) + sess_update_st_cer(s, &s->si[1]); + else if (s->si[1].state == SI_ST_EST) + sess_establish(s, &s->si[1]); + + /* state is now one of SI_ST_CON (still in progress), SI_ST_EST + * (established), SI_ST_DIS (abort), SI_ST_CLO (last error), + * SI_ST_ASS/SI_ST_TAR/SI_ST_REQ for retryable errors. + */ + } + + /* check buffer timeouts, and close the corresponding stream interfaces + * for future reads or writes. Note: this will also concern upper layers + * but we do not touch any other flag. We must be careful and correctly + * detect state changes when calling them. + */ + if (unlikely(s->req->flags & (BF_READ_TIMEOUT|BF_WRITE_TIMEOUT))) { + if (s->req->flags & BF_READ_TIMEOUT) + s->req->prod->shutr(s->req->prod); + if (s->req->flags & BF_WRITE_TIMEOUT) + s->req->cons->shutw(s->req->cons); + DPRINTF(stderr, + "[%u] %s:%d: task=%p s=%p, sfl=0x%08x, rq=%p, rp=%p, exp(r,w)=%u,%u rqf=%08x rpf=%08x rql=%d rpl=%d cs=%d ss=%d, cet=0x%x set=0x%x retr=%d\n", + now_ms, __FUNCTION__, __LINE__, + t, + s, s->flags, + s->req, s->rep, + s->req->rex, s->rep->wex, + s->req->flags, s->rep->flags, + s->req->l, s->rep->l, s->rep->cons->state, s->req->cons->state, + s->rep->cons->err_type, s->req->cons->err_type, + s->conn_retries); + } + + if (unlikely(s->rep->flags & (BF_READ_TIMEOUT|BF_WRITE_TIMEOUT))) { + if (s->rep->flags & BF_READ_TIMEOUT) + s->rep->prod->shutr(s->rep->prod); + if (s->rep->flags & BF_WRITE_TIMEOUT) + s->rep->cons->shutw(s->rep->cons); + DPRINTF(stderr, + "[%u] %s:%d: task=%p s=%p, sfl=0x%08x, rq=%p, rp=%p, exp(r,w)=%u,%u rqf=%08x rpf=%08x rql=%d rpl=%d cs=%d ss=%d, cet=0x%x set=0x%x retr=%d\n", + now_ms, __FUNCTION__, __LINE__, + t, + s, s->flags, + s->req, s->rep, + s->req->rex, s->rep->wex, + s->req->flags, s->rep->flags, + s->req->l, s->rep->l, s->rep->cons->state, s->req->cons->state, + s->rep->cons->err_type, s->req->cons->err_type, + s->conn_retries); + } + + /* Check for connection closure */ + +resync_stream_interface: + DPRINTF(stderr, + "[%u] %s:%d: task=%p s=%p, sfl=0x%08x, rq=%p, rp=%p, exp(r,w)=%u,%u rqf=%08x rpf=%08x rql=%d rpl=%d cs=%d ss=%d, cet=0x%x set=0x%x retr=%d\n", + now_ms, __FUNCTION__, __LINE__, + t, + s, s->flags, + s->req, s->rep, + s->req->rex, s->rep->wex, + s->req->flags, s->rep->flags, + s->req->l, s->rep->l, s->rep->cons->state, s->req->cons->state, + s->rep->cons->err_type, s->req->cons->err_type, + s->conn_retries); + + /* nothing special to be done on client side */ + if (unlikely(s->req->prod->state == SI_ST_DIS)) + s->req->prod->state = SI_ST_CLO; + + /* When a server-side connection is released, we have to count it and + * check for pending connections on this server. + */ + if (unlikely(s->req->cons->state == SI_ST_DIS)) { + s->req->cons->state = SI_ST_CLO; + if (s->srv) { + if (s->flags & SN_CURR_SESS) { + s->flags &= ~SN_CURR_SESS; + s->srv->cur_sess--; + } + sess_change_server(s, NULL); + if (may_dequeue_tasks(s->srv, s->be)) + process_srv_queue(s->srv); + } + } + + /* + * Note: of the transient states (REQ, CER, DIS), only REQ may remain + * at this point. + */ + + /**** Process layer 7 below ****/ + + resync = 0; + + /* Analyse request */ + if ((s->req->flags & BF_MASK_ANALYSER) || + (s->req->flags ^ rqf_last) & BF_MASK_STATIC) { + unsigned int flags = s->req->flags; + + if (s->req->prod->state >= SI_ST_EST) { + /* it's up to the analysers to reset write_ena */ + buffer_write_ena(s->req); + if (s->req->analysers) + process_request(s); + } + s->req->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; + flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; + if (s->req->flags != flags) + resync = 1; + } + + /* reflect what the L7 analysers have seen last */ + rqf_last = s->req->flags; + + /* + * Now forward all shutdown requests between both sides of the buffer + */ + + /* first, let's check if the request buffer needs to shutdown(write) */ + if (unlikely((s->req->flags & (BF_SHUTW|BF_SHUTW_NOW|BF_EMPTY|BF_HIJACK|BF_WRITE_ENA|BF_SHUTR)) == + (BF_EMPTY|BF_WRITE_ENA|BF_SHUTR))) + buffer_shutw_now(s->req); + else if ((s->req->flags & (BF_SHUTW|BF_SHUTW_NOW|BF_EMPTY|BF_WRITE_ENA)) == (BF_EMPTY|BF_WRITE_ENA) && + (s->req->cons->state == SI_ST_EST) && + s->be->options & PR_O_FORCE_CLO && + s->rep->flags & BF_READ_ACTIVITY) { + /* We want to force the connection to the server to close, + * and the server has begun to respond. That's the right + * time. + */ + buffer_shutw_now(s->req); + } + + /* shutdown(write) pending */ + if (unlikely((s->req->flags & (BF_SHUTW|BF_SHUTW_NOW)) == BF_SHUTW_NOW)) + s->req->cons->shutw(s->req->cons); + + /* shutdown(write) done on server side, we must stop the client too */ + if (unlikely((s->req->flags & (BF_SHUTW|BF_SHUTR|BF_SHUTR_NOW)) == BF_SHUTW)) + buffer_shutr_now(s->req); + + /* shutdown(read) pending */ + if (unlikely((s->req->flags & (BF_SHUTR|BF_SHUTR_NOW)) == BF_SHUTR_NOW)) + s->req->prod->shutr(s->req->prod); + + /* it's possible that an upper layer has requested a connection setup */ + if (s->req->cons->state == SI_ST_INI && + (s->req->flags & (BF_WRITE_ENA|BF_SHUTW|BF_SHUTW_NOW)) == BF_WRITE_ENA) + s->req->cons->state = SI_ST_REQ; + + /* we may have a pending connection request, or a connection waiting + * for completion. + */ + if (s->si[1].state >= SI_ST_REQ && s->si[1].state < SI_ST_CON) { + do { + /* nb: step 1 might switch from QUE to ASS, but we first want + * to give a chance to step 2 to perform a redirect if needed. + */ + if (s->si[1].state != SI_ST_REQ) + sess_update_stream_int(s, &s->si[1]); + if (s->si[1].state == SI_ST_REQ) + sess_prepare_conn_req(s, &s->si[1]); + + if (s->si[1].state == SI_ST_ASS && s->srv && + s->srv->rdr_len && (s->flags & SN_REDIRECTABLE)) + perform_http_redirect(s, &s->si[1]); + } while (s->si[1].state == SI_ST_ASS); + } + + /* + * Here we want to check if we need to resync or not. + */ + if ((s->req->flags ^ rqf_last) & BF_MASK_STATIC) + resync = 1; + + s->req->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; + + /* according to benchmarks, it makes sense to resync now */ + if (resync) + goto resync_stream_interface; + + + /* Analyse response */ + + if (unlikely(s->rep->flags & BF_HIJACK)) { + /* In inject mode, we wake up everytime something has + * happened on the write side of the buffer. + */ + unsigned int flags = s->rep->flags; + + if ((s->rep->flags & (BF_WRITE_PARTIAL|BF_WRITE_ERROR|BF_SHUTW)) && + !(s->rep->flags & BF_FULL)) { + produce_content(s); + } + s->rep->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; + flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; + if (s->rep->flags != flags) + resync = 1; + } + else if ((s->rep->flags & BF_MASK_ANALYSER) || + (s->rep->flags ^ rpf_last) & BF_MASK_STATIC) { + unsigned int flags = s->rep->flags; + + if (s->rep->prod->state >= SI_ST_EST) { + /* it's up to the analysers to reset write_ena */ + buffer_write_ena(s->rep); + if (s->rep->analysers) + process_response(s); + } + s->rep->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; + flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; + if (s->rep->flags != flags) + resync = 1; + } + + /* reflect what the L7 analysers have seen last */ + rpf_last = s->rep->flags; + + /* + * Now forward all shutdown requests between both sides of the buffer + */ + + /* + * FIXME: this is probably where we should produce error responses. + */ + + /* first, let's check if the request buffer needs to shutdown(write) */ + if (unlikely((s->rep->flags & (BF_SHUTW|BF_SHUTW_NOW|BF_EMPTY|BF_HIJACK|BF_WRITE_ENA|BF_SHUTR)) == + (BF_EMPTY|BF_WRITE_ENA|BF_SHUTR))) + buffer_shutw_now(s->rep); + + /* shutdown(write) pending */ + if (unlikely((s->rep->flags & (BF_SHUTW|BF_SHUTW_NOW)) == BF_SHUTW_NOW)) + s->rep->cons->shutw(s->rep->cons); + + /* shutdown(write) done on the client side, we must stop the server too */ + if (unlikely((s->rep->flags & (BF_SHUTW|BF_SHUTR|BF_SHUTR_NOW)) == BF_SHUTW)) + buffer_shutr_now(s->rep); + + /* shutdown(read) pending */ + if (unlikely((s->rep->flags & (BF_SHUTR|BF_SHUTR_NOW)) == BF_SHUTR_NOW)) + s->rep->prod->shutr(s->rep->prod); + + /* + * Here we want to check if we need to resync or not. + */ + if ((s->rep->flags ^ rpf_last) & BF_MASK_STATIC) + resync = 1; + + s->rep->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; + + if (resync) + goto resync_stream_interface; + + + /* This is needed only when debugging is enabled, to indicate + * client-side or server-side close. Please note that in the unlikely + * event where both sides would close at once, the sequence is reported + * on the server side first. + */ + if (unlikely((global.mode & MODE_DEBUG) && + (!(global.mode & MODE_QUIET) || + (global.mode & MODE_VERBOSE)))) { + int len; + + if (s->si[1].state == SI_ST_CLO && + s->si[1].prev_state == SI_ST_EST) { + len = sprintf(trash, "%08x:%s.srvcls[%04x:%04x]\n", + s->uniq_id, s->be->id, + (unsigned short)s->si[0].fd, + (unsigned short)s->si[1].fd); + write(1, trash, len); + } + + if (s->si[0].state == SI_ST_CLO && + s->si[0].prev_state == SI_ST_EST) { + len = sprintf(trash, "%08x:%s.clicls[%04x:%04x]\n", + s->uniq_id, s->be->id, + (unsigned short)s->si[0].fd, + (unsigned short)s->si[1].fd); + write(1, trash, len); + } + } + + if (likely((s->rep->cons->state != SI_ST_CLO) || + (s->req->cons->state > SI_ST_INI && s->req->cons->state < SI_ST_CLO))) { + + if ((s->fe->options & PR_O_CONTSTATS) && (s->flags & SN_BE_ASSIGNED)) + session_process_counters(s); + + if (s->rep->cons->state == SI_ST_EST) + stream_sock_data_finish(s->rep->cons->fd); + + if (s->req->cons->state == SI_ST_EST) + stream_sock_data_finish(s->req->cons->fd); + + s->req->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; + s->rep->flags &= BF_CLEAR_READ & BF_CLEAR_WRITE & BF_CLEAR_TIMEOUT; + s->si[0].prev_state = s->si[0].state; + s->si[1].prev_state = s->si[1].state; + s->si[0].flags = s->si[1].flags = SI_FL_NONE; + + /* Trick: if a request is being waiting for the server to respond, + * and if we know the server can timeout, we don't want the timeout + * to expire on the client side first, but we're still interested + * in passing data from the client to the server (eg: POST). Thus, + * we can cancel the client's request timeout if the server's + * request timeout is set and the server has not yet sent a response. + */ + + if ((s->rep->flags & (BF_WRITE_ENA|BF_SHUTR)) == 0 && + (tick_isset(s->req->wex) || tick_isset(s->rep->rex))) + s->req->rex = TICK_ETERNITY; + + t->expire = tick_first(tick_first(s->req->rex, s->req->wex), + tick_first(s->rep->rex, s->rep->wex)); + if (s->req->analysers) + t->expire = tick_first(t->expire, s->req->analyse_exp); + + if (s->si[0].exp) + t->expire = tick_first(t->expire, s->si[0].exp); + + if (s->si[1].exp) + t->expire = tick_first(t->expire, s->si[1].exp); + +#ifdef DEBUG_FULL + fprintf(stderr, "[%u] queuing with exp=%u req->rex=%u req->wex=%u req->ana_exp=%u rep->rex=%u rep->wex=%u, cs=%d, ss=%d\n", + now_ms, t->expire, s->req->rex, s->req->wex, s->req->analyse_exp, s->rep->rex, s->rep->wex, s->si[0].state, s->si[1].state); +#endif + /* restore t to its place in the task list */ + task_queue(t); + +#ifdef DEBUG_DEV + /* this may only happen when no timeout is set or in case of an FSM bug */ + if (!t->expire) + ABORT_NOW(); +#endif + *next = t->expire; + return; /* nothing more to do */ + } + + s->fe->feconn--; + if (s->flags & SN_BE_ASSIGNED) + s->be->beconn--; + actconn--; + + if (unlikely((global.mode & MODE_DEBUG) && + (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)))) { + int len; + len = sprintf(trash, "%08x:%s.closed[%04x:%04x] (term_trace=0x%08x)\n", + s->uniq_id, s->be->id, + (unsigned short)s->req->prod->fd, (unsigned short)s->req->cons->fd, + s->term_trace); + write(1, trash, len); + } + + s->logs.t_close = tv_ms_elapsed(&s->logs.tv_accept, &now); + session_process_counters(s); + + /* let's do a final log if we need it */ + if (s->logs.logwait && + !(s->flags & SN_MONITOR) && + (!(s->fe->options & PR_O_NULLNOLOG) || s->req->total)) { + if (s->fe->to_log & LW_REQ) + http_sess_log(s); + else + tcp_sess_log(s); + } + + /* the task MUST not be in the run queue anymore */ + task_delete(t); + session_free(s); + task_free(t); + *next = TICK_ETERNITY; +} + /* * This function adjusts sess->srv_conn and maintains the previous and new * server's served session counts. Setting newsrv to NULL is enough to release