From e4d4282026d6d2f3f002450a50bbbb395a9f0baf Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Tue, 11 Aug 2026 09:02:46 +0800 Subject: [PATCH] perf: parallelise compute-side batch NIO update per node The update_batch_nios handler looped serially across all entries (5010 for a 2505-link topology). On started nodes each entry does uBridge I/O, so the serial loop added orders of magnitude to the fan-out wall time. Group entries by node_id before dispatching. Different nodes talk to their own uBridge process (AF_UNIX socket) and are fully independent, so their updates run in parallel via asyncio.gather. Per-node entries are still serial (respecting the per-node uBridge command lock). --- gns3server/api/routes/compute/projects.py | 27 ++++++++++++++++------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/gns3server/api/routes/compute/projects.py b/gns3server/api/routes/compute/projects.py index 832892d5f..e09677f3e 100644 --- a/gns3server/api/routes/compute/projects.py +++ b/gns3server/api/routes/compute/projects.py @@ -22,6 +22,7 @@ import os import shutil import urllib.parse import inspect +import asyncio import logging @@ -281,15 +282,25 @@ async def update_batch_nios( and re-binds it. """ - updated = 0 + # Group entries by node so that different nodes' uBridge processes are + # updated in parallel (each node has its own AF_UNIX socket). Within a + # node entries are serial to respect the per-node uBridge command lock. + per_node = {} for entry in batch.nios: - node = project.get_node(entry.node_id) - nio = _get_existing_nio(node, entry.adapter_number, entry.port_number) - nio.filters = entry.nio.filters or {} - nio.markers = entry.nio.markers or {} - await _update_nio_binding(node, entry.adapter_number, entry.port_number, nio) - updated += 1 - return {"updated": updated} + per_node.setdefault(entry.node_id, []).append(entry) + + async def _update_one_node(node_id, entries): + node = project.get_node(node_id) + for e in entries: + nio = _get_existing_nio(node, e.adapter_number, e.port_number) + nio.filters = e.nio.filters or {} + nio.markers = e.nio.markers or {} + await _update_nio_binding(node, e.adapter_number, e.port_number, nio) + + await asyncio.gather( + *[_update_one_node(nid, ents) for nid, ents in per_node.items()] + ) + return {"updated": len(batch.nios)} @router.get("/projects/{project_id}/files", response_model=List[schemas.ProjectFile])