Async client¶
AsyncP4RuntimeClient (added in 1.6) is the async parallel to the
sync P4RuntimeClient shipped since 1.0. Same public method names,
every method async def, backed by grpc.aio instead of the sync
gRPC channel.
The two clients are independent and can coexist. The sync API at
switch.client remains the recommended starting point for new code;
the async API at switch.async_client is the right choice when you
need concurrency across switches, async PacketIn handlers, or want to
avoid the sync client's threading-related caveats.
AsyncP4RuntimeClient is Stable in p4net 1.x since version 1.7.0
(promoted from Provisional after empirical user soak — see the
API Stability page for the contract).
Why async¶
Three reasons to reach for the async API:
-
Concurrent operations across switches. Programming N switches' forwarding tables sequentially is O(N × RTT); using
asyncio.gathercollapses that to O(RTT) plus event-loop overhead. Theasync_concurrentexample shows the speedup pattern in practice. -
Natural async PacketIn handlers. Register an
async defcallback withclient.on_packet_in(...)and propagate values through the rest of your async pipeline. The sync client's handlers run on a background thread; the async client's run on your event loop. -
Bypasses the sync client's multi-threaded-fork pathology. The sync
P4RuntimeClientspawns gRPC background threads; combining that withsubprocess.runin the same Python process can deadlock — see Known Limitations.grpc.aiodoesn't spawn background threads, so the trap doesn't apply.
Quick start¶
import asyncio
from p4net import Network
from p4net.topo import Topology
topology = Topology()
# ... configure topology ...
async def main(net: Network) -> None:
sw = net.switch("s1")
client = sw.async_client
async with client:
await client.insert_table_entry(
"MyIngress.ipv4_lpm",
{"hdr.ipv4.dstAddr": "10.0.0.0/24"},
"MyIngress.set_egress_port",
{"port": 2},
)
async for entry in client.list_table_entries("MyIngress.ipv4_lpm"):
print(entry)
with Network(topology) as net:
asyncio.run(main(net))
API reference¶
See the generated docs for p4net.control.AsyncP4RuntimeClient:
constructor, properties, lifecycle, table CRUD, counters, registers,
packet I/O.
The API mirror is exact at the method-name level. Where the sync API
returns a list[dict] for streaming reads, the async API returns an
AsyncIterator[dict] — list_table_entries is the canonical
example. Where the sync API takes a Callable[[bytes, dict], None]
for on_packet_in, the async API takes a
Callable[[bytes, dict], Awaitable[None]].
Mastership and dual clients¶
Each P4Runtime client owns an election ID. The BMv2 device accepts
exactly one client as primary at any moment; others are
secondary. The sync and async clients are independent — they
each have their own election ID, even when they're hitting the same
switch.
Three usage patterns:
-
All sync (the v1.0 default). Build a
Network, useswitch.clientfor everything. The sync client's election ID is the millisecond-time-since-epoch ofNetwork.start(), so it's primary by default. -
All async. Use
switch.async_client.connect()for every switch. Passelection_id=(higher_value, 0)if the sync client is also connected somewhere; otherwise the async client takes primary on first arbitration. -
Mixed with explicit election IDs. Sync primary (writes); async secondary (reads only). Construct the async client with
election_id=(0, 0)to force it into secondary; reads still work, writes raiseNotPrimaryError.
Do not mix primary writes through both clients against the same switch. The BMv2 will accept whichever arrived most recently as primary and reject the other; in practice this produces confusing intermittent failures.
Cancellation and error handling¶
grpc.aio surfaces remote errors as grpc.aio.AioRpcError; the
async client translates these into the same exception hierarchy as
the sync client (DuplicateEntryError, EntryNotFoundError,
NotPrimaryError, PipelineError, P4RuntimeError).
When an in-flight operation is cancelled — typically because the
owning task was cancelled, or disconnect() was called concurrently
— the async client raises
AsyncOperationCancelledError,
a subclass of P4RuntimeError. This lets cancellation sites
distinguish a clean cancel from a connection failure:
try:
await asyncio.wait_for(client.insert_table_entry(...), timeout=1.0)
except asyncio.TimeoutError:
... # the wait_for timeout
except AsyncOperationCancelledError:
... # in-flight task was cancelled mid-RPC
except P4RuntimeError:
... # any other client failure
asyncio.CancelledError itself propagates through the client
unchanged for tasks the caller cancels at the task level (rather
than inside the RPC).
Stability¶
The async API is Stable in p4net 1.x since version 1.7.0. It was introduced as Provisional in 1.6.0 and promoted after real-world user soak surfaced no need for backwards-incompatible adjustments — see the API Stability page for the full contract and promotion rationale.