Skip to content

Commit 1644240

Browse files
author
Jonathan Woollett-Light
committed
feat: Procedural overhaul
Signed-off-by: Jonathan Woollett-Light <[email protected]>
1 parent b81f257 commit 1644240

17 files changed

+203
-2457
lines changed

Cargo.toml

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,4 @@ edition = "2021"
1111

1212
[dependencies]
1313
vmm-sys-util = "0.11.0"
14-
libc = "0.2.39"
15-
16-
[dev-dependencies]
17-
criterion = "0.3.5"
18-
19-
[features]
20-
remote_endpoint = []
21-
test_utilities = []
22-
23-
[[bench]]
24-
name = "main"
25-
harness = false
26-
27-
[lib]
28-
bench = false # https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options
29-
30-
[profile.bench]
31-
lto = true
32-
codegen-units = 1
14+
libc = "0.2.39"

docs/DESIGN.md

Lines changed: 7 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,147 +1,13 @@
11
# Event Manager Design
22

3-
## Interest List Updates
4-
5-
Subscribers can update their interest list when the `EventManager` calls
6-
their `process` function. The EventManager crates a specialized `EventOps`
7-
object. `EventOps` limits the operations that the subscribers may call to the
8-
ones that are related to the interest list as follows:
9-
- Adding a new event that the subscriber is interested in.
10-
- Modifying an existing event (for example: update an event to be
11-
edge-triggered instead of being level-triggered or update the user data
12-
associated with an event).
13-
- Remove an existing event.
14-
15-
The subscriber is responsible for handling the errors returned from calling
16-
`add`, `modify` or `remove`.
17-
18-
The `EventManager` knows how to associate these actions to a registered
19-
subscriber because it adds the corresponding `SubscriberId` when it creates the
20-
`EventOps` object.
21-
22-
## Events
23-
24-
By default, `Events` wrap a file descriptor, and a bit mask of events
25-
(for example `EPOLLIN | EPOLLOUT`). The `Events` can optionally contain user
26-
defined data.
27-
28-
The `Events` are used in `add`, `remove` and `modify` functions
29-
in [`EventOps`](../src/events.rs). While their semantic is very similar to that
30-
of `libc::epoll_event`, they come with an additional requirement. When
31-
creating `Events` objects, the subscribers must specify the file descriptor
32-
associated with the event mask. There are a few reasons behind this choice:
33-
- Reducing the number of parameters on the `EventOps` functions. Instead of
34-
always passing the file descriptor along with an `epoll_event` object, the
35-
user only needs to pass `Events`.
36-
- Backing the file descriptor in `Events` provides a simple mapping from a file
37-
descriptor to the subscriber that is watching events on that particular file
38-
descriptor.
39-
40-
Storing the file descriptor in all `Events` means that there are 32 bits left
41-
for custom user data.
42-
A file descriptor can be registered only once (it can be associated with only
43-
one subscriber).
44-
45-
### Using Events With Custom Data
46-
47-
The 32-bits in custom data can be used to map events to internal callbacks
48-
based on user-defined numeric values instead of file descriptors. In the
49-
below example, the user defined values are consecutive so that the match
50-
statement can be optimized to a jump table.
51-
52-
```rust
53-
struct Painter {}
54-
const PROCESS_GREEN:u32 = 0;
55-
const PROCESS_RED: u32 = 1;
56-
const PROCESS_BLUE: u32 = 2;
57-
58-
impl Painter {
59-
fn process_green(&self, event: Events) {}
60-
fn process_red(&self, event: Events) {}
61-
fn process_blue(&self, events: Events) {}
62-
}
63-
64-
impl MutEventSubscriber for Painter {
65-
fn init(&mut self, ops: &mut EventOps) {
66-
let green_eventfd = EventFd::new(0).unwrap();
67-
let ev_for_green = Events::with_data(&green_eventfd, PROCESS_GREEN, EventSet::IN);
68-
ops.add(ev_for_green).unwrap();
693

70-
let red_eventfd = EventFd::new(0).unwrap();
71-
let ev_for_red = Events::with_data(&red_eventfd, PROCESS_RED, EventSet::IN);
72-
ops.add(ev_for_red).unwrap();
4+
`EventManager` is a wrapper over [epoll](https://man7.org/linux/man-pages/man7/epoll.7.html) that
5+
allows for more ergonomic usage with many events.
736

74-
let blue_eventfd = EventFd::new(0).unwrap();
75-
let ev_for_blue = Events::with_data(&blue_eventfd, PROCESS_BLUE, EventSet::IN);
76-
ops.add(ev_for_blue).unwrap();
77-
}
78-
79-
fn process(&mut self, events: Events, ops: &mut EventOps) {
80-
match events.data() {
81-
PROCESS_GREEN => self.process_green(events),
82-
PROCESS_RED => self.process_red(events),
83-
PROCESS_BLUE => self.process_blue(events),
84-
_ => error!("spurious event"),
85-
};
86-
}
87-
}
88-
```
89-
90-
## Remote Endpoint
91-
92-
A manager remote endpoint allows users to interact with the `EventManger`
93-
(as a `SubscriberOps` trait object) from a different thread of execution.
94-
This is particularly useful when the `EventManager` owns the subscriber object
95-
the user wants to interact with, and the communication happens from a separate
96-
thread. This functionality is gated behind the `remote_endpoint` feature.
97-
98-
The current implementation relies on passing boxed closures to the manager and
99-
getting back a boxed result. The manager is notified about incoming invocation
100-
requests via an [`EventFd`](https://docs.rs/vmm-sys-util/latest/vmm_sys_util/eventfd/struct.EventFd.html)
101-
which is added by the manager to its internal run loop. The manager runs each
102-
closure to completion, and then returns the boxed result using a sender object
103-
that is part of the initial message that also included the closure. The
104-
following example uses the previously defined `Painter` subscriber type.
105-
106-
```rust
107-
fn main() {
108-
// Create an event manager object.
109-
let mut event_manager = EventManager::<Painter>::new().unwrap();
110-
111-
// Obtain a remote endpoint object.
112-
let endpoint = event_manager.remote_endpoint();
113-
114-
// Move the event manager to a new thread and start running the event loop there.
115-
let thread_handle = thread::spawn(move || loop {
116-
event_manager.run().unwrap();
117-
});
118-
119-
let subscriber = Painter {};
120-
121-
// Add the subscriber using the remote endpoint. The subscriber is moved to the event
122-
// manager thread, and is now owned by the manager. In return, we get the subscriber id,
123-
// which can be used to identify the subscriber for subsequent operations.
124-
let id = endpoint
125-
.call_blocking(move |sub_ops| -> Result<SubscriberId> {
126-
Ok(sub_ops.add_subscriber(subscriber))
127-
})
128-
.unwrap();
129-
// ...
130-
131-
// Add a new event to the subscriber, using fd 1 as an example.
132-
let events = Events::new_raw(1, EventSet::OUT);
133-
endpoint
134-
.call_blocking(move |sub_ops| -> Result<()> { sub_ops.event_ops(id)?.add(events) })
135-
.unwrap();
136-
137-
// ...
7+
## Interest List Updates
1388

139-
thread_handle.join();
140-
}
141-
```
9+
Event actions are represented by a closure, these are given a mutable reference to the
10+
`EventManager`, this can be used to:
14211

143-
The `call_blocking` invocation sends a message over a channel to the event manager on the
144-
other thread, and then blocks until a response is received. The event manager detects the
145-
presence of such messages as with any other event, and handles them as part of the event
146-
loop. This can lead to deadlocks if, for example, `call_blocking` is invoked in the `process`
147-
implmentation of a subscriber to the same event manager.
12+
- Add a new event.
13+
- Remove an existing event.

docs/DEVELOPMENT.md

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,8 @@
22

33
## Testing
44

5-
The `event-manager` is tested using:
6-
- unit tests - defined in their corresponding modules
7-
- Rust integration tests - defined in the [tests](../tests) directory
8-
- performance tests - defined in the [benches](../benches) directory
9-
10-
The integration and performance tests share subscribers implementations
11-
which can be found under the [src/utilities](../src/utilities) module.
12-
13-
The `utilities` module is compiled only when using the `test_utilities`
14-
feature. To run unit tests, integration tests, and performance tests, the user
15-
needs to specify the `test_utilities` feature; otherwise the build fails.
16-
17-
```bash
18-
cargo test --features test_utilities
19-
cargo bench --features test_utilities
20-
```
21-
22-
We recommend running all the tests before submitting a PR as follows:
5+
The `event-manager` is tested using unit tests.
236

247
```bash
25-
cargo test --all-features
26-
```
27-
28-
Performance tests are implemented using
29-
[criterion](https://docs.rs/crate/criterion/). Running the performance tests
30-
locally should work, but only when they're run as part of the CI performance
31-
improvements/degradations can be noticed. More details about performance tests
32-
[here](https://github.com/rust-vmm/rust-vmm-ci#performance-tests).
8+
cargo test
9+
```

docs/event-manager.png

-66.4 KB
Binary file not shown.

0 commit comments

Comments
 (0)