On Mon, Jun 17, 2019 at 10:20:57PM +0200, Sebastian Benoit wrote:
> Claudio Jeker([email protected]) on 2019.06.17 21:34:30 +0200:
> > Hi,
> > 
> > Now that the community rewrite is in here another diff to make bgpd more
> > awesome. There was one loop left that did not run asynchronous compared to
> > the main event loop. This loop was in nexthop_update() now a route server
> > or a big route reflector would be hit hard by this loop since it will send
> > out many updates when a nexthop becomes valid. I have seen times where the
> > RDE was busy for 15min in nexthop_update().
> > 
> > This diff changes nexthop_update() to do this work asynchronous and
> > therefor returns to the main event loop much more often.
> > Nexthops now have a prefix pointer next_prefix which is used for looping
> > through all prefixes. The for loop is broken up in RDE_RUNNER_ROUNDS steps
> > which should hopefully return quickly.
> > 
> > Additionally prefixes belonging to a RIB that is not evaluated (no
> > decision process running) are no longer linked into this nexthop list
> > which should also shorten the loop a bit (at least Adj-RIB-In and
> > Adj-RIB-Out are now skipped). To make this work correctly the linking and
> > unlinking of prefixes has been adjusted to always work in the right way
> > (nexhop last on link and first on unlink).
> 
> reads ok, one comment.

Fixed.
 
> > When testing please watch out for this message:
> > prefix_updateall: prefix with F_RIB_NOEVALUATE hit
> > This should never happen.
> > 
> > I'm running this on a few routers with good success. Please check if you see
> > a) worse convergence time (updates propagating much slower)
> > b) better responsiveness (to e.g. bgpctl commands)
> 
> i see things much faster, actually too fast, i cant meaningdul test bgpctl
> responsiveness.

You just need around 500 peers which are affected by a big nexthop update :)
 
> > There is some potential for tuning but I would prefer to do this at a
> > later stage.
> 
> sure. one question about that: is the TAILQ_INSERT_TAIL/TAILQ_INSERT_HEAD
> optimal?

The idea is to make this like a ring. Start with the latest arrival
first and then process one after the other. This makes sure that every
entry gets about the same amount of process time. The TAILQ_INSERT_HEAD
in nexthop_update() will reduce initial latency for nexthops with only
few prefixes since they will be done in one nexthop_runner() call.

There is also additional interactions with the other runners
(imsg processing, rib_dump_runner and rde_update_queue_runner) which I
need to profile to understand them better. It looks like
rde_dispatch_imsg_session() is running ahead and produces a lot of work
that can't be absorbed that quickly by the other runners. At least that is
the case in the big route server setup that I'm working with.

