]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: filters: Fix channels synchronization in flt_end_analyze
authorChristopher Faulet <cfaulet@haproxy.com>
Fri, 10 Mar 2017 10:52:44 +0000 (11:52 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 15 Mar 2017 18:09:06 +0000 (19:09 +0100)
When a filter is used, there are 2 channel's analyzers to surround all the
others, flt_start_analyze and flt_end_analyze. This is the good place to acquire
and release resources used by filters, when needed. In addition, the last one is
used to synchronize the both channels, especially for HTTP streams. We must wait
that the analyze is finished for the both channels for an HTTP transaction
before restarting it for the next one.

But this part was buggy, leading to unexpected behaviours. First, depending on
which channel ends first, the request or the response can be switch in a
"forward forever" mode. Then, the HTTP transaction can be cleaned up too early,
while a processing is still in progress on a channel.

To fix the bug, the flag CF_FLT_ANALYZE has been added. It is set on channels in
flt_start_analyze and is kept if at least one filter is still analyzing the
channel. So, we can trigger the channel syncrhonization if this flag was removed
on the both channels. In addition, the flag TX_WAIT_CLEANUP has been added on
the transaction to know if the transaction must be cleaned up or not during
channels syncrhonization. This way, we are sure to reset everything once all the
processings are finished.

This patch should be backported in 1.7.

include/types/channel.h
include/types/proto_http.h
src/filters.c
src/proto_http.c

index 76f1ca0a453f42bc57091fa82c49d9c46995a2e4..03bb4e278889f597b18128a09322481aedcc721c 100644 (file)
 #define CF_NEVER_WAIT     0x08000000  /* never wait for sending data (permanent) */
 
 #define CF_WAKE_ONCE      0x10000000  /* pretend there is activity on this channel (one-shoot) */
-/* unused: 0x20000000, 0x40000000 */
+#define CF_FLT_ANALYZE    0x20000000  /* at least one filter is still analyzing this channel */
+/* unused: 0x40000000 */
 #define CF_ISRESP         0x80000000  /* 0 = request channel, 1 = response channel */
 
 /* Masks which define input events for stream analysers */
index 66f7397dc6c233a089c4b378903ee78e4c4cbe99..9987c33f75d3fdc411f37626e9d2a1785044eb62 100644 (file)
 #define TX_CACHE_COOK  0x00002000      /* a cookie in the response is cacheable */
 #define TX_CACHE_SHIFT 12              /* bit shift */
 
-/* Unused: 0x4000, 0x8000, 0x10000, 0x20000, 0x80000 */
+/* Unused: 0x4000, 0x8000 */
+
+#define TX_WAIT_CLEANUP        0x0010000       /* this transaction is waiting for a clean up */
+
+/* Unused: 0x20000, 0x80000 */
+
 
 /* indicate how we *want* the connection to behave, regardless of what is in
  * the headers. We have 4 possible values right now :
index cafc4492199f87df69dee94358dd64cd4fc6647e..24e1d533b23c299c0d25ca0e5202d8d446ba429a 100644 (file)
@@ -693,6 +693,9 @@ flt_start_analyze(struct stream *s, struct channel *chn, unsigned int an_bit)
        /* If this function is called, this means there is at least one filter,
         * so we do not need to check the filter list's emptiness. */
 
+       /* Set flag on channel to tell that the channel is filtered */
+       chn->flags |= CF_FLT_ANALYZE;
+
        RESUME_FILTER_LOOP(s, chn) {
                if (!(chn->flags & CF_ISRESP)) {
                        if (an_bit == AN_REQ_FLT_START_BE &&
@@ -819,6 +822,11 @@ flt_end_analyze(struct stream *s, struct channel *chn, unsigned int an_bit)
 {
        int ret = 1;
 
+       /* Check if all filters attached on the stream have finished their
+        * processing on this channel. */
+       if (!(chn->flags & CF_FLT_ANALYZE))
+               goto sync;
+
        RESUME_FILTER_LOOP(s, chn) {
                FLT_NXT(filter, chn) = 0;
                FLT_FWD(filter, chn) = 0;
@@ -831,27 +839,31 @@ flt_end_analyze(struct stream *s, struct channel *chn, unsigned int an_bit)
                }
        } RESUME_FILTER_END;
 
-end:
-       ret = handle_analyzer_result(s, chn, an_bit, ret);
-
-       /* Check if 'channel_end_analyze' callback has been called for the
-        * request and the response. */
-       if (!(s->req.analysers & AN_REQ_FLT_END) && !(s->res.analysers & AN_RES_FLT_END)) {
-               /* When we are waiting for a new request, so we must reset
-                * stream analyzers. The input must not be closed the request
-                * channel, else it is useless to wait. */
-               if (s->txn && (s->txn->flags & TX_WAIT_NEXT_RQ) && !channel_input_closed(&s->req)) {
-                       s->req.analysers = strm_li(s) ? strm_li(s)->analysers : 0;
-                       s->res.analysers = 0;
-
-                       /* Remove backend filters from the list */
-                       flt_stream_release(s, 1);
-               }
-
+ end:
+       /* We don't remove yet this analyzer because we need to synchronize the
+        * both channels. So here, we just remove the flag CF_FLT_ANALYZE. */
+       ret = handle_analyzer_result(s, chn, 0, ret);
+       if (ret)
+               chn->flags &= ~CF_FLT_ANALYZE;
+
+ sync:
+       /* Now we can check if filters have finished their work on the both
+        * channels */
+       if (!(s->req.flags & CF_FLT_ANALYZE) && !(s->res.flags & CF_FLT_ANALYZE)) {
+               /* Sync channels by removing this analyzer for the both channels */
+               s->req.analysers &= ~AN_REQ_FLT_END;
+               s->res.analysers &= ~AN_RES_FLT_END;
+
+               /* Clean up the HTTP transaction if needed */
+               if (s->txn && (s->txn->flags & TX_WAIT_CLEANUP))
+                       http_end_txn_clean_session(s);
+
+               /* Remove backend filters from the list */
+               flt_stream_release(s, 1);
        }
-       else if (ret) {
-               /* Analyzer ends only for one channel. So wake up the stream to
-                * be sure to process it for the other side as soon as
+       else {
+               /* This analyzer ends only for one channel. So wake up the
+                * stream to be sure to process it for the other side as soon as
                 * possible. */
                task_wakeup(s->task, TASK_WOKEN_MSG);
        }
index c0f57c5210455d8c9dc200326f04bbb2fb6770f5..6d55e6a8a781a064304863ef9a00c4cf69b17070 100644 (file)
@@ -5339,15 +5339,8 @@ void http_end_txn_clean_session(struct stream *s)
                else
                        si_idle_conn(&s->si[1], &srv->idle_conns);
        }
-
-       if (HAS_FILTERS(s)) {
-               s->req.analysers &= AN_REQ_FLT_END;
-               s->res.analysers &= AN_RES_FLT_END;
-       }
-       else {
-               s->req.analysers = strm_li(s) ? strm_li(s)->analysers : 0;
-               s->res.analysers = 0;
-       }
+       s->req.analysers = strm_li(s) ? strm_li(s)->analysers : 0;
+       s->res.analysers = 0;
 }
 
 
@@ -5703,8 +5696,12 @@ int http_resync_states(struct stream *s)
                        s->req.flags |= CF_WAKE_WRITE;
                else if (channel_congested(&s->res))
                        s->res.flags |= CF_WAKE_WRITE;
-               else
-                       http_end_txn_clean_session(s);
+               else {
+                       s->req.analysers = AN_REQ_FLT_END;
+                       s->res.analysers = AN_RES_FLT_END;
+                       txn->flags |= TX_WAIT_CLEANUP;
+                       return 1;
+               }
        }
 
        return txn->req.msg_state != old_req_state ||