|
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