|
1 | 1 | # Event Manager Design
|
2 | 2 |
|
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(); |
69 | 3 |
|
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. |
73 | 6 |
|
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 |
138 | 8 |
|
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: |
142 | 11 |
|
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. |
0 commit comments