[MAJOR] process_session: rely only on buffer flags
Instead of calling all functions in a loop, process_session now
calls them according to buffer flags changes. This ensures that
we almost never call functions for nothing. The flags settings
are still quite coarse, but the number of average functions
calls per session has dropped from 31 to 18 (the calls to
process_srv dropped from 13 to 7 and the calls to process_cli
dropped from 13 to 8).
This could still be improved by memorizing which flags each
function uses, but that would add a level of complexity which
is not desirable and maybe even not worth the small gain.
diff --git a/src/proto_http.c b/src/proto_http.c
index 294ab37..808dcee 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -645,36 +645,88 @@
return ptr;
}
-/* Processes the client and server 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
- * the time the task accepts to wait, or TIME_ETERNITY for
- * infinity.
+/* 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 <next> 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. If one of the functions called returns non-zero, then it
+ * will be called once again after all other functions. This permits explicit
+ * external loops which may be useful for complex state machines.
*/
+#define PROCESS_CLI 0x1
+#define PROCESS_SRV 0x2
+#define PROCESS_REQ 0x4
+#define PROCESS_RTR 0x8
+#define PROCESS_ALL (PROCESS_CLI|PROCESS_SRV|PROCESS_REQ|PROCESS_RTR)
+
void process_session(struct task *t, int *next)
{
struct session *s = t->context;
- int fsm_resync = 0;
+ unsigned resync = PROCESS_ALL;
+ unsigned int rqf;
+ unsigned int rpf;
do {
- fsm_resync = 0;
+ if (resync & PROCESS_REQ) {
+ resync &= ~PROCESS_REQ;
+ if (s->analysis & AN_REQ_ANY) {
+ rqf = s->req->flags;
+ rpf = s->rep->flags;
+
+ if (process_request(s))
+ resync |= PROCESS_REQ;
+
+ if (!(s->analysis & AN_REQ_ANY) && !(s->req->flags & BF_MAY_FORWARD))
+ s->req->flags |= BF_MAY_FORWARD;
+
+ if (rqf != s->req->flags || rpf != s->rep->flags)
+ resync |= PROCESS_ALL & ~PROCESS_REQ;
+ }
+ }
+
+ if (resync & PROCESS_RTR) {
+ resync &= ~PROCESS_RTR;
+ if (s->analysis & AN_RTR_ANY) {
+ rqf = s->req->flags;
+ rpf = s->rep->flags;
- //fprintf(stderr,"before_cli:cli=%d, srv=%d, resync=%d\n", s->cli_state, s->srv_state, fsm_resync);
- fsm_resync |= process_cli(s);
+ if (process_response(s))
+ resync |= PROCESS_RTR;
- //fprintf(stderr,"before_req:cli=%d, srv=%d, resync=%d\n", s->cli_state, s->srv_state, fsm_resync);
- if (s->analysis & AN_REQ_ANY)
- fsm_resync |= process_request(s);
+ if (!(s->analysis & AN_RTR_ANY) && !(s->rep->flags & BF_MAY_FORWARD))
+ s->rep->flags |= BF_MAY_FORWARD;
+
+ if (rqf != s->req->flags || rpf != s->rep->flags)
+ resync |= PROCESS_ALL & ~PROCESS_RTR;
+ }
+ }
+
+ if (resync & PROCESS_CLI) {
+ rqf = s->req->flags;
+ rpf = s->rep->flags;
+
+ resync &= ~PROCESS_CLI;
+ if (process_cli(s))
+ resync |= PROCESS_CLI;
+
+ if (rqf != s->req->flags || rpf != s->rep->flags)
+ resync |= PROCESS_ALL & ~PROCESS_CLI;
+ }
- //fprintf(stderr,"before_srv:cli=%d, srv=%d, resync=%d\n", s->cli_state, s->srv_state, fsm_resync);
- fsm_resync |= process_srv(s);
+ if (resync & PROCESS_SRV) {
+ rqf = s->req->flags;
+ rpf = s->rep->flags;
- //fprintf(stderr,"before_rep:cli=%d, srv=%d, resync=%d\n", s->cli_state, s->srv_state, fsm_resync);
- if (s->analysis & AN_RTR_ANY)
- fsm_resync |= process_response(s);
+ resync &= ~PROCESS_SRV;
+ if (process_srv(s))
+ resync |= PROCESS_SRV;
- //fprintf(stderr,"endof_loop:cli=%d, srv=%d, resync=%d\n", s->cli_state, s->srv_state, fsm_resync);
- } while (fsm_resync);
+ if (rqf != s->req->flags || rpf != s->rep->flags)
+ resync |= PROCESS_ALL & ~PROCESS_SRV;
+ }
+ } while (resync);
if (likely(s->cli_state != CL_STCLOSE || s->srv_state != SV_STCLOSE)) {
@@ -1563,15 +1615,14 @@
}
/* This function performs all the processing enabled for the current request.
- * It returns 1 if it changes its state and it believes that another function
- * must be updated, otherwise zero. It might make sense to explode it into
- * several other functions.
+ * It normally returns zero, but may return 1 if it absolutely needs to be
+ * called again after other functions. It relies on buffers flags, and updates
+ * t->analysis. It might make sense to explode it into several other functions.
*/
int process_request(struct session *t)
{
struct buffer *req = t->req;
struct buffer *rep = t->rep;
- int fsm_resync = 0;
DPRINTF(stderr,"[%u] process_req: c=%s s=%s set(r,w)=%d,%d exp(r,w)=%u,%u req=%08x rep=%08x analysis=%02x\n",
now_ms,
@@ -1580,6 +1631,7 @@
t->cli_fd >= 0 && fdtab[t->cli_fd].state != FD_STCLOSE ? EV_FD_ISSET(t->cli_fd, DIR_WR) : 0,
req->rex, rep->wex, req->flags, rep->flags, t->analysis);
+ update_state:
if (t->analysis & AN_REQ_INSPECT) {
struct tcp_rule *rule;
int partial;
@@ -1597,7 +1649,7 @@
t->flags |= SN_ERR_CLICL;
if (!(t->flags & SN_FINST_MASK))
t->flags |= SN_FINST_R;
- return 1;
+ return 0;
}
/* Abort if client read timeout has expired */
@@ -1609,7 +1661,7 @@
t->flags |= SN_ERR_CLITO;
if (!(t->flags & SN_FINST_MASK))
t->flags |= SN_FINST_R;
- return 1;
+ return 0;
}
/* We don't know whether we have enough data, so must proceed
@@ -1637,8 +1689,7 @@
/* just set the request timeout once at the beginning of the request */
if (!tick_isset(t->inspect_exp))
t->inspect_exp = tick_add_ifset(now_ms, t->fe->tcp_req.inspect_delay);
-
- return fsm_resync;
+ return 0;
}
ret = acl_pass(ret);
@@ -1660,7 +1711,7 @@
if (!(t->flags & SN_FINST_MASK))
t->flags |= SN_FINST_R;
t->inspect_exp = TICK_ETERNITY;
- return 1;
+ return 0;
}
/* otherwise accept */
break;
@@ -1672,7 +1723,6 @@
*/
t->analysis &= ~AN_REQ_INSPECT;
t->inspect_exp = TICK_ETERNITY;
- fsm_resync = 1;
}
if (t->analysis & AN_REQ_HTTP_HDR) {
@@ -1762,7 +1812,7 @@
t->flags |= SN_ERR_CLICL;
if (!(t->flags & SN_FINST_MASK))
t->flags |= SN_FINST_R;
- return 1;
+ return 0;
}
/* 3: has the read timeout expired ? */
@@ -1777,7 +1827,7 @@
t->flags |= SN_ERR_CLITO;
if (!(t->flags & SN_FINST_MASK))
t->flags |= SN_FINST_R;
- return 1;
+ return 0;
}
/* 4: have we encountered a read error or did we have to shutdown ? */
@@ -1790,7 +1840,7 @@
t->flags |= SN_ERR_CLICL;
if (!(t->flags & SN_FINST_MASK))
t->flags |= SN_FINST_R;
- return 1;
+ return 0;
}
/* just set the request timeout once at the beginning of the request */
@@ -1798,7 +1848,7 @@
t->txn.exp = tick_add_ifset(now_ms, t->fe->timeout.httpreq);
/* we're not ready yet */
- return fsm_resync;
+ return 0;
}
@@ -2428,7 +2478,6 @@
}
/* OK let's go on with the BODY now */
- fsm_resync = 1;
goto end_of_headers;
return_bad_req: /* let's centralize all bad requests */
@@ -2442,7 +2491,7 @@
t->flags |= SN_ERR_PRXCOND;
if (!(t->flags & SN_FINST_MASK))
t->flags |= SN_FINST_R;
- return 1;
+ return 0;
end_of_headers:
; // to keep gcc happy
}
@@ -2506,17 +2555,19 @@
/* The situation will not evolve, so let's give up on the analysis. */
t->logs.tv_request = now; /* update the request timer to reflect full request */
t->analysis &= ~AN_REQ_HTTP_BODY;
- fsm_resync = 1;
}
}
- return fsm_resync;
+ /* we want to leave with that clean */
+ if (unlikely(t->analysis & AN_REQ_ANY))
+ goto update_state;
+ return 0;
}
/* This function performs all the processing enabled for the current response.
- * It returns 1 if it changes its state and it believes that another function
- * must be updated, otherwise zero. It might make sense to explode it into
- * several other functions.
+ * It normally returns zero, but may return 1 if it absolutely needs to be
+ * called again after other functions. It relies on buffers flags, and updates
+ * t->analysis. It might make sense to explode it into several other functions.
*/
int process_response(struct session *t)
{
@@ -2531,6 +2582,7 @@
t->srv_fd >= 0 && fdtab[t->srv_fd].state != FD_STCLOSE ? EV_FD_ISSET(t->srv_fd, DIR_WR) : 0,
req->rex, rep->wex, req->flags, rep->flags, t->analysis);
+ update_state:
if (t->analysis & AN_RTR_HTTP_HDR) { /* receiving server headers */
/*
* Now parse the partial (or complete) lines.
@@ -2614,7 +2666,7 @@
if (t->srv && may_dequeue_tasks(t->srv, t->be))
process_srv_queue(t->srv);
- return 1;
+ return 0;
}
/* write error to client, read error or close from server */
if (req->flags & BF_WRITE_ERROR ||
@@ -2640,7 +2692,7 @@
if (t->srv && may_dequeue_tasks(t->srv, t->be))
process_srv_queue(t->srv);
- return 1;
+ return 0;
}
/* too large response does not fit in buffer. */
else if (rep->flags & BF_FULL) {
@@ -2668,7 +2720,7 @@
if (t->srv && may_dequeue_tasks(t->srv, t->be))
process_srv_queue(t->srv);
- return 1;
+ return 0;
}
return 0;
}
@@ -2768,7 +2820,7 @@
*/
if (t->srv && may_dequeue_tasks(t->srv, cur_proxy))
process_srv_queue(t->srv);
- return 1;
+ return 0;
}
}
@@ -2974,16 +3026,20 @@
* otherwise we would not let the client side wake up.
*/
- return 1;
+ return 0;
}
+
+ /* we want to leave with that clean */
+ if (unlikely(t->analysis & AN_RTR_ANY))
+ goto update_state;
return 0;
}
/*
- * manages the client FSM and its socket. BTW, it also tries to handle the
- * cookie. It returns 1 if a state has changed (and a resync may be needed),
- * 0 else.
- * Note: process_srv is the ONLY function allowed to set cli_state to anything
+ * Manages the client FSM and its socket. It normally returns zero, but may
+ * return 1 if it absolutely wants to be called again.
+ *
+ * Note: process_cli is the ONLY function allowed to set cli_state to anything
* but CL_STCLOSE.
*/
int process_cli(struct session *t)
@@ -3000,10 +3056,7 @@
req->flags, rep->flags,
req->l, rep->l);
- /* if no analysis remains, it's time to forward the connection */
- if (!(t->analysis & AN_REQ_ANY) && !(req->flags & BF_MAY_FORWARD))
- req->flags |= BF_MAY_FORWARD;
-
+ update_state:
/* FIXME: we still have to check for CL_STSHUTR because client_retnclose
* still set this state (and will do until unix sockets are converted).
*/
@@ -3027,7 +3080,7 @@
else
t->flags |= SN_FINST_D;
}
- return 1;
+ goto update_state;
}
/* last read, or end of server write */
else if (!(req->flags & BF_SHUTR) && /* already done */
@@ -3042,7 +3095,7 @@
t->cli_state = CL_STCLOSE;
trace_term(t, TT_HTTP_CLI_3);
}
- return 1;
+ goto update_state;
}
/* last server read and buffer empty : we only check them when we're
* allowed to forward the data.
@@ -3064,7 +3117,7 @@
t->cli_state = CL_STCLOSE;
trace_term(t, TT_HTTP_CLI_5);
}
- return 1;
+ goto update_state;
}
/* read timeout */
else if (tick_is_expired(req->rex, now_ms)) {
@@ -3091,7 +3144,7 @@
else
t->flags |= SN_FINST_D;
}
- return 1;
+ goto update_state;
}
/* write timeout */
else if (tick_is_expired(rep->wex, now_ms)) {
@@ -3123,7 +3176,7 @@
else
t->flags |= SN_FINST_D;
}
- return 1;
+ goto update_state;
}
/* manage read timeout */
@@ -3152,7 +3205,7 @@
fd_delete(t->cli_fd);
t->cli_state = CL_STCLOSE;
trace_term(t, TT_HTTP_CLI_10);
- return 1;
+ goto update_state;
}
}
@@ -3186,19 +3239,18 @@
}
return 0;
}
-#ifdef DEBUG_FULL
- else {
- fprintf(stderr, "FIXME !!!! impossible state at %s:%d = %d\n", __FILE__, __LINE__, t->cli_state);
- exit(1);
- }
+#ifdef DEBUG_DEV
+ fprintf(stderr, "FIXME !!!! impossible state at %s:%d = %d\n", __FILE__, __LINE__, t->cli_state);
+ ABORT_NOW();
#endif
return 0;
}
/*
- * manages the server FSM and its socket. It returns 1 if a state has changed
- * (and a resync may be needed), 0 else.
+ * Manages the server FSM and its socket. It normally returns zero, but may
+ * return 1 if it absolutely wants to be called again.
+ *
* Note: process_srv is the ONLY function allowed to set srv_state to anything
* but SV_STCLOSE. The only exception is for functions called from this
* one (eg: those in backend.c).
@@ -3219,10 +3271,7 @@
req->flags, rep->flags,
req->l, rep->l);
- /* if no analysis remains, we have to let the data pass */
- if (!(t->analysis & AN_RTR_ANY) && !(rep->flags & BF_MAY_FORWARD))
- rep->flags |= BF_MAY_FORWARD;
-
+ update_state:
if (t->srv_state == SV_STIDLE) {
if ((rep->flags & BF_SHUTW) ||
((req->flags & BF_SHUTR) &&
@@ -3239,7 +3288,7 @@
srv_close_with_err(t, SN_ERR_CLICL, t->pend_pos ? SN_FINST_Q : SN_FINST_C, 0, NULL);
trace_term(t, TT_HTTP_SRV_1);
- return 1;
+ goto update_state;
}
else if (req->flags & BF_MAY_FORWARD) {
/* the client allows the server to connect */
@@ -3262,7 +3311,7 @@
srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_T,
500, error_message(t, HTTP_ERR_500));
trace_term(t, TT_HTTP_SRV_2);
- return 1;
+ goto update_state;
}
/* Right now, we will need to create a connection to the server.
@@ -3283,7 +3332,7 @@
if (t->srv)
t->srv->failed_conns++;
t->be->failed_conns++;
- return 1;
+ goto update_state;
}
}
@@ -3292,8 +3341,11 @@
if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)
t->flags |= SN_REDIRECTABLE;
- if (srv_redispatch_connect(t))
- return t->srv_state != SV_STIDLE;
+ if (srv_redispatch_connect(t)) {
+ if (t->srv_state == SV_STIDLE)
+ return 0;
+ goto update_state;
+ }
if ((t->flags & SN_REDIRECTABLE) && t->srv && t->srv->rdr_len) {
/* Server supporting redirection and it is possible.
@@ -3335,14 +3387,14 @@
/* FIXME: we should increase a counter of redirects per server and per backend. */
if (t->srv)
t->srv->cum_sess++;
- return 1;
+ goto update_state;
cancel_redir:
txn->status = 400;
t->fe->failed_req++;
srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C,
400, error_message(t, HTTP_ERR_400));
trace_term(t, TT_HTTP_SRV_4);
- return 1;
+ goto update_state;
}
/* try to (re-)connect to the server, and fail if we expire the
@@ -3350,10 +3402,13 @@
*/
if (srv_retryable_connect(t)) {
t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now);
- return t->srv_state != SV_STIDLE;
+ if (t->srv_state == SV_STIDLE)
+ return 0;
+ goto update_state;
}
} while (1);
- }
+ } /* end if may_forward */
+ return 0;
}
else if (t->srv_state == SV_STCONN) { /* connection in progress */
if ((rep->flags & BF_SHUTW) ||
@@ -3375,7 +3430,7 @@
*/
srv_close_with_err(t, SN_ERR_CLICL, SN_FINST_C, 0, NULL);
trace_term(t, TT_HTTP_SRV_5);
- return 1;
+ goto update_state;
}
if (!(req->flags & BF_WRITE_STATUS) && !tick_is_expired(req->cex, now_ms)) {
return 0; /* nothing changed */
@@ -3401,8 +3456,9 @@
conn_err = SN_ERR_SRVCL; // it was an asynchronous connect error.
/* ensure that we have enough retries left */
- if (srv_count_retry_down(t, conn_err))
- return 1;
+ if (srv_count_retry_down(t, conn_err)) {
+ goto update_state;
+ }
if (req->flags & BF_WRITE_ERROR) {
/* we encountered an immediate connection error, and we
@@ -3430,8 +3486,11 @@
t->prev_srv = t->srv;
/* first, get a connection */
- if (srv_redispatch_connect(t))
- return t->srv_state != SV_STCONN;
+ if (srv_redispatch_connect(t)) {
+ if (t->srv_state == SV_STCONN)
+ return 0;
+ goto update_state;
+ }
} else {
if (t->srv)
t->srv->retries++;
@@ -3446,12 +3505,17 @@
*/
if (srv_retryable_connect(t)) {
t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now);
- return t->srv_state != SV_STCONN;
+ if (t->srv_state == SV_STCONN)
+ return 0;
+ goto update_state;
}
/* we need to redispatch the connection to another server */
- if (srv_redispatch_connect(t))
- return t->srv_state != SV_STCONN;
+ if (srv_redispatch_connect(t)) {
+ if (t->srv_state == SV_STCONN)
+ return 0;
+ goto update_state;
+ }
} while (1);
}
else { /* no error or write 0 */
@@ -3473,7 +3537,6 @@
if (t->be->mode == PR_MODE_TCP) { /* let's allow immediate data connection in this case */
EV_FD_SET(t->srv_fd, DIR_RD);
rep->rex = tick_add_ifset(now_ms, t->be->timeout.server);
- t->srv_state = SV_STDATA;
buffer_set_rlim(rep, BUFSIZE); /* no rewrite needed */
/* if the user wants to log as soon as possible, without counting
@@ -3490,7 +3553,6 @@
#endif
}
else {
- t->srv_state = SV_STDATA;
t->analysis |= AN_RTR_HTTP_HDR;
buffer_set_rlim(rep, BUFSIZE - MAXREWRITE); /* rewrite needed */
t->txn.rsp.msg_state = HTTP_MSG_RPBEFORE;
@@ -3499,9 +3561,13 @@
* hdr_idx_init(&t->txn.hdr_idx);
*/
}
+
+ t->srv_state = SV_STDATA;
+ if (!(t->analysis & AN_RTR_ANY))
+ t->rep->flags |= BF_MAY_FORWARD;
req->cex = TICK_ETERNITY;
- return 1;
- }
+ goto update_state;
+ } /* else no error or write 0 */
}
else if (t->srv_state == SV_STDATA) {
/* read or write error */
@@ -3526,7 +3592,7 @@
if (may_dequeue_tasks(t->srv, t->be))
process_srv_queue(t->srv);
- return 1;
+ goto update_state;
}
/* last read, or end of client write */
else if (!(rep->flags & BF_SHUTR) && /* not already done */
@@ -3548,7 +3614,7 @@
if (may_dequeue_tasks(t->srv, t->be))
process_srv_queue(t->srv);
}
- return 1;
+ goto update_state;
}
/* end of client read and no more data to send. We can forward
* the close when we're allowed to forward data (anytime right
@@ -3581,7 +3647,7 @@
if (may_dequeue_tasks(t->srv, t->be))
process_srv_queue(t->srv);
}
- return 1;
+ goto update_state;
}
/* read timeout */
else if (tick_is_expired(rep->rex, now_ms)) {
@@ -3606,7 +3672,8 @@
t->flags |= SN_ERR_SRVTO;
if (!(t->flags & SN_FINST_MASK))
t->flags |= SN_FINST_D;
- return 1;
+
+ goto update_state;
}
/* write timeout */
else if (tick_is_expired(req->wex, now_ms)) {
@@ -3636,7 +3703,8 @@
t->flags |= SN_ERR_SRVTO;
if (!(t->flags & SN_FINST_MASK))
t->flags |= SN_FINST_D;
- return 1;
+
+ goto update_state;
}
/* manage read timeout */
@@ -3682,11 +3750,9 @@
}
return 0;
}
-#ifdef DEBUG_FULL
- else {
- fprintf(stderr, "FIXME !!!! impossible state at %s:%d = %d\n", __FILE__, __LINE__, t->srv_state);
- exit(1);
- }
+#ifdef DEBUG_DEV
+ fprintf(stderr, "FIXME !!!! impossible state at %s:%d = %d\n", __FILE__, __LINE__, t->srv_state);
+ ABORT_NOW();
#endif
return 0;
}