[PATCH net-next] net: openvswitch: don't schedule rebalancing if there are no datapaths

From: Ilya Maximets

Date: Wed Sep 02 2026 - 16:32:15 EST


During namespace initialization the masks rebalancing work is
scheduled and automatically re-scheduled every 4 seconds afterwards.
This is happening in every namespace. On a large kubernetes node with
500 pods, i.e., 500+ namespaces, this creates a decent amount of
unnecessary churn scheduling 500 jobs every 4 seconds that take the
mutex, check that there are no datapaths in their namespace, release
the mutex, re-schedule themselves and exit. These 500 unnecessary
mutex locks may hold off operations in a single namespace that
actually has a datapath configured and has real user requests to
handle under this lock. They can also add delay to removal of other
namespaces as ovs_exit_net() needs to take that lock as well and
synchronously waits for the work to be cancelled.

Let's only fire the job when the first datapath is actually created
and not re-arm it if there are no more datapaths configured in the
namespace.

Another approach would be to make ovs_mutex per-namespace, but it's
a much larger change that should be handled separately, and the
unnecessary work scheduling feels like a waste regardless.

It's safe to check and re-arm outside of the mutex as DP_CMD_NEW
handler will re-arm if the new datapath appears. The scheduling
attempt also doesn't change the work or delay if it is already queued,
so it's also safe to call multiple times.

Skipping the re-arming is more elegant than canceling on removal of
the last datapath as it allows us to not think about potential race
conditions at a negligible cost of potentially one extra re-scheduling.

msecs_to_jiffies() moved to the macro to save on line length.

Signed-off-by: Ilya Maximets <i.maximets@xxxxxxx>
---
net/openvswitch/datapath.c | 14 ++++++++++----
net/openvswitch/datapath.h | 2 +-
2 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c
index 631a03136fa14..2187034143255 100644
--- a/net/openvswitch/datapath.c
+++ b/net/openvswitch/datapath.c
@@ -1921,6 +1921,10 @@ static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)

ovs_unlock();

+ /* Start periodic mask rebalancing if it wasn't already. */
+ schedule_delayed_work(&ovs_net->masks_rebalance,
+ DP_MASKS_REBALANCE_INTERVAL);
+
ovs_notify(&dp_datapath_genl_family, reply, info);
return 0;

@@ -2598,16 +2602,20 @@ static void ovs_dp_masks_rebalance(struct work_struct *work)
struct ovs_net *ovs_net = container_of(work, struct ovs_net,
masks_rebalance.work);
struct datapath *dp;
+ bool rearm;

ovs_lock();

list_for_each_entry(dp, &ovs_net->dps, list_node)
ovs_flow_masks_rebalance(&dp->table);

+ rearm = !list_empty(&ovs_net->dps);
+
ovs_unlock();

- schedule_delayed_work(&ovs_net->masks_rebalance,
- msecs_to_jiffies(DP_MASKS_REBALANCE_INTERVAL));
+ if (rearm)
+ schedule_delayed_work(&ovs_net->masks_rebalance,
+ DP_MASKS_REBALANCE_INTERVAL);
}

static const struct nla_policy vport_policy[OVS_VPORT_ATTR_MAX + 1] = {
@@ -2713,8 +2721,6 @@ static int __net_init ovs_init_net(struct net *net)
if (err)
return err;

- schedule_delayed_work(&ovs_net->masks_rebalance,
- msecs_to_jiffies(DP_MASKS_REBALANCE_INTERVAL));
return 0;
}

diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h
index b2c2b8da12d40..7a7afaeceee5e 100644
--- a/net/openvswitch/datapath.h
+++ b/net/openvswitch/datapath.h
@@ -23,7 +23,7 @@

#define DP_MAX_PORTS USHRT_MAX
#define DP_VPORT_HASH_BUCKETS 1024
-#define DP_MASKS_REBALANCE_INTERVAL 4000
+#define DP_MASKS_REBALANCE_INTERVAL msecs_to_jiffies(4000)

/**
* struct dp_stats_percpu - per-cpu packet processing statistics for a given
--
2.55.0