> From: "Ben Pfaff" <b...@ovn.org>
> To: "Lance Richardson" <lrich...@redhat.com>
> Cc: dev@openvswitch.org
> Sent: Saturday, July 23, 2016 1:49:36 AM
> Subject: Re: [ovs-dev] [PATCH v2] ovn-controller: eliminate stall in ofctrl 
> state machine
> 
> On Fri, Jul 22, 2016 at 11:04:47PM -0400, Lance Richardson wrote:
> > ----- Original Message -----
> > > From: "Ben Pfaff" <b...@ovn.org>
> > > To: "Lance Richardson" <lrich...@redhat.com>
> > > Cc: dev@openvswitch.org
> > > Sent: Friday, July 22, 2016 6:47:14 PM
> > > Subject: Re: [ovs-dev] [PATCH v2] ovn-controller: eliminate stall in
> > > ofctrl state machine
> > > 
> > > On Thu, Jul 07, 2016 at 08:31:08PM -0400, Lance Richardson wrote:
> > > > The "ovn -- 2 HVs, 3 LRs connected via LS, static routes"
> > > > test case currently exhibits frequent failures. These failures
> > > > occur because, at the time that the test packets are sent to
> > > > verify forwarding, no flows have been installed in the vswitch
> > > > for one of the hypervisors.
> > > > 
> > > > Investigation shows that, in the failing case, the ofctrl state
> > > > machine has not yet transitioned to the S_UPDATE_FLOWS state.
> > > > This occurrs when ofctrl_run() is called and:
> > > >    1) The state is S_TLV_TABLE_MOD_SENT.
> > > >    2) An OFPTYPE_NXT_TLV_TABLE_REPLY message is queued for reception.
> > > >    3) No event (other than SB probe timer expiration) is expected
> > > >       that would unblock poll_block() in the main ovn-controller
> > > >       loop.
> > > > 
> > > > In this scenario, ofctrl_run() will move state to S_CLEAR_FLOWS
> > > > and return, without having executed run_S_CLEAR_FLOWS() which
> > > > would have immediately transitioned the state to S_UPDATE_FLOWS
> > > > which is needed in order for ovn-controller to configure flows
> > > > in ovs-vswitchd. After a delay of about 5 seconds (the default
> > > > SB probe timer interval), ofctrl_run() would be called again
> > > > to make the transition to S_UPDATE_FLOWS, but by this time
> > > > the test case has already failed.
> > > > 
> > > > Fix by expanding the state machine's "while state != old_state"
> > > > loop to include processing of receive events, with a maximum
> > > > iteration limit to prevent excessive looping in pathological
> > > > cases. Without this fix, around 40 failures are seen out of
> > > > 100 attempts, with this fix no failures have been observed in
> > > > several hundred attempts.
> > > > 
> > > > Signed-off-by: Lance Richardson <lrich...@redhat.com>
> > > > ---
> > > >  v2: Added maximum iteration limit to state machine loop.
> > > 
> > > Thanks for investigating this.
> > > 
> > > I understand why your patch fixes a problem, but I don't yet understand
> > > the root cause of the problem, and I'd like to know more before I commit
> > > the patch.  I have two questions:
> > > 
> > > First, I don't understand why, if there's a message queued for reception
> > > as you describe in 2), the main loop would wait for a timer expiration.
> > > Before poll_block(), the main loop should call ofctrl_wait(), which
> > > calls rconn_recv_wait(), which should cause poll_block() to wake up if
> > > there's anything incoming from the OpenFlow connection.
> > > 
> > > Second, I don't know why the test "state == old_state" is here in
> > > ofctrl_run().  I think that we can delete it.  It could be a culprit,
> > > but on the other hand it seems like it's not a big deal if poll_block()
> > > properly wakes up when a message is received:
> > > 
> > >     for (int i = 0; state == old_state && i < 50; i++) {
> > >         struct ofpbuf *msg = rconn_recv(swconn);
> > >         if (!msg) {
> > >             break;
> > >         }
> > > 
> > > Thanks,
> > > 
> > > Ben.
> > > 
> > As currently implemented, when ofctrl_run() is called,
> > the run_*() functions will be called to transition through states that
> > don't depend on external events until the state no longer changes, then
> > any pending receive events are processed until the arbitrary limit of 50
> > events is processed, all receive events have been processed, or the state
> > changes (which does seem odd).
> > 
> > <This is the important bit:>
> > If the receive event handling happens to transition to a state for which
> > receive events are not expected, i.e. where the run_*() needs to be called
> > in order to transition to the next state, then the ofctrl_run() will return
> > and the transition will not be made until an event (timer expiration or
> > receive) occurs.
> > 
> > So the "message queued for reception" I referred to in the description is
> > the
> > one that transitions into S_CLEAR_FLOWS state.  ofctrl_run() returns after
> > making this transition, despite the fact there is no receive event expected
> > which would
> > cause ofctrl_run() to be called again. The state machine is stuck
> > in this state until the next event (timer expiration) occurs to unblock
> > the poll loop and cause ofctrl_run() to be called again.  When ofctrl_run()
> > is called, the run_S_CLEAR_FLOWS() function immediately transitions into
> > S_UPDATE_FLOWS state.
> > 
> > Another way to describe this: if a receive event causes a state transition,
> > the run_*() function for the new state needs to be called in order to
> > execute the actions for the new state (including possibly transitioning
> > into another state).  If this doesn't occur, the state machine stalls
> > until an event occurs to unblock the poll loop.
> > 
> > I can't say I'm particularly happy with the solution proposed in my patch,
> > there's probably a better way. If the above explanation still doesn't help,
> > let me know and I'll make another attempt when I'm a little less tired ;-)
> 
> Now that you explained it so clearly, it's obvious ;-)  Sorry I didn't
> catch on quickly.  Your patience is admirable.
> 
> Your version will definitely work.  However, in the furtherance of a
> long personal tradition of screwing with people's code, here's my
> version.
> 
> --8<--------------------------cut here-------------------------->8--
> 
> From: Lance Richardson <lrich...@redhat.com>
> Date: Thu, 7 Jul 2016 20:31:08 -0400
> Subject: [PATCH] ovn-controller: eliminate stall in ofctrl state machine
> 
> The "ovn -- 2 HVs, 3 LRs connected via LS, static routes"
> test case currently exhibits frequent failures. These failures
> occur because, at the time that the test packets are sent to
> verify forwarding, no flows have been installed in the vswitch
> for one of the hypervisors.
> 
> The state machine implemented by ofctrl_run() is intended to
> iterate as long as progress is being made, either as long as
> the state continues to change or as long as packets are being
> received.  Unfortunately, the code had a bug: if receiving a
> packet caused the state to change, it didn't call the state's
> run function again to try to see if it would change the state.
> This caused a real problem in the following case:
> 
>    1) The state is S_TLV_TABLE_MOD_SENT.
>    2) An OFPTYPE_NXT_TLV_TABLE_REPLY message is received.
>    3) No event (other than SB probe timer expiration) is expected
>       that would unblock poll_block() in the main ovn-controller
>       loop.
> 
> In such a case, ofctrl_run() would receive the packet and
> advance the state, but not call the run function for the new
> state, and then leave the state machine paused until the next
> event (e.g. a timer event) occurred.
> 
> This commit fixes the problem by continuing to iterate the state
> machine until the state remains the same and no packet is
> received in the same iteration.  Without this fix, around 40
> failures are seen out of 100 attempts, with this fix no failures
> have been observed in several hundred attempts (using an earlier
> version of this patch).
> 
> Signed-off-by: Lance Richardson <lrich...@redhat.com>
> Signed-off-by: Ben Pfaff <b...@ovn.org>
> ---
>  ovn/controller/ofctrl.c | 56
>  ++++++++++++++++++++++++++++---------------------
>  1 file changed, 32 insertions(+), 24 deletions(-)
> 
> diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c
> index 184e86f..f0451b7 100644
> --- a/ovn/controller/ofctrl.c
> +++ b/ovn/controller/ofctrl.c
> @@ -35,6 +35,7 @@
>  #include "openvswitch/vlog.h"
>  #include "ovn-controller.h"
>  #include "ovn/lib/actions.h"
> +#include "poll-loop.h"
>  #include "physical.h"
>  #include "rconn.h"
>  #include "socket-util.h"
> @@ -409,9 +410,10 @@ ofctrl_run(const struct ovsrec_bridge *br_int)
>          state = S_NEW;
>      }
>  
> -    enum ofctrl_state old_state;
> -    do {
> -        old_state = state;
> +    bool progress = true;
> +    for (int i = 0; progress && i < 50; i++) {
> +        /* Allow the state machine to run. */
> +        enum ofctrl_state old_state = state;
>          switch (state) {
>  #define STATE(NAME) case NAME: run_##NAME(); break;
>              STATES
> @@ -419,35 +421,41 @@ ofctrl_run(const struct ovsrec_bridge *br_int)
>          default:
>              OVS_NOT_REACHED();
>          }
> -    } while (state != old_state);
>  
> -    for (int i = 0; state == old_state && i < 50; i++) {
> +        /* Try to process a received packet. */
>          struct ofpbuf *msg = rconn_recv(swconn);
> -        if (!msg) {
> -            break;
> -        }
> -
> -        const struct ofp_header *oh = msg->data;
> -        enum ofptype type;
> -        enum ofperr error;
> +        if (msg) {
> +            const struct ofp_header *oh = msg->data;
> +            enum ofptype type;
> +            enum ofperr error;
>  
> -        error = ofptype_decode(&type, oh);
> -        if (!error) {
> -            switch (state) {
> +            error = ofptype_decode(&type, oh);
> +            if (!error) {
> +                switch (state) {
>  #define STATE(NAME) case NAME: recv_##NAME(oh, type); break;
> -                STATES
> +                    STATES
>  #undef STATE
> -            default:
> -                OVS_NOT_REACHED();
> +                default:
> +                    OVS_NOT_REACHED();
> +                }
> +            } else {
> +                char *s = ofp_to_string(oh, ntohs(oh->length), 1);
> +                VLOG_WARN("could not decode OpenFlow message (%s): %s",
> +                          ofperr_to_string(error), s);
> +                free(s);
>              }
> -        } else {
> -            char *s = ofp_to_string(oh, ntohs(oh->length), 1);
> -            VLOG_WARN("could not decode OpenFlow message (%s): %s",
> -                      ofperr_to_string(error), s);
> -            free(s);
> +
> +            ofpbuf_delete(msg);
>          }
>  
> -        ofpbuf_delete(msg);
> +        /* If we did some work, plan to go around again. */
> +        progress = old_state != state || msg;
> +    }
> +    if (progress) {
> +        /* We bailed out to limit the amount of work we do in one go, to
> allow
> +         * other code a chance to run.  We were still making progress at
> that
> +         * point, so ensure that we come back again without waiting. */
> +        poll_immediate_wake();
>      }
>  
>      return (state == S_CLEAR_FLOWS || state == S_UPDATE_FLOWS
> --

I think your version is better, it's cleaner and should be more robust. So:

Acked-by: Lance Richardson <lrich...@redhat.com>

Thanks,

   Lance


_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to