]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Table: fix a race condition in export
authorMaria Matejka <mq@ucw.cz>
Fri, 23 May 2025 17:17:53 +0000 (19:17 +0200)
committerMaria Matejka <mq@ucw.cz>
Sun, 25 May 2025 19:02:51 +0000 (21:02 +0200)
The race condition happens as follows:

- channel A starts feeding
- channel B imports a route ahead of the feeding pointer
- channel A exports this route and continues feeding from the pointer
- no other import hits this specific prefix
- there is at least one channel C which has not cleared this export
- channel A computes ecnt=0 for this prefix because all exports
  have been already cleared
- the condition e >= ecnt mistakenly triggers retry

If the birdloops involved get assigned to the same thread, this race
condition then can't recover and the thread is stuck in an infinite
loop.

Fixed the race condition by moving the consistency check after actually
checking eligibility of the export, not before.

Found by randomly observing performance tests.

nest/rt-table.c

index fb3cce650313a04df9104f8013f68268319f6db8..8046bcc863528b2297aabd9c251786f72b59f630 100644 (file)
@@ -2508,10 +2508,11 @@ rt_net_feed_index(struct rtable_reading *tr, net *n, struct bmap *seen, bool (*p
       uint rpos = rcnt;
       for (const struct rt_pending_export *rpe = first; rpe;
          rpe = atomic_load_explicit(&rpe->next, memory_order_acquire))
-       if (e >= ecnt)
-         RT_READ_RETRY(tr);
-       else if (!seen || !bmap_test(seen, rpe->it.seq))
+       if (!seen || !bmap_test(seen, rpe->it.seq))
        {
+         if (e >= ecnt)
+           RT_READ_RETRY(tr);
+
          feed->exports[e++] = rpe->it.seq;
 
          /* Copy also obsolete routes */
@@ -2644,10 +2645,11 @@ rt_feed_net_best(struct rt_exporter *e, struct rcu_unwinder *u, u32 index, struc
     uint e = 0;
     for (const struct rt_pending_export *rpe = first; rpe;
        rpe = atomic_load_explicit(&rpe->next, memory_order_acquire))
-      if (e >= ecnt)
-       RT_READ_RETRY(tr);
-      else if (!seen || !bmap_test(seen, rpe->it.seq))
+      if (!seen || !bmap_test(seen, rpe->it.seq))
       {
+       if (e >= ecnt)
+         RT_READ_RETRY(tr);
+
        feed->exports[e++] = rpe->it.seq;
        if (rpe->it.old && (!best || (rpe->it.old != &best->rte)))
        {