> > -- 
> > :wq Claudio
> > 
> > Index: rde.c
> > ===================================================================
> > RCS file: /cvs/src/usr.sbin/bgpd/rde.c,v
> > retrieving revision 1.470
> > diff -u -p -r1.470 rde.c
> > --- rde.c   17 Jun 2019 13:35:43 -0000      1.470
> > +++ rde.c   17 Jun 2019 13:56:16 -0000
> > @@ -256,9 +256,6 @@ rde_main(int debug, int verbose)
> >             set_pollfd(&pfd[PFD_PIPE_SESSION], ibuf_se);
> >             set_pollfd(&pfd[PFD_PIPE_SESSION_CTL], ibuf_se_ctl);
> >  
> > -           if (rib_dump_pending() || rde_update_queue_pending())
> > -                   timeout = 0;
> > -
> >             i = PFD_PIPE_COUNT;
> >             for (mctx = LIST_FIRST(&rde_mrts); mctx != 0; mctx = xmctx) {
> >                     xmctx = LIST_NEXT(mctx, entry);
> > @@ -275,6 +272,10 @@ rde_main(int debug, int verbose)
> >                     }
> >             }
> >  
> > +           if (rib_dump_pending() || rde_update_queue_pending() ||
> > +               nexthop_pending())
> > +                   timeout = 0;
> > +
> >             if (poll(pfd, i, timeout) == -1) {
> >                     if (errno != EINTR)
> >                             fatal("poll error");
> > @@ -311,12 +312,13 @@ rde_main(int debug, int verbose)
> >                     mctx = LIST_NEXT(mctx, entry);
> >             }
> >  
> > +           rib_dump_runner();
> > +           nexthop_runner();
> >             if (ibuf_se && ibuf_se->w.queued < SESS_MSG_HIGH_MARK) {
> >                     rde_update_queue_runner();
> >                     for (aid = AID_INET6; aid < AID_MAX; aid++)
> >                             rde_update6_queue_runner(aid);
> >             }
> > -           rib_dump_runner();
> >     }
> >  
> >     /* do not clean up on shutdown on production, it takes ages. */
> > Index: rde.h
> > ===================================================================
> > RCS file: /cvs/src/usr.sbin/bgpd/rde.h,v
> > retrieving revision 1.214
> > diff -u -p -r1.214 rde.h
> > --- rde.h   17 Jun 2019 13:35:43 -0000      1.214
> > +++ rde.h   17 Jun 2019 13:56:16 -0000
> > @@ -228,12 +228,15 @@ struct rde_aspath {
> >  enum nexthop_state {
> >     NEXTHOP_LOOKUP,
> >     NEXTHOP_UNREACH,
> > -   NEXTHOP_REACH
> > +   NEXTHOP_REACH,
> > +   NEXTHOP_FLAPPED
> >  };
> >  
> >  struct nexthop {
> >     LIST_ENTRY(nexthop)     nexthop_l;
> > +   TAILQ_ENTRY(nexthop)    runner_l;
> >     struct prefix_list      prefix_h;
> > +   struct prefix           *next_prefix;
> >     struct bgpd_addr        exit_nexthop;
> >     struct bgpd_addr        true_nexthop;
> >     struct bgpd_addr        nexthop_net;
> > @@ -246,6 +249,7 @@ struct nexthop {
> >  #endif
> >     int                     refcnt;
> >     enum nexthop_state      state;
> > +   enum nexthop_state      oldstate;
> >     u_int8_t                nexthop_netlen;
> >     u_int8_t                flags;
> >  #define NEXTHOP_CONNECTED  0x01
> > @@ -593,6 +597,8 @@ prefix_vstate(struct prefix *p)
> >  
> >  void                nexthop_init(u_int32_t);
> >  void                nexthop_shutdown(void);
> > +int                 nexthop_pending(void);
> > +void                nexthop_runner(void);
> >  void                nexthop_modify(struct nexthop *, enum action_types, 
> > u_int8_t,
> >                 struct nexthop **, u_int8_t *);
> >  void                nexthop_link(struct prefix *);
> > Index: rde_rib.c
> > ===================================================================
> > RCS file: /cvs/src/usr.sbin/bgpd/rde_rib.c,v
> > retrieving revision 1.191
> > diff -u -p -r1.191 rde_rib.c
> > --- rde_rib.c       17 Jun 2019 11:02:20 -0000      1.191
> > +++ rde_rib.c       17 Jun 2019 19:10:07 -0000
> > @@ -517,13 +517,15 @@ path_empty(struct rde_aspath *asp)
> >     return (asp == NULL || asp->refcnt <= 0);
> >  }
> >  
> > -static inline void
> > +static inline struct rde_aspath *
> >  path_ref(struct rde_aspath *asp)
> >  {
> >     if ((asp->flags & F_ATTR_LINKED) == 0)
> >             fatalx("%s: unlinked object", __func__);
> >     asp->refcnt++;
> >     rdemem.path_refs++;
> > +
> > +   return asp;
> >  }
> >  
> >  static inline void
> > @@ -955,19 +957,16 @@ prefix_move(struct prefix *p, struct rde
> >             fatalx("prefix_move: cross peer move");
> >  
> >     np = prefix_alloc();
> > -   np->aspath = asp;
> > -   np->communities = comm;
> > -   np->nexthop = nexthop_ref(nexthop);
> > -   nexthop_link(np);
> > +   /* add reference to new AS path and communities */
> > +   np->aspath = path_ref(asp);
> > +   np->communities = communities_get(comm);
> >     np->peer = peer;
> >     np->re = p->re;
> > -   np->lastchange = time(NULL);
> > -   np->nhflags = nhflags;
> >     np->validation_state = vstate;
> > -
> > -   /* add reference to new AS path and communities */
> > -   path_ref(asp);
> > -   communities_get(comm);
> > +   np->nhflags = nhflags;
> > +   np->nexthop = nexthop_ref(nexthop);
> > +   nexthop_link(np);
> > +   np->lastchange = time(NULL);
> >  
> >     /*
> >      * no need to update the peer prefix count because we are only moving
> > @@ -988,18 +987,18 @@ prefix_move(struct prefix *p, struct rde
> >     /* remove old prefix node */
> >     /* as before peer count needs no update because of move */
> >     oasp = p->aspath;
> > -   if (oasp)
> > -           path_unref(oasp);
> >  
> >     /* destroy all references to other objects and free the old prefix */
> > -   p->aspath = NULL;
> > +   nexthop_unlink(p);
> > +   nexthop_put(p->nexthop);
> >     communities_put(p->communities);
> > +   if (oasp)
> > +           path_unref(oasp);
> >     p->communities = NULL;
> > +   p->nexthop = NULL;
> > +   p->aspath = NULL;
> >     p->peer = NULL;
> >     p->re = NULL;
> > -   nexthop_unlink(p);
> > -   nexthop_put(p->nexthop);
> > -   p->nexthop = NULL;
> >     prefix_free(p);
> >  
> >     /* destroy old path if empty */
> > @@ -1252,19 +1251,23 @@ prefix_updateall(struct prefix *p, enum 
> >      enum nexthop_state oldstate)
> >  {
> >     /* Skip non local-RIBs or RIBs that are flagged as noeval. */
> > -   if (re_rib(p->re)->flags & F_RIB_NOEVALUATE)
> > +   if (re_rib(p->re)->flags & F_RIB_NOEVALUATE) {
> > +           log_warnx("%s: prefix with F_RIB_NOEVALUATE hit", __func__);
> >             return;
> > +   }
> >  
> > -   if (oldstate == state && state == NEXTHOP_REACH) {
> > +   if (oldstate == state) {
> >             /*
> >              * The state of the nexthop did not change. The only
> >              * thing that may have changed is the true_nexthop
> >              * or other internal infos. This will not change
> >              * the routing decision so shortcut here.
> >              */
> > -           if ((re_rib(p->re)->flags & F_RIB_NOFIB) == 0 &&
> > -               p == p->re->active)
> > -                   rde_send_kroute(re_rib(p->re), p, NULL);
> > +           if (state == NEXTHOP_REACH) {
> > +                   if ((re_rib(p->re)->flags & F_RIB_NOFIB) == 0 &&
> > +                       p == p->re->active)
> > +                           rde_send_kroute(re_rib(p->re), p, NULL);
> > +           }
> >             return;
> >     }
> >  
> > @@ -1305,17 +1308,15 @@ prefix_link(struct prefix *p, struct rib
> >      struct rde_aspath *asp, struct rde_community *comm,
> >      struct nexthop *nexthop, u_int8_t nhflags, u_int8_t vstate)
> >  {
> > -   path_ref(asp);
> > -
> > -   p->aspath = asp;
> > -   p->peer = peer;
> > +   p->aspath = path_ref(asp);
> >     p->communities = communities_get(comm);
> > +   p->peer = peer;
> > +   p->re = re;
> > +   p->validation_state = vstate;
> > +   p->nhflags = nhflags;
> >     p->nexthop = nexthop_ref(nexthop);
> >     nexthop_link(p);
> > -   p->re = re;
> >     p->lastchange = time(NULL);
> > -   p->nhflags = nhflags;
> > -   p->validation_state = vstate;
> >  
> >     /* make route decision */
> >     prefix_evaluate(p, re);
> > @@ -1336,22 +1337,21 @@ prefix_unlink(struct prefix *p)
> >     LIST_REMOVE(p, rib_l);
> >     prefix_evaluate(NULL, re);
> >  
> > -   if (p->aspath)
> > -           path_unref(p->aspath);
> > -
> > -   if (rib_empty(re))
> > -           rib_remove(re);
> > -
> >     /* destroy all references to other objects */
> > -   communities_put(p->communities);
> >     nexthop_unlink(p);
> >     nexthop_put(p->nexthop);
> > +   communities_put(p->communities);
> > +   if (p->aspath)
> > +           path_unref(p->aspath);
> >     p->communities = NULL;
> >     p->nexthop = NULL;
> >     p->aspath = NULL;
> >     p->peer = NULL;
> >     p->re = NULL;
> >  
> > +   if (rib_empty(re))
> > +           rib_remove(re);
> > +
> >     /*
> >      * It's the caller's duty to do accounting and remove empty aspath
> >      * structures. Also freeing the unlinked prefix is the caller's duty.
> > @@ -1401,6 +1401,8 @@ struct nexthop_table {
> >  
> >  SIPHASH_KEY nexthoptablekey;
> >  
> > +TAILQ_HEAD(nexthop_queue, nexthop) nexthop_runners;
> > +
> >  void
> >  nexthop_init(u_int32_t hashsize)
> >  {
> > @@ -1412,6 +1414,7 @@ nexthop_init(u_int32_t hashsize)
> >     if (nexthoptable.nexthop_hashtbl == NULL)
> >             fatal("nextop_init");
> >  
> > +   TAILQ_INIT(&nexthop_runners);
> >     for (i = 0; i < hs; i++)
> >             LIST_INIT(&nexthoptable.nexthop_hashtbl[i]);
> >     arc4random_buf(&nexthoptablekey, sizeof(nexthoptablekey));
> > @@ -1443,12 +1446,45 @@ nexthop_shutdown(void)
> >     free(nexthoptable.nexthop_hashtbl);
> >  }
> >  
> > +int
> > +nexthop_pending(void)
> > +{
> > +   return !TAILQ_EMPTY(&nexthop_runners);
> > +}
> > +
> > +void
> > +nexthop_runner(void)
> > +{
> > +   struct nexthop *nh;
> > +   struct prefix *p;
> > +   u_int32_t j;
> > +
> > +   nh = TAILQ_FIRST(&nexthop_runners);
> > +   if (nh == NULL)
> > +           return;
> > +
> > +   /* remove from runnner queue */
> > +   TAILQ_REMOVE(&nexthop_runners, nh, runner_l);
> > +
> > +   p = nh->next_prefix;
> > +   for (j = 0; p != NULL && j < RDE_RUNNER_ROUNDS; j++) {
> > +           prefix_updateall(p, nh->state, nh->oldstate);
> > +           p = LIST_NEXT(p, nexthop_l);
> > +   }
> > +
> > +   /* prep for next run, if not finished readd to tail of queue */
> > +   nh->next_prefix = p;
> > +   if (p != NULL)
> > +           TAILQ_INSERT_TAIL(&nexthop_runners, nh, runner_l);
> > +   else
> > +           log_debug("nexthop %s update finished",
> > +               log_addr(&nh->exit_nexthop));
> > +}
> > +
> >  void
> >  nexthop_update(struct kroute_nexthop *msg)
> >  {
> >     struct nexthop          *nh;
> > -   struct prefix           *p;
> > -   enum nexthop_state       oldstate;
> >  
> >     nh = nexthop_lookup(&msg->nexthop);
> >     if (nh == NULL) {
> > @@ -1457,7 +1493,20 @@ nexthop_update(struct kroute_nexthop *ms
> >             return;
> >     }
> >  
> > -   oldstate = nh->state;
> > +   nh->oldstate = nh->state;
> > +   if (nh->oldstate == NEXTHOP_LOOKUP)
> > +           /* drop reference which was hold during the lookup */
> > +           if (nexthop_put(nh))
> > +                   return;         /* nh lost last ref, no work left */  
> 
> trailing whitespace
> 
> > +
> > +   if (nh->next_prefix) {
> > +           /*
> > +            * If nexthop_runner() is not finished with this nexthop
> > +            * then ensure that all prefixes are updated by setting
> > +            * the oldstate to NEXTHOP_FLAPPED.
> > +            */
> > +           nh->oldstate = NEXTHOP_FLAPPED;
> > +   }
> >  
> >     if (msg->valid)
> >             nh->state = NEXTHOP_REACH;
> > @@ -1476,21 +1525,16 @@ nexthop_update(struct kroute_nexthop *ms
> >         sizeof(nh->nexthop_net));
> >     nh->nexthop_netlen = msg->netlen;
> >  
> > -   if (oldstate == NEXTHOP_LOOKUP)
> > -           /* drop reference which was hold during the lookup */
> > -           if (nexthop_put(nh))
> > -                   return;
> > -
> >     if (rde_noevaluate())
> >             /*
> >              * if the decision process is turned off there is no need
> > -            * for the aspath list walk.
> > +            * for the prefix list walk.
> >              */
> >             return;
> >  
> > -   LIST_FOREACH(p, &nh->prefix_h, nexthop_l) {
> > -           prefix_updateall(p, nh->state, oldstate);
> > -   }
> > +   nh->next_prefix = LIST_FIRST(&nh->prefix_h);
> > +   TAILQ_INSERT_HEAD(&nexthop_runners, nh, runner_l);
> > +   log_debug("nexthop %s update starting", log_addr(&nh->exit_nexthop));
> >  }
> >  
> >  void
> > @@ -1532,6 +1576,10 @@ nexthop_link(struct prefix *p)
> >     if (p->nexthop == NULL)
> >             return;
> >  
> > +   /* no need to link prefixes in RIBs that have no decision process */
> > +   if (re_rib(p->re)->flags & F_RIB_NOEVALUATE)
> > +           return;
> > +
> >     LIST_INSERT_HEAD(&p->nexthop->prefix_h, p, nexthop_l);
> >  }
> >  
> > @@ -1541,6 +1589,12 @@ nexthop_unlink(struct prefix *p)
> >     if (p->nexthop == NULL)
> >             return;
> >  
> > +   if (re_rib(p->re)->flags & F_RIB_NOEVALUATE)
> > +           return;
> > +
> > +   if (p == p->nexthop->next_prefix)
> > +           p->nexthop->next_prefix = LIST_NEXT(p, nexthop_l);
> > +
> >     LIST_REMOVE(p, nexthop_l);
> >  }
> >  
> > @@ -1588,7 +1642,11 @@ nexthop_put(struct nexthop *nh)
> >  
> >     /* sanity check */
> >     if (!LIST_EMPTY(&nh->prefix_h) || nh->state == NEXTHOP_LOOKUP)
> > -           fatalx("nexthop_put: refcnt error");
> > +           fatalx("%s: refcnt error", __func__);
> > +
> > +   /* is nexthop update running? impossible, that is a refcnt error */
> > +   if (nh->next_prefix)
> > +           fatalx("%s: next_prefix not NULL", __func__);
> >  
> >     LIST_REMOVE(nh, nexthop_l);
> >     rde_send_nexthop(&nh->exit_nexthop, 0);
> > 
> 

-- 
:wq Claudio

Reply via email to