@@ -236,6 +236,14 @@ lflow_ls_stateful_handler(struct engine_node *node, void *data)
return EN_UNHANDLED;
}
+ /* Switch datapaths created/updated in this run had their ls_stateful
+ * flows already handled by lflow_handle_northd_ls_changes(); pass northd's
+ * tracked switches so we don't reprocess them here. */
+ struct northd_data *northd_data = engine_get_input_data("northd", node);
+ const struct tracked_dps *trk_switches =
+ northd_has_lswitches_in_tracked_data(&northd_data->trk_data)
+ ? &northd_data->trk_data.trk_switches : NULL;
+
const struct engine_context *eng_ctx = engine_get_context();
struct lflow_data *lflow_data = data;
struct lflow_input lflow_input;
@@ -243,6 +251,7 @@ lflow_ls_stateful_handler(struct engine_node *node, void *data)
lflow_get_input_data(node, &lflow_input);
if (!lflow_handle_ls_stateful_changes(eng_ctx->ovnsb_idl_txn,
&ls_sful_data->trk_data,
+ trk_switches,
&lflow_input,
lflow_data->lflow_table)) {
return EN_UNHANDLED;
@@ -22390,6 +22390,7 @@ exit:
bool
lflow_handle_ls_stateful_changes(struct ovsdb_idl_txn *ovnsb_txn,
struct ls_stateful_tracked_data *trk_data,
+ const struct tracked_dps *trk_switches,
struct lflow_input *lflow_input,
struct lflow_table *lflows)
{
@@ -22403,6 +22404,15 @@ lflow_handle_ls_stateful_changes(struct ovsdb_idl_txn *ovnsb_txn,
ovs_assert(od->nbs && uuid_equals(&od->nbs->header_.uuid,
&ls_stateful_rec->nbs_uuid));
+ /* Newly created/updated switch datapaths already had their
+ * ls_stateful flows built and synced by
+ * lflow_handle_northd_ls_changes() (which processes both the by_ls
+ * and ls_stateful refs together to keep shared datapath groups
+ * stable). Skip them here to avoid rebuilding the same flows. */
+ if (trk_switches && hmapx_contains(&trk_switches->crupdated, od)) {
+ continue;
+ }
+
lflow_ref_unlink_lflows(ls_stateful_rec->lflow_ref);
/* Generate new lflows. */
@@ -22420,6 +22430,17 @@ lflow_handle_ls_stateful_changes(struct ovsdb_idl_txn *ovnsb_txn,
* those datapath groups within those flows over and over again. */
HMAPX_FOR_EACH (hmapx_node, &trk_data->crupdated) {
struct ls_stateful_record *ls_stateful_rec = hmapx_node->data;
+
+ /* Already synced by lflow_handle_northd_ls_changes() (see above). */
+ if (trk_switches) {
+ const struct ovn_datapath *od =
+ ovn_datapaths_find_by_index(lflow_input->ls_datapaths,
+ ls_stateful_rec->ls_index);
+ if (hmapx_contains(&trk_switches->crupdated, od)) {
+ continue;
+ }
+ }
+
/* Sync the new flows to SB. */
bool handled = lflow_ref_sync_lflows(
ls_stateful_rec->lflow_ref, lflows, ovnsb_txn,
@@ -22432,6 +22453,10 @@ lflow_handle_ls_stateful_changes(struct ovsdb_idl_txn *ovnsb_txn,
}
}
+ /* No skip is needed for deleted records: an ls_stateful record is deleted
+ * only along with its switch datapath, so lflow_handle_northd_ls_changes()
+ * has already unlinked and synced its lflow_ref, and syncing destroys the
+ * unlinked ref nodes. The resync below therefore walks an empty ref. */
HMAPX_FOR_EACH (hmapx_node, &trk_data->deleted) {
struct ls_stateful_record *ls_stateful_rec = hmapx_node->data;
@@ -1026,6 +1026,7 @@ bool lflow_handle_lr_stateful_changes(struct ovsdb_idl_txn *,
struct lflow_table *lflows);
bool lflow_handle_ls_stateful_changes(struct ovsdb_idl_txn *,
struct ls_stateful_tracked_data *,
+ const struct tracked_dps *trk_switches,
struct lflow_input *,
struct lflow_table *lflows);
bool northd_handle_sb_port_binding_changes(
When a logical switch is created or updated, lflow_handle_northd_ls_changes() already builds and syncs that switch's ls_stateful flows together with its by_ls flows (so the shared datapath groups stay stable). The ls_stateful record for the same switch also shows up in the ls_stateful node's tracked "crupdated" set, so lflow_ls_stateful_handler() would rebuild and resync those exact flows a second time. Pass northd's tracked switches down to lflow_handle_ls_stateful_changes() and skip any crupdated ls_stateful record whose switch datapath was already handled by lflow_handle_northd_ls_changes(). Records for pre-existing switches (e.g. an ACL or port-group change) are untouched and still processed here. Deleted records need no such skip. An ls_stateful record is deleted only along with its switch datapath, so lflow_handle_northd_ls_changes() has already unlinked and synced its lflow_ref, and syncing destroys the ref nodes that were left unlinked. The resync done here therefore walks an already empty ref. Assisted-by: Claude Opus 4.8, Claude Code Signed-off-by: Lucas Vargas Dias <lucas.vdias@magalu.cloud> --- northd/en-lflow.c | 9 +++++++++ northd/northd.c | 25 +++++++++++++++++++++++++ northd/northd.h | 1 + 3 files changed, 35 insertions(+)