Skip to main content

vpp_plugin/vlib/process_node/
mod.rs

1//! Process node module for async/await support in VPP plugins.
2//!
3//! This module provides the infrastructure for running async Rust futures
4//! within VPP's process node framework, enabling plugin authors to write
5//! async code that integrates with VPP's event loop.
6//!
7//! # Overview
8//!
9//! VPP process nodes are cooperative multi-tasking threads that run within the
10//! vlib framework. They are ideal for control-plane tasks that need to wait for
11//! events or timers without consuming CPU while idle. This module enables writing
12//! such process nodes using Rust's async/await syntax, combining the safety and
13//! expressiveness of modern Rust with VPP's event-driven architecture.
14//!
15//! ## Key Features
16//!
17//! - **Single async coroutine per process node**: Each process node runs exactly one async function (future)
18//! - **MPSC channels for events**: External code can send events to the future via multiple-producer single-consumer channels
19//! - **Timer integration**: Async sleep and timeout functions built on timer wheel
20//! - **VPP event loop integration**: Suspends using `vlib_process_wait_for_event_or_clock()`
21//!
22//! ## Basic Usage
23//!
24//! ```
25//! use vpp_plugin::{
26//!     vlib_process_node,
27//!     vlib::{ProcessNode, MainRef, process_node::{channel, Sender, Receiver}},
28//!     vlib::node::{NodeRuntimeRef, NextNodes, ErrorCounters},
29//!     ErrorCounters,
30//!     NextNodes,
31//! };
32//! use std::sync::{Mutex, LazyLock};
33//!
34//! #[derive(NextNodes)]
35//! enum MyProcessNextNode {
36//!     #[next_node = "error-drop"]
37//!     Drop,
38//! }
39//!
40//! #[derive(ErrorCounters)]
41//! enum MyProcessErrors {
42//!     #[error_counter(description = "Example error", severity = ERROR)]
43//!     Example,
44//! }
45//!
46//! #[derive(Debug)]
47//! enum MyProcessMessage {
48//!     Enable,
49//!     Disable,
50//! }
51//!
52//! // The mutex is in case the channel is created (for the sender) in another thread
53//! type GetOnceProcessMessageReceiver = Mutex<Option<Receiver<MyProcessMessage>>>;
54//!
55//! // Create a static instance of the process node
56//! static MY_PROCESS_NODE: MyProcessNode = MyProcessNode::new();
57//!
58//! // Register the process node with VPP
59//! #[vlib_process_node(
60//!     name = "my-process",
61//!     instance = MY_PROCESS_NODE,
62//! )]
63//! struct MyProcessNode {
64//!     channel: LazyLock<(Sender<MyProcessMessage>, GetOnceProcessMessageReceiver)>,
65//! }
66//!
67//! impl MyProcessNode {
68//!    const fn new() -> Self {
69//!         Self {
70//!             channel: LazyLock::new(|| {
71//!                 let (sender, receiver) = channel();
72//!                 (sender, Mutex::new(Some(receiver)))
73//!             }),
74//!         }
75//!     }
76//!
77//!     fn channel_sender(&self) -> Sender<MyProcessMessage> {
78//!         self.channel.0.clone()
79//!     }
80//!
81//!     fn channel_receiver(&self) -> Receiver<MyProcessMessage> {
82//!         self.channel.1.lock().unwrap().take().unwrap()
83//!     }
84//! }
85//!
86//! // Implement the ProcessNode trait with an async function
87//! impl ProcessNode for MyProcessNode {
88//!     type NextNodes = MyProcessNextNode;
89//!     type RuntimeData = ();
90//!     type Errors = MyProcessErrors;
91//!
92//!     async fn function(&self, vm: &mut MainRef, node: &mut NodeRuntimeRef<Self>) {
93//!         // Get the event channel receiver
94//!         let rx = self.channel_receiver();
95//!
96//!         loop {
97//!             // Wait for events from the channel
98//!             match rx.recv().await {
99//!                 Some(event) => {
100//!                     println!("Received event: {:?}", event);
101//!                 }
102//!                 None => break,
103//!             }
104//!         }
105//!     }
106//! }
107//! ```
108//!
109//! # Comparison with VPP C Process Nodes
110//!
111//! Writing process nodes in C requires manual management of the event loop:
112//!
113//! ## C Implementation Pattern
114//!
115//! ```c
116//! // Typical C process node structure
117//! while (1) {
118//!     // Explicitly wait for event or timeout
119//!     vlib_process_wait_for_event_or_clock(vm, timeout);
120//!
121//!     // Manually dispatch on event type
122//!     event_type = vlib_process_get_events(vm, &event_data);
123//!     switch (event_type) {
124//!         case EVENT1:
125//!             handle_event1(event_data);
126//!             vlib_process_suspend(vm, 0.1);
127//!             handle_event1_continued(event_data);
128//!             break;
129//!         case ~0:  // timeout
130//!             handle_periodic();
131//!             break;
132//!     }
133//!     vec_reset_length(event_data);
134//! }
135//! ```
136//!
137//! ## Key Differences
138//!
139//! The most obvious difference when using Rust async for process nodes is that the event loop is
140//! taken core of by infrastructure.
141//!
142//! Another difference is that a caller cannot be surprised by a callee suspending, since with
143//! Rust the only way to suspend inside an async function, and the caller must call .await for an
144//! async function to do anything.
145//!
146//! # Comparison with Other Rust Async Runtimes
147//!
148//! This runtime is designed for the unique constraints of VPP plugin development and
149//! differs significantly from popular async runtimes such as [tokio][tokio].
150//!
151//! The primary difference is that there is a single async coroutine per VPP process node and
152//! tasks cannot be spawned. However, multiple futures created and awaited simultaneously from
153//! within the single VPP process node future. If you have a number of futures all of the same
154//! type, you can use [`FuturesUnordered`][FuturesUnordered] to achieve this. If they are of
155//! different types you can use [`select()`][select()].
156//!
157//! ## Important Notes for Plugin Authors
158//!
159//! ### CPU-Bound Work
160//!
161//! In common with VPP C code and regular tasks in other Rust async runtimes, CPU-bound work will
162//! block both simultaneous events from being processed by the current task/process as well as
163//! potentially other tasks/processes from performing work. Therefore, it is recommended to break
164//! up large computations into chunks that yield periodically.
165//!
166//! ### Blocking I/O in Async Context
167//!
168//! In common with VPP C code and regular tasks in other Rust async runtimes, performing blocking
169//! operations (file I/O, syscalls, synchronous locking) may impact concurrent events for the
170//! current task/process as we as other tasks/processes. Generally, it's OK to use blocking I/O
171//! when it's expected to complete quickly (such as writing a small amount of data to a file
172//! locally). Similarly, it's OK to use synchronous locking if the contention is low.
173//!
174//! [tokio]: https://tokio.rs
175//! [FuturesUnordered]: https://docs.rs/futures-util/0.3.32/futures_util/stream/futures_unordered/struct.FuturesUnordered.html
176//! [select()]: https://docs.rs/futures-util/0.3.32/futures_util/future/fn.select.html
177
178pub mod core;
179pub mod mpsc;
180mod tw_timer;
181
182// Re-export these for convenience
183pub use core::{
184    LocalFutureObj, ProcessAsyncContext, ProcessNode, ProcessNodeRegistration, sleep, timeout,
185};
186pub use mpsc::{Receiver, Sender, channel};