The _connect_nio thread-pool optimisation (send_batch_sync) targeted
node-start performance, but start_all already runs at concurrency=3
(by design, to avoid overwhelming the host). It also introduced a
Python 3.13 incompatibility (trsock.setblocking forbidden) that
prevented docker nodes from starting. Since node-start is not the
target of this branch (project-open link creation is), revert to the
simple per-command async _ubridge_send.
The project-open batch NIO dispatch (create_batch_nios) is unaffected —
it never called _connect_nio (nodes aren't started during open).
Python 3.13's asyncio TransportSocket wrapper rejects setblocking(). Dup
the underlying fd via socket.fromfd() into a plain socket that the
executor thread can drive in blocking mode, then detach() after the
batch to avoid closing the transport's fd.
Replace the default asyncio executor (capped at ~32 threads) with a
dedicated ThreadPoolExecutor sized for large-topology parallelism.
When 500 nodes each call _connect_nio, up to 500 OS threads can now
send blocking ubridge commands in parallel — no longer serialised by
either the event loop or a small thread pool.
- ubridge_hypervisor: module-level _ubridge_sync_pool (max_workers=500)
- docker_vm._connect_nio: dispatches to the dedicated pool instead of
the default executor
Replace the 3-5 sequential await _ubridge_send calls in _connect_nio
with a single run_in_executor batch. The batch holds the node-level
asyncio Lock to prevent interleaving with async sends, then uses the
hypervisor's new send_batch_sync method which does blocking socket
sendall/recv inside the thread pool. Different nodes' batches now
run in true OS-thread parallelism rather than serialising through
the asyncio event loop between every command.
- ubridge_hypervisor.send_batch_sync: blocking batch send using
the underlying socket from the asyncio transport, protected by
threading.Lock.
- _connect_nio: builds command list (add_nio_udp, start_capture,
bridge start, reset_packet_filters, add_packet_filter) and
dispatches to the default executor.
Drop the platform-specific floor (0.9.12 darwin / 0.9.14 others) in
favour of a single 1.2.0 minimum. This server now relies on features
only present in recent uBridge builds: the AF_UNIX control channel
(-U / SO_PEERCRED), the marker (mark) filter, and the brctl-backed
builtin Ethernet Switch. Removes the now-unused sys import left behind
by the dropped darwin branch (target is Linux-only).
Use the node id (UUID) instead of an incrementing counter for the unix
control socket name, so each socket identifies its owning node at a glance
(one ubridge per node => node_id is unique). Falls back to the counter only
when no node id is supplied. A single UUID fits sun_path's 107-byte cap
(~69 bytes), so no project_id is needed.
start() now checks whether ubridge exited immediately after launch. An
old ubridge build that does not understand -U exits with a non-zero code
right away; surface the reason from ubridge.log instead of waiting for
connect() to time out 10s later with a confusing "couldn't connect" error.
Add ubridge_control_transport to [Server] config (default "tcp", fully
backward compatible). Selecting "unix" switches the uBridge hypervisor
control channel from the unauthenticated TCP listener (-H) to an AF_UNIX
socket (-U) authenticated in-kernel via SO_PEERCRED.
- UBridgeHypervisor: supports both socket_path (AF_UNIX) and host/port
(TCP); a new `endpoint` property unifies log/error strings for both
- Hypervisor: unix mode allocates a short socket path under a 0700
private runtime dir and unlinks it on stop; tcp mode restores the
original getaddrinfo ephemeral-port allocation
- base_node: reads the transport from config and passes host through
- schemas/config.py + config_samples/gns3_server.conf: new option
Default deployments are unchanged. "unix" requires a ubridge build that
understands -U.