From: luyun <luyun_611@163.com>
To: netdev-bot+sashiko@kernel.org
Cc: jv@jvosburgh.net, andrew+netdev@lunn.ch, davem@davemloft.net,
edumazet@google.com, kuba@kernel.org, pabeni@redhat.com,
horms@kernel.org, netdev@vger.kernel.org
Subject: Re: [PATCH net v2] bonding: 3ad: select a port when the TX array is empty
Date: Thu, 8 Oct 2026 10:52:34 +0800 [thread overview]
Message-ID: <0ef3f90a-9832-4392-87f2-58513e0f7e2f@163.com> (raw)
In-Reply-To: <179006864570.2160803.16949905511627036501@kernel.org>
Thanks for the review. The v3 has been posted:
https://lore.kernel.org/all/20261008024031.16224-1-luyun_611@163.com/
Main changes in v3:
- Drop the pending latch and all mode_lock usage in the transmit path.
The fallback is now lockless, so both the contention drops and the
recursive-TX concern are gone, and the latch handoff race no longer
exists.
- Eligibility uses the same criteria as bond_update_slave_arr() (port's
aggregator active + bond_slave_can_tx), with one aggregator object
pinned per transmit, so selection never spans two objects.
- broadcast_neighbor packets are replicated to all eligible members of
the pinned object instead of a single copy.
- The commit message now states the remaining scope: XDP transmit and
ndo_sk_get_lower_dev are left for follow-up changes.
---
Thanks,
Yun Lu
在 2026/9/22 17:17, netdev-bot+sashiko@kernel.org 写道:
> Thank you for your contribution! Sashiko AI review found 4 potential
> issue(s) to consider.
>
> Critical: 0 · High: 0 · Medium: 2 · Low: 2
>
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
>
> - [Medium] Incomplete fix: the new empty-array fallback is wired only
> into bond_3ad_xor_xmit(), bond_xmit_broadcast() (broadcast-neighbor…
> - [Medium] bond_3ad_xmit_fallback() abandons selection (returns NULL ->
> bond_tx_drop() in bond_3ad_xor_xmit(), or a NULL ndo_get_xmit_slave…
> - [Low] Documented behaviour vs implementation mismatch for
> broadcast_neighbor.
> - [Low] Residual drop window from read/write order asymmetry between the
> array publication and the latch clear.
>
> --
>
> bonding: 3ad: select a port when the TX array is empty
>
> This adds a boolean latch (bond->slave_arr_update_pending) set by the 802.3ad
> state machine and cleared by the slave array worker, and uses it to let the
> transmit paths pick a port straight from the active aggregator while the
> usable_slaves array update is still unpublished. The fallback is wired into
> bond_3ad_xor_xmit(), the broadcast-neighbor case of bond_xmit_broadcast() and
> bond_xmit_get_slave().
>
>> diff --git a/drivers/net/bonding/bond_3ad.c b/drivers/net/bonding/bond_3ad.c
>> index b8e4b4d68dd6..1fe499ade439 100644
>> --- a/drivers/net/bonding/bond_3ad.c
>> +++ b/drivers/net/bonding/bond_3ad.c
>> @@ -2614,6 +2614,8 @@ void bond_3ad_state_machine_handler(struct work_struct *work)
>> break;
>> }
>> }
>> + if (update_slave_arr)
>> + WRITE_ONCE(bond->slave_arr_update_pending, true);
>> rcu_read_unlock();
>> spin_unlock_bh(&bond->mode_lock);
>>
>> diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c
>> index a9bff7663eec..1144281912d3 100644
>> --- a/drivers/net/bonding/bond_main.c
>> +++ b/drivers/net/bonding/bond_main.c
> [ ... ]
>
>> @@ -5093,6 +5094,8 @@ static void bond_slave_arr_handler(struct work_struct *work)
>> pr_warn_ratelimited("Failed to update slave array from WT\n");
>> goto err;
>> }
>> + /* bond->wq is ordered, so no newer request can race this clear. */
>> + WRITE_ONCE(bond->slave_arr_update_pending, false);
>> return;
>>
>> err:
> [Severity: Low]
> Can a packet still be dropped in the window this patch closes? The worker
> publishes the new array first and clears the latch second, and the transmit
> side reads those two pieces of state in the same order:
>
> CPU0 bond_slave_arr_handler()
> bond_update_slave_arr(bond, NULL); /* publishes non-empty array */
> WRITE_ONCE(bond->slave_arr_update_pending, false);
>
> CPU1 bond_3ad_xor_xmit()
> slaves = rcu_dereference(bond->usable_slaves); /* old, count == 0 */
> bond_xmit_hash(bond, skb); /* full dissection */
> bond_3ad_xmit_fallback()
> if (!READ_ONCE(bond->slave_arr_update_pending))
> return NULL; /* latch now clear */
> return bond_tx_drop(dev, skb);
>
> The gap between the two reads on CPU1 spans a full bond_xmit_hash() call, so
> CPU1 can observe the stale empty array and then the already cleared latch.
>
> The re-check of the latch under mode_lock inside bond_3ad_xmit_fallback()
> does not cover this, since the clearing side (bond_slave_arr_handler()) never
> takes mode_lock; only the setting side in bond_3ad_state_machine_handler()
> does.
>
> Would re-reading bond->usable_slaves after observing a cleared latch, or
> reading the latch before sampling the array and re-validating, close this
> remainder?
>
>> @@ -5289,9 +5292,67 @@ static bool bond_should_broadcast_neighbor(struct sk_buff *skb,
>> return false;
>> }
>>
>> -/* Use this Xmit function for 3AD as well as XOR modes. The current
>> - * usable slave array is formed in the control path. The xmit function
>> - * just calculates hash and sends the packet out.
>> +/* Called with RCU and bond->mode_lock held. */
>> +static bool bond_3ad_slave_is_eligible(struct slave *slave)
>> +{
>> + const struct aggregator *agg;
>> +
>> + agg = rcu_dereference(SLAVE_AD_INFO(slave)->port.aggregator);
>> + return agg && agg->is_active && bond_slave_can_tx(slave);
>> +}
>> +
>> +/* Called with RCU held. */
>> +static struct slave *bond_3ad_xmit_fallback(struct bonding *bond, u32 hash)
>> +{
>> + struct slave *selected = NULL;
>> + unsigned int eligible = 0;
>> + unsigned int target;
>> + struct list_head *iter;
>> + struct slave *slave;
>> +
>> + /* Limit the list walk to the array-update window. */
>> + if (!netif_carrier_ok(bond->dev) ||
>> + !READ_ONCE(bond->slave_arr_update_pending))
>> + return NULL;
>> +
>> + /* TX may recurse while mode_lock is already held. */
>> + if (unlikely(netpoll_tx_running(bond->dev)) ||
>> + !spin_trylock_bh(&bond->mode_lock))
>> + return NULL;
> [Severity: Medium]
> The trylock is needed for the recursive TX case, but does it also turn plain
> remote-CPU contention into a packet drop during exactly the convergence
> period the patch targets?
>
> mode_lock is held for the whole body of bond_3ad_state_machine_handler(),
> walking every slave and running ad_rx_machine(), ad_periodic_machine(),
> ad_port_selection_logic(), ad_mux_machine(), ad_tx_machine() and
> ad_churn_machine(), with the latch published only at the very end:
>
> spin_lock_bh(&bond->mode_lock);
> ...
> if (update_slave_arr)
> WRITE_ONCE(bond->slave_arr_update_pending, true);
> rcu_read_unlock();
> spin_unlock_bh(&bond->mode_lock);
>
> It is also taken by the LACPDU receive path in bond_3ad_rx_indication():
>
> /* Protect against concurrent state machines */
> spin_lock(&slave->bond->mode_lock);
> ad_rx_machine(lacpdu, port);
> spin_unlock(&slave->bond->mode_lock);
>
> and by bond_3ad_handle_link_change(). Since bond_setup() sets
> bond_dev->lltx = true, transmitters are not serialized by the device TX lock,
> so a transmit on another CPU landing in any of those sections (or racing
> another CPU already inside the fallback's own two list walks) fails the
> trylock and bond_3ad_xor_xmit() drops the skb even though the latch is set
> and eligible ports exist.
>
> Would having the state machine publish a fallback slave via RCU, so the TX
> fast path never needs the LACP state-machine spinlock, avoid both the
> recursion problem and the contention drops? As written, the new code also
> puts a mode_lock acquisition plus a full lower-device list walk on the
> per-packet TX path for the duration of the window.
>
>> +
>> + if (!READ_ONCE(bond->slave_arr_update_pending))
>> + goto out;
>> +
>> + bond_for_each_slave_rcu(bond, slave, iter)
>> + if (bond_3ad_slave_is_eligible(slave))
>> + eligible++;
>> +
>> + if (!eligible)
>> + goto out;
>> +
>> + target = hash % eligible;
>> + bond_for_each_slave_rcu(bond, slave, iter) {
>> + if (!bond_3ad_slave_is_eligible(slave))
>> + continue;
>> +
>> + if (!selected)
>> + selected = slave;
>> +
>> + if (!target) {
>> + selected = slave;
>> + break;
>> + }
>> + target--;
>> + }
>> +
>> +out:
>> + spin_unlock_bh(&bond->mode_lock);
>> + return selected;
>> +}
>> +
>> +/* Use this Xmit function for 3AD as well as XOR modes. The usable slave
>> + * array is formed in the control path. In 3AD mode, fall back to the current
>> + * port state while an empty array update is pending.
>> */
>> static netdev_tx_t bond_3ad_xor_xmit(struct sk_buff *skb,
>> struct net_device *dev)
>> @@ -5302,6 +5363,8 @@ static netdev_tx_t bond_3ad_xor_xmit(struct sk_buff *skb,
>>
>> slaves = rcu_dereference(bond->usable_slaves);
>> slave = bond_xmit_3ad_xor_slave_get(bond, skb, slaves);
>> + if (unlikely(!slave) && BOND_MODE(bond) == BOND_MODE_8023AD)
>> + slave = bond_3ad_xmit_fallback(bond, bond_xmit_hash(bond, skb));
>> if (likely(slave))
>> return bond_dev_queue_xmit(bond, skb, slave->dev);
>>
>> @@ -5327,6 +5390,16 @@ static netdev_tx_t bond_xmit_broadcast(struct sk_buff *skb,
>> slaves = rcu_dereference(bond->usable_slaves);
>>
>> slaves_count = slaves ? READ_ONCE(slaves->count) : 0;
>> + if (!slaves_count && !all_slaves &&
>> + BOND_MODE(bond) == BOND_MODE_8023AD) {
>> + struct slave *slave;
>> +
>> + slave = bond_3ad_xmit_fallback(bond,
>> + bond_xmit_hash(bond, skb));
>> + if (slave)
>> + return bond_dev_queue_xmit(bond, skb, slave->dev);
>> + }
>> +
> [Severity: Low]
> Does this branch match the documented meaning of broadcast_neighbor?
>
> Documentation/networking/bonding.rst describes the option as:
>
> Option specifying whether to broadcast ARP/ND packets to all
> active slaves. This option has no effect in modes other than
> 802.3ad mode. The default is off (0).
>
> and the descriptor for BOND_OPT_BROADCAST_NEIGH in bond_options.c says
> "Broadcast neighbor packets to all active slaves".
>
> bond_3ad_xmit_fallback() is a hash-selecting single-port helper
> (target = hash % eligible), so this sends one copy on one port and returns
> before the replication loop. Since the port choice is
> bond_xmit_hash(bond, skb) % eligible, the same neighbour deterministically
> leaves through the same port and the other eligible members of the active
> aggregator see no ARP/ND for the whole update window.
>
> bond_3ad_xmit_fallback() already enumerates the full eligible set under
> mode_lock. Could the broadcast caller replicate to every eligible port
> instead, to match the array path it mirrors? Otherwise, should the
> documentation describe the degraded behaviour during the window?
>
>> for (i = 0; i < slaves_count; i++) {
>> struct slave *slave = slaves->arr[i];
>> struct sk_buff *skb2;
>> @@ -5432,6 +5505,10 @@ static struct net_device *bond_xmit_get_slave(struct net_device *master_dev,
>> else
>> slaves = rcu_dereference(bond->usable_slaves);
>> slave = bond_xmit_3ad_xor_slave_get(bond, skb, slaves);
>> + if (!slave && !all_slaves &&
>> + BOND_MODE(bond) == BOND_MODE_8023AD)
>> + slave = bond_3ad_xmit_fallback(bond,
>> + bond_xmit_hash(bond, skb));
>> break;
> [Severity: Medium]
> Should the XDP transmit selector get the same treatment?
> bond_xdp_xmit_3ad_xor_slave_get() reads the same bond->usable_slaves array
> and still has the unconditional empty-array return:
>
> count = slaves ? READ_ONCE(slaves->count) : 0;
> if (unlikely(!count))
> return NULL;
>
> so during the same window bond_xdp_get_xmit_slave() returns NULL for
> BOND_MODE_8023AD and bond_xdp_xmit() aborts the batch:
>
> slave_dev = bond_xdp_get_xmit_slave(bond_dev, &xdp);
> if (!slave_dev) {
> err = -ENXIO;
> break;
> }
>
> __bond_sk_get_lower_dev() has the same count == 0 NULL return for the
> TLS/socket lower-device lookup, so two consumers of the same emptiness
> condition now answer differently.
>
> The commit message says:
>
> If an skb transmit or ndo_get_xmit_slave query finds an empty usable array
> while the latch is set, select a port directly from the active aggregator.
>
> but it does not state that the XDP transmit path keeps dropping frames in
> that window. The only mention, "leave XDP for a separate follow-up patch",
> is in the v2 changelog below the --- separator, which git does not record.
> Could that limitation be stated in the commit message itself, given the
> Fixes: tag makes this a backport candidate?
>
> [ ... ]
>
prev parent reply other threads:[~2026-10-08 2:53 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-18 9:01 [PATCH net v2] bonding: 3ad: select a port when the TX array is empty Yun Lu
2026-09-22 9:17 ` netdev-bot+sashiko
2026-10-08 2:52 ` luyun [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=0ef3f90a-9832-4392-87f2-58513e0f7e2f@163.com \
--to=luyun_611@163.com \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=jv@jvosburgh.net \
--cc=kuba@kernel.org \
--cc=netdev-bot+sashiko@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox