From a77817198e7bdc077931a3910ec1c93c4632ac5c Mon Sep 17 00:00:00 2001 From: Jonathan Woollett-Light Date: Tue, 30 May 2023 18:14:07 +0100 Subject: [PATCH] feat: Procedural overhaul Signed-off-by: Jonathan Woollett-Light --- Cargo.toml | 15 +- benches/main.rs | 299 ++++++++++++++++----- docs/DESIGN.md | 148 +---------- docs/DEVELOPMENT.md | 29 +-- docs/event-manager.png | Bin 68017 -> 0 bytes rust-vmm-ci | 2 +- src/endpoint.rs | 187 ------------- src/epoll.rs | 89 ------- src/events.rs | 322 ----------------------- src/lib.rs | 493 ++++++++++++++++++----------------- src/manager.rs | 483 ---------------------------------- src/subscribers.rs | 54 ---- src/utilities/mod.rs | 18 -- src/utilities/subscribers.rs | 328 ----------------------- tests/basic_event_manager.rs | 111 -------- tests/endpoint.rs | 143 ---------- tests/multi_threaded.rs | 150 ----------- tests/negative_tests.rs | 105 -------- tests/regressions.rs | 24 -- 19 files changed, 503 insertions(+), 2497 deletions(-) delete mode 100644 docs/event-manager.png delete mode 100644 src/endpoint.rs delete mode 100644 src/epoll.rs delete mode 100644 src/events.rs delete mode 100644 src/manager.rs delete mode 100644 src/subscribers.rs delete mode 100644 src/utilities/mod.rs delete mode 100644 src/utilities/subscribers.rs delete mode 100644 tests/basic_event_manager.rs delete mode 100644 tests/endpoint.rs delete mode 100644 tests/multi_threaded.rs delete mode 100644 tests/negative_tests.rs delete mode 100644 tests/regressions.rs diff --git a/Cargo.toml b/Cargo.toml index c892e13..c3f3dcd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,19 +14,8 @@ vmm-sys-util = "0.11.0" libc = "0.2.39" [dev-dependencies] -criterion = "0.3.5" - -[features] -remote_endpoint = [] -test_utilities = [] +criterion = "0.5.1" [[bench]] name = "main" -harness = false - -[lib] -bench = false # https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options - -[profile.bench] -lto = true -codegen-units = 1 +harness = false \ No newline at end of file diff --git a/benches/main.rs b/benches/main.rs index a845621..4c95c3e 100644 --- a/benches/main.rs +++ b/benches/main.rs @@ -3,71 +3,154 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use event_manager::utilities::subscribers::{ - CounterInnerMutSubscriber, CounterSubscriber, CounterSubscriberWithData, -}; -use event_manager::{EventManager, EventSubscriber, MutEventSubscriber, SubscriberOps}; +use event_manager::{BufferedEventManager, EventManager}; +use std::os::fd::AsFd; +use std::os::fd::FromRawFd; +use std::os::fd::OwnedFd; +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; +use vmm_sys_util::epoll::EventSet; // Test the performance of event manager when it manages a single subscriber type. // The performance is assessed under stress, all added subscribers have active events. fn run_basic_subscriber(c: &mut Criterion) { - let no_of_subscribers = 200; + let no_of_subscribers = 200i32; - let mut event_manager = EventManager::::new().unwrap(); - for _ in 0..no_of_subscribers { - let mut counter_subscriber = CounterSubscriber::default(); - counter_subscriber.trigger_event(); - event_manager.add_subscriber(counter_subscriber); - } + let mut event_manager = + BufferedEventManager::with_capacity(false, no_of_subscribers as usize).unwrap(); + + let subscribers = (0..no_of_subscribers).map(|_| { + // Create an eventfd that is initialized with 1 waiting event. + let event_fd = unsafe { + let raw_fd = libc::eventfd(1,0); + assert_ne!(raw_fd, -1); + OwnedFd::from_raw_fd(raw_fd) + }; + + event_manager.add(event_fd.as_fd(),EventSet::IN | EventSet::ERROR | EventSet::HANG_UP,Box::new(move |_:&mut EventManager, event_set: EventSet| { + match event_set { + EventSet::IN => (), + EventSet::ERROR => { + eprintln!("Got error on the monitored event."); + }, + EventSet::HANG_UP => { + // TODO Do this: https://github.com/rust-vmm/event-manager/blob/main/src/utilities/subscribers.rs#L116:L118 + panic!("Cannot continue execution. Associated fd was closed."); + }, + _ => { + eprintln!("Received spurious event from the event manager {event_set:#?}."); + } + } + })).unwrap(); + + event_fd + }).collect::>(); c.bench_function("process_basic", |b| { b.iter(|| { - let ev_count = event_manager.run().unwrap(); - assert_eq!(ev_count, no_of_subscribers) + assert_eq!(event_manager.wait(Some(0)), Ok(no_of_subscribers)); }) }); + + drop(subscribers); } // Test the performance of event manager when the subscribers are wrapped in an Arc. // The performance is assessed under stress, all added subscribers have active events. fn run_arc_mutex_subscriber(c: &mut Criterion) { - let no_of_subscribers = 200; + let no_of_subscribers = 200i32; + + let mut event_manager = + BufferedEventManager::with_capacity(false, no_of_subscribers as usize).unwrap(); + + let subscribers = (0..no_of_subscribers).map(|_| { + // Create an eventfd that is initialized with 1 waiting event. + let event_fd = unsafe { + let raw_fd = libc::eventfd(1,0); + assert_ne!(raw_fd, -1); + OwnedFd::from_raw_fd(raw_fd) + }; + let counter = Arc::new(Mutex::new(0u64)); + let counter_clone = counter.clone(); + + event_manager.add(event_fd.as_fd(),EventSet::IN | EventSet::ERROR | EventSet::HANG_UP,Box::new(move |_:&mut EventManager, event_set: EventSet| { + match event_set { + EventSet::IN => { + *counter_clone.lock().unwrap() += 1; + }, + EventSet::ERROR => { + eprintln!("Got error on the monitored event."); + }, + EventSet::HANG_UP => { + // TODO Do this: https://github.com/rust-vmm/event-manager/blob/main/src/utilities/subscribers.rs#L116:L118 + panic!("Cannot continue execution. Associated fd was closed."); + }, + _ => { + eprintln!("Received spurious event from the event manager {event_set:#?}."); + } + } + })).unwrap(); - let mut event_manager = EventManager::>>::new().unwrap(); - for _ in 0..no_of_subscribers { - let counter_subscriber = Arc::new(Mutex::new(CounterSubscriber::default())); - counter_subscriber.lock().unwrap().trigger_event(); - event_manager.add_subscriber(counter_subscriber); - } + (event_fd,counter) + }).collect::>(); c.bench_function("process_with_arc_mutex", |b| { b.iter(|| { - let ev_count = event_manager.run().unwrap(); - assert_eq!(ev_count, no_of_subscribers) + assert_eq!(event_manager.wait(Some(0)), Ok(no_of_subscribers)); }) }); + + drop(subscribers); } // Test the performance of event manager when the subscribers are wrapped in an Arc, and they // leverage inner mutability to update their internal state. // The performance is assessed under stress, all added subscribers have active events. fn run_subscriber_with_inner_mut(c: &mut Criterion) { - let no_of_subscribers = 200; + let no_of_subscribers = 200i32; + + let mut event_manager = + BufferedEventManager::with_capacity(false, no_of_subscribers as usize).unwrap(); + + let subscribers = (0..no_of_subscribers).map(|_| { + // Create an eventfd that is initialized with 1 waiting event. + let event_fd = unsafe { + let raw_fd = libc::eventfd(1,0); + assert_ne!(raw_fd, -1); + OwnedFd::from_raw_fd(raw_fd) + }; + let counter = Arc::new(AtomicU64::new(0)); + let counter_clone = counter.clone(); + + event_manager.add(event_fd.as_fd(),EventSet::IN | EventSet::ERROR | EventSet::HANG_UP,Box::new(move |_:&mut EventManager, event_set: EventSet| { + match event_set { + EventSet::IN => { + counter_clone.fetch_add(1, Ordering::SeqCst); + }, + EventSet::ERROR => { + eprintln!("Got error on the monitored event."); + }, + EventSet::HANG_UP => { + // TODO Do this: https://github.com/rust-vmm/event-manager/blob/main/src/utilities/subscribers.rs#L116:L118 + panic!("Cannot continue execution. Associated fd was closed."); + }, + _ => { + eprintln!("Received spurious event from the event manager {event_set:#?}."); + } + } + })).unwrap(); - let mut event_manager = EventManager::>::new().unwrap(); - for _ in 0..no_of_subscribers { - let counter_subscriber = CounterInnerMutSubscriber::default(); - counter_subscriber.trigger_event(); - event_manager.add_subscriber(Arc::new(counter_subscriber)); - } + (event_fd,counter) + }).collect::>(); c.bench_function("process_with_inner_mut", |b| { b.iter(|| { - let ev_count = event_manager.run().unwrap(); - assert_eq!(ev_count, no_of_subscribers) + assert_eq!(event_manager.wait(Some(0)), Ok(no_of_subscribers)); }) }); + + drop(subscribers); } // Test the performance of event manager when it manages subscribers of different types, that are @@ -76,63 +159,151 @@ fn run_subscriber_with_inner_mut(c: &mut Criterion) { // The performance is assessed under stress, all added subscribers have active events, and the // CounterSubscriberWithData subscribers have multiple active events. fn run_multiple_subscriber_types(c: &mut Criterion) { - let no_of_subscribers = 100; + let no_of_subscribers = 100i32; + + let total = no_of_subscribers + (no_of_subscribers * i32::try_from(EVENTS).unwrap()); + + let mut event_manager = + BufferedEventManager::with_capacity(false, usize::try_from(total).unwrap()).unwrap(); - let mut event_manager = EventManager::>>::new() - .expect("Cannot create event manager."); + let subscribers = (0..no_of_subscribers).map(|_| { + // Create an eventfd that is initialized with 1 waiting event. + let event_fd = unsafe { + let raw_fd = libc::eventfd(1,0); + assert_ne!(raw_fd, -1); + OwnedFd::from_raw_fd(raw_fd) + }; + let counter = Arc::new(AtomicU64::new(0)); + let counter_clone = counter.clone(); - for i in 0..no_of_subscribers { - // The `CounterSubscriberWithData` expects to receive a number as a parameter that - // represents the number it can use as its inner Events data. - let mut data_subscriber = CounterSubscriberWithData::new(i * no_of_subscribers); - data_subscriber.trigger_all_counters(); - event_manager.add_subscriber(Arc::new(Mutex::new(data_subscriber))); + event_manager.add(event_fd.as_fd(),EventSet::IN | EventSet::ERROR | EventSet::HANG_UP,Box::new(move |_:&mut EventManager, event_set: EventSet| { + match event_set { + EventSet::IN => { + counter_clone.fetch_add(1, Ordering::SeqCst); + }, + EventSet::ERROR => { + eprintln!("Got error on the monitored event."); + }, + EventSet::HANG_UP => { + // TODO Do this: https://github.com/rust-vmm/event-manager/blob/main/src/utilities/subscribers.rs#L116:L118 + panic!("Cannot continue execution. Associated fd was closed."); + }, + _ => { + eprintln!("Received spurious event from the event manager {event_set:#?}."); + } + } + })).unwrap(); - let mut counter_subscriber = CounterSubscriber::default(); - counter_subscriber.trigger_event(); - event_manager.add_subscriber(Arc::new(Mutex::new(counter_subscriber))); - } + (event_fd,counter) + }).collect::>(); + + const EVENTS: usize = 3; + + let subscribers_with_data = (0..no_of_subscribers) + .map(|_| { + let data = Arc::new([AtomicU64::new(0), AtomicU64::new(0), AtomicU64::new(0)]); + assert_eq!(data.len(), EVENTS); + + // Create eventfd's that are initialized with 1 waiting event. + let inner_subscribers = (0..EVENTS) + .map(|_| unsafe { + let raw_fd = libc::eventfd(1, 0); + assert_ne!(raw_fd, -1); + OwnedFd::from_raw_fd(raw_fd) + }) + .collect::>(); + + for i in 0..EVENTS { + let data_clone = data.clone(); + + event_manager + .add( + inner_subscribers[i].as_fd(), + EventSet::IN | EventSet::ERROR | EventSet::HANG_UP, + Box::new(move |_: &mut EventManager, event_set: EventSet| { + match event_set { + EventSet::IN => { + data_clone[i].fetch_add(1, Ordering::SeqCst); + } + EventSet::ERROR => { + eprintln!("Got error on the monitored event."); + } + EventSet::HANG_UP => { + // TODO Do this: https://github.com/rust-vmm/event-manager/blob/main/src/utilities/subscribers.rs#L116:L118 + panic!("Cannot continue execution. Associated fd was closed."); + } + _ => {} + } + }), + ) + .unwrap(); + } + + (inner_subscribers, data) + }) + .collect::>(); c.bench_function("process_dynamic_dispatch", |b| { b.iter(|| { - let _ = event_manager.run().unwrap(); + assert_eq!(event_manager.wait(Some(0)), Ok(total)); }) }); + + drop(subscribers); + drop(subscribers_with_data); } // Test the performance of event manager when it manages a single subscriber type. // Just a few of the events are active in this test scenario. fn run_with_few_active_events(c: &mut Criterion) { - let no_of_subscribers = 200; + let no_of_subscribers = 200i32; + let active = 1 + no_of_subscribers / 23; + + let mut event_manager = + BufferedEventManager::with_capacity(false, no_of_subscribers as usize).unwrap(); - let mut event_manager = EventManager::::new().unwrap(); + let subscribers = (0..no_of_subscribers).map(|i| { + // Create an eventfd that is initialized with 1 waiting event. + let event_fd = unsafe { + let raw_fd = libc::eventfd((i % 23 == 0) as u8 as u32,0); + assert_ne!(raw_fd, -1); + OwnedFd::from_raw_fd(raw_fd) + }; - for i in 0..no_of_subscribers { - let mut counter_subscriber = CounterSubscriber::default(); - // Let's activate the events for a few subscribers (i.e. only the ones that are - // divisible by 23). 23 is a random number that I just happen to like. - if i % 23 == 0 { - counter_subscriber.trigger_event(); - } - event_manager.add_subscriber(counter_subscriber); - } + event_manager.add(event_fd.as_fd(),EventSet::IN | EventSet::ERROR | EventSet::HANG_UP,Box::new(move |_:&mut EventManager, event_set: EventSet| { + match event_set { + EventSet::IN => (), + EventSet::ERROR => { + eprintln!("Got error on the monitored event."); + }, + EventSet::HANG_UP => { + // TODO Do this: https://github.com/rust-vmm/event-manager/blob/main/src/utilities/subscribers.rs#L116:L118 + panic!("Cannot continue execution. Associated fd was closed."); + }, + _ => { + eprintln!("Received spurious event from the event manager {event_set:#?}."); + } + } + })).unwrap(); + + event_fd + }).collect::>(); c.bench_function("process_dispatch_few_events", |b| { b.iter(|| { - let _ = event_manager.run().unwrap(); + assert_eq!(event_manager.wait(Some(0)), Ok(active)); }) }); + + drop(subscribers); } -criterion_group! { +criterion_group!( name = benches; config = Criterion::default() .sample_size(200) .measurement_time(std::time::Duration::from_secs(40)); targets = run_basic_subscriber, run_arc_mutex_subscriber, run_subscriber_with_inner_mut, - run_multiple_subscriber_types, run_with_few_active_events -} - -criterion_main! { - benches -} + run_multiple_subscriber_types, run_with_few_active_events +); +criterion_main!(benches); diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 623076f..7bc587c 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -1,147 +1,13 @@ # Event Manager Design -## Interest List Updates - -Subscribers can update their interest list when the `EventManager` calls -their `process` function. The EventManager crates a specialized `EventOps` -object. `EventOps` limits the operations that the subscribers may call to the -ones that are related to the interest list as follows: -- Adding a new event that the subscriber is interested in. -- Modifying an existing event (for example: update an event to be - edge-triggered instead of being level-triggered or update the user data - associated with an event). -- Remove an existing event. - -The subscriber is responsible for handling the errors returned from calling -`add`, `modify` or `remove`. - -The `EventManager` knows how to associate these actions to a registered -subscriber because it adds the corresponding `SubscriberId` when it creates the -`EventOps` object. - -## Events - -By default, `Events` wrap a file descriptor, and a bit mask of events -(for example `EPOLLIN | EPOLLOUT`). The `Events` can optionally contain user -defined data. - -The `Events` are used in `add`, `remove` and `modify` functions -in [`EventOps`](../src/events.rs). While their semantic is very similar to that -of `libc::epoll_event`, they come with an additional requirement. When -creating `Events` objects, the subscribers must specify the file descriptor -associated with the event mask. There are a few reasons behind this choice: -- Reducing the number of parameters on the `EventOps` functions. Instead of - always passing the file descriptor along with an `epoll_event` object, the - user only needs to pass `Events`. -- Backing the file descriptor in `Events` provides a simple mapping from a file - descriptor to the subscriber that is watching events on that particular file - descriptor. - -Storing the file descriptor in all `Events` means that there are 32 bits left -for custom user data. -A file descriptor can be registered only once (it can be associated with only -one subscriber). - -### Using Events With Custom Data - -The 32-bits in custom data can be used to map events to internal callbacks -based on user-defined numeric values instead of file descriptors. In the -below example, the user defined values are consecutive so that the match -statement can be optimized to a jump table. - -```rust - struct Painter {} - const PROCESS_GREEN:u32 = 0; - const PROCESS_RED: u32 = 1; - const PROCESS_BLUE: u32 = 2; - - impl Painter { - fn process_green(&self, event: Events) {} - fn process_red(&self, event: Events) {} - fn process_blue(&self, events: Events) {} - } - - impl MutEventSubscriber for Painter { - fn init(&mut self, ops: &mut EventOps) { - let green_eventfd = EventFd::new(0).unwrap(); - let ev_for_green = Events::with_data(&green_eventfd, PROCESS_GREEN, EventSet::IN); - ops.add(ev_for_green).unwrap(); - let red_eventfd = EventFd::new(0).unwrap(); - let ev_for_red = Events::with_data(&red_eventfd, PROCESS_RED, EventSet::IN); - ops.add(ev_for_red).unwrap(); +`EventManager` is a wrapper over [epoll](https://man7.org/linux/man-pages/man7/epoll.7.html) that +allows for more ergonomic usage with many events. - let blue_eventfd = EventFd::new(0).unwrap(); - let ev_for_blue = Events::with_data(&blue_eventfd, PROCESS_BLUE, EventSet::IN); - ops.add(ev_for_blue).unwrap(); - } - - fn process(&mut self, events: Events, ops: &mut EventOps) { - match events.data() { - PROCESS_GREEN => self.process_green(events), - PROCESS_RED => self.process_red(events), - PROCESS_BLUE => self.process_blue(events), - _ => error!("spurious event"), - }; - } - } -``` - -## Remote Endpoint - -A manager remote endpoint allows users to interact with the `EventManger` -(as a `SubscriberOps` trait object) from a different thread of execution. -This is particularly useful when the `EventManager` owns the subscriber object -the user wants to interact with, and the communication happens from a separate -thread. This functionality is gated behind the `remote_endpoint` feature. - -The current implementation relies on passing boxed closures to the manager and -getting back a boxed result. The manager is notified about incoming invocation -requests via an [`EventFd`](https://docs.rs/vmm-sys-util/latest/vmm_sys_util/eventfd/struct.EventFd.html) -which is added by the manager to its internal run loop. The manager runs each -closure to completion, and then returns the boxed result using a sender object -that is part of the initial message that also included the closure. The -following example uses the previously defined `Painter` subscriber type. - -```rust -fn main() { - // Create an event manager object. - let mut event_manager = EventManager::::new().unwrap(); - - // Obtain a remote endpoint object. - let endpoint = event_manager.remote_endpoint(); - - // Move the event manager to a new thread and start running the event loop there. - let thread_handle = thread::spawn(move || loop { - event_manager.run().unwrap(); - }); - - let subscriber = Painter {}; - - // Add the subscriber using the remote endpoint. The subscriber is moved to the event - // manager thread, and is now owned by the manager. In return, we get the subscriber id, - // which can be used to identify the subscriber for subsequent operations. - let id = endpoint - .call_blocking(move |sub_ops| -> Result { - Ok(sub_ops.add_subscriber(subscriber)) - }) - .unwrap(); - // ... - - // Add a new event to the subscriber, using fd 1 as an example. - let events = Events::new_raw(1, EventSet::OUT); - endpoint - .call_blocking(move |sub_ops| -> Result<()> { sub_ops.event_ops(id)?.add(events) }) - .unwrap(); - - // ... +## Interest List Updates - thread_handle.join(); -} -``` +Event actions are represented by a closure, these are given a mutable reference to the +`EventManager`, this can be used to: -The `call_blocking` invocation sends a message over a channel to the event manager on the -other thread, and then blocks until a response is received. The event manager detects the -presence of such messages as with any other event, and handles them as part of the event -loop. This can lead to deadlocks if, for example, `call_blocking` is invoked in the `process` -implmentation of a subscriber to the same event manager. \ No newline at end of file +- Add a new event. +- Remove an existing event. \ No newline at end of file diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index f38a4a2..a8fa63f 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -2,31 +2,8 @@ ## Testing -The `event-manager` is tested using: -- unit tests - defined in their corresponding modules -- Rust integration tests - defined in the [tests](../tests) directory -- performance tests - defined in the [benches](../benches) directory - -The integration and performance tests share subscribers implementations -which can be found under the [src/utilities](../src/utilities) module. - -The `utilities` module is compiled only when using the `test_utilities` -feature. To run unit tests, integration tests, and performance tests, the user -needs to specify the `test_utilities` feature; otherwise the build fails. - -```bash -cargo test --features test_utilities -cargo bench --features test_utilities -``` - -We recommend running all the tests before submitting a PR as follows: +The `event-manager` is tested using unit tests. ```bash -cargo test --all-features -``` - -Performance tests are implemented using -[criterion](https://docs.rs/crate/criterion/). Running the performance tests -locally should work, but only when they're run as part of the CI performance -improvements/degradations can be noticed. More details about performance tests -[here](https://github.com/rust-vmm/rust-vmm-ci#performance-tests). +cargo test +``` \ No newline at end of file diff --git a/docs/event-manager.png b/docs/event-manager.png deleted file mode 100644 index 5e8808a24cf6c38a5cf12b08b9be543cbf8bee58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68017 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 in order to have a single concrete type definition for the messages that -// contain closures to be executed (the `FnMsg` defined below). The actual return type is -// recovered by the initiator of the remote call (more details in the implementation of -// `RemoteEndpoint::call_blocking` below). The `Send` bound is required to send back the boxed -// result over the return channel. -type ErasedResult = Box; - -// Type alias for the boxed closures received by the manager. The `Send` bound at the end applies -// to the type of the closure, and is required to send the box over the channel. -type FnOnceBox = Box) -> ErasedResult + Send>; - -// The type of the messages received by the manager over its receive mpsc channel. -pub(crate) struct FnMsg { - // The closure to execute. - pub(crate) fnbox: FnOnceBox, - // The sending endpoint of the channel used by the remote called to wait for the result. - pub(crate) sender: Option>, -} - -// Used by the `EventManager` to keep state associated with the channel. -pub(crate) struct EventManagerChannel { - // A clone of this is given to every `RemoteEndpoint` and used to signal the presence of - // an new message on the channel. - pub(crate) event_fd: Arc, - // A clone of this sender is given to every `RemoteEndpoint` and used to send `FnMsg` objects - // to the `EventManager` over the channel. - pub(crate) sender: Sender>, - // The receiving half of the channel, used to receive incoming `FnMsg` objects. - pub(crate) receiver: Receiver>, -} - -impl EventManagerChannel { - pub(crate) fn new() -> Result { - let (sender, receiver) = channel(); - Ok(EventManagerChannel { - event_fd: Arc::new( - EventFd::new(EFD_NONBLOCK).map_err(|e| Error::EventFd(Errno::from(e)))?, - ), - sender, - receiver, - }) - } - - pub(crate) fn fd(&self) -> RawFd { - self.event_fd.as_raw_fd() - } - - pub(crate) fn remote_endpoint(&self) -> RemoteEndpoint { - RemoteEndpoint { - msg_sender: self.sender.clone(), - event_fd: self.event_fd.clone(), - } - } -} - -/// Enables interactions with an `EventManager` that runs on a different thread of execution. -pub struct RemoteEndpoint { - // A sender associated with `EventManager` channel requests are sent over. - msg_sender: Sender>, - // Used to notify the `EventManager` about the arrival of a new request. - event_fd: Arc, -} - -impl Clone for RemoteEndpoint { - fn clone(&self) -> Self { - RemoteEndpoint { - msg_sender: self.msg_sender.clone(), - event_fd: self.event_fd.clone(), - } - } -} - -impl RemoteEndpoint { - // Send a message to the remote EventManger and raise a notification. - fn send(&self, msg: FnMsg) -> Result<()> { - self.msg_sender.send(msg).map_err(|_| Error::ChannelSend)?; - self.event_fd - .write(1) - .map_err(|e| Error::EventFd(Errno::from(e)))?; - Ok(()) - } - - /// Call the specified closure on the associated remote `EventManager` (provided as a - /// `SubscriberOps` trait object), and return the result. This method blocks until the result - /// is received, and calling it from the same thread where the event loop runs leads to - /// a deadlock. - pub fn call_blocking(&self, f: F) -> result::Result - where - F: FnOnce(&mut dyn SubscriberOps) -> result::Result + Send + 'static, - O: Send + 'static, - E: From + Send + 'static, - { - // Create a temporary channel used to get back the result. We keep the receiving end, - // and put the sending end into the message we pass to the remote `EventManager`. - let (sender, receiver) = channel(); - - // We erase the return type of `f` by moving and calling it inside another closure which - // hides the result as an `ErasedResult`. This allows using the same channel to send - // closures with different signatures (and thus different types) to the remote - // `EventManager`. - let fnbox = Box::new( - move |ops: &mut dyn SubscriberOps| -> ErasedResult { Box::new(f(ops)) }, - ); - - // Send the message requesting the closure invocation. - self.send(FnMsg { - fnbox, - sender: Some(sender), - })?; - - // Block until a response is received. We can use unwrap because the downcast cannot fail, - // since the signature of F (more specifically, the return value) constrains the concrete - // type that's in the box. - let result_box = receiver - .recv() - .map_err(|_| Error::ChannelRecv)? - .downcast() - .unwrap(); - - // Turns out the dereference operator has a special behaviour for boxed objects; if we - // own a `b: Box` and call `*b`, the box goes away and we get the `T` inside. - *result_box - } - - /// Call the specified closure on the associated local/remote `EventManager` (provided as a - /// `SubscriberOps` trait object), and discard the result. This method only fires - /// the request but does not wait for result, so it may be called from the same thread where - /// the event loop runs. - pub fn fire(&self, f: F) -> Result<()> - where - F: FnOnce(&mut dyn SubscriberOps) + Send + 'static, - { - // We erase the return type of `f` by moving and calling it inside another closure which - // hides the result as an `ErasedResult`. This allows using the same channel send closures - // with different signatures (and thus different types) to the remote `EventManager`. - let fnbox = Box::new( - move |ops: &mut dyn SubscriberOps| -> ErasedResult { - f(ops); - Box::new(()) - }, - ); - - // Send the message requesting the closure invocation. - self.send(FnMsg { - fnbox, - sender: None, - }) - } - - /// Kick the worker thread to wake up from the epoll event loop. - pub fn kick(&self) -> Result<()> { - self.event_fd - .write(1) - .map(|_| ()) - .map_err(|e| Error::EventFd(Errno::from(e))) - } -} diff --git a/src/epoll.rs b/src/epoll.rs deleted file mode 100644 index b174f8d..0000000 --- a/src/epoll.rs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -use std::collections::HashMap; -use std::os::unix::io::RawFd; - -use vmm_sys_util::epoll::{ControlOperation, Epoll, EpollEvent}; - -use super::{Errno, Error, EventOps, Result, SubscriberId}; - -// Internal use structure that keeps the epoll related state of an EventManager. -pub(crate) struct EpollWrapper { - // The epoll wrapper. - pub(crate) epoll: Epoll, - // Records the id of the subscriber associated with the given RawFd. The event event_manager - // does not currently support more than one subscriber being associated with an fd. - pub(crate) fd_dispatch: HashMap, - // Records the set of fds that are associated with the subscriber that has the given id. - // This is used to keep track of all fds associated with a subscriber. - pub(crate) subscriber_watch_list: HashMap>, - // A scratch buffer to avoid allocating/freeing memory on each poll iteration. - pub(crate) ready_events: Vec, -} - -impl EpollWrapper { - pub(crate) fn new(ready_events_capacity: usize) -> Result { - Ok(EpollWrapper { - epoll: Epoll::new().map_err(|e| Error::Epoll(Errno::from(e)))?, - fd_dispatch: HashMap::new(), - subscriber_watch_list: HashMap::new(), - ready_events: vec![EpollEvent::default(); ready_events_capacity], - }) - } - - // Poll the underlying epoll fd for pending IO events. - pub(crate) fn poll(&mut self, milliseconds: i32) -> Result { - let event_count = match self.epoll.wait(milliseconds, &mut self.ready_events[..]) { - Ok(ev) => ev, - // EINTR is not actually an error that needs to be handled. The documentation - // for epoll.run specifies that run exits when it for an event, on timeout, or - // on interrupt. - Err(e) if e.raw_os_error() == Some(libc::EINTR) => return Ok(0), - Err(e) => return Err(Error::Epoll(Errno::from(e))), - }; - - Ok(event_count) - } - - // Remove the fds associated with the provided subscriber id from the epoll set and the - // other structures. The subscriber id must be valid. - pub(crate) fn remove(&mut self, subscriber_id: SubscriberId) { - let fds = self - .subscriber_watch_list - .remove(&subscriber_id) - .unwrap_or_default(); - for fd in fds { - // We ignore the result of the operation since there's nothing we can't do, and its - // not a significant error condition at this point. - let _ = self - .epoll - .ctl(ControlOperation::Delete, fd, EpollEvent::default()); - self.remove_event(fd); - } - } - - // Flush and stop receiving IO events associated with the file descriptor. - pub(crate) fn remove_event(&mut self, fd: RawFd) { - self.fd_dispatch.remove(&fd); - for event in self.ready_events.iter_mut() { - if event.fd() == fd { - // It's a little complex to remove the entry from the Vec, so do soft removal - // by setting it to default value. - *event = EpollEvent::default(); - } - } - } - - // Gets the id of the subscriber associated with the provided fd (if such an association - // exists). - pub(crate) fn subscriber_id(&self, fd: RawFd) -> Option { - self.fd_dispatch.get(&fd).copied() - } - - // Creates and returns an EventOps object for the subscriber associated with the provided - // id. The subscriber id must be valid. - pub(crate) fn ops_unchecked(&mut self, subscriber_id: SubscriberId) -> EventOps { - EventOps::new(self, subscriber_id) - } -} diff --git a/src/events.rs b/src/events.rs deleted file mode 100644 index d8dd8c7..0000000 --- a/src/events.rs +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -use std::os::unix::io::{AsRawFd, RawFd}; - -use super::{Errno, Error, Result, SubscriberId}; -use crate::epoll::EpollWrapper; -use vmm_sys_util::epoll::{ControlOperation, EpollEvent, EventSet}; - -/// Wrapper over an `epoll::EpollEvent` object. -/// -/// When working directly with epoll related methods, the user associates an `u64` wide -/// epoll_data_t object with every event. We want to use fds as identifiers, but at the same time -/// keep the ability to associate opaque data with an event. An `Events` object always contains an -/// fd and an `u32` data member that can be supplied by the user. When registering events with the -/// inner epoll event set, the fd and data members of `Events` are used together to generate the -/// underlying `u64` member of the epoll_data union. -#[derive(Clone, Copy, Debug)] -pub struct Events { - inner: EpollEvent, -} - -impl PartialEq for Events { - fn eq(&self, other: &Events) -> bool { - self.fd() == other.fd() - && self.data() == other.data() - && self.event_set() == other.event_set() - } -} - -impl Events { - pub(crate) fn with_inner(inner: EpollEvent) -> Self { - Self { inner } - } - - /// Create an empty event set associated with `source`. - /// - /// No explicit events are monitored for the associated file descriptor. - /// Nevertheless, [`EventSet::ERROR`](struct.EventSet.html#associatedconstant.ERROR) and - /// [`EventSet::HANG_UP`](struct.EventSet.html#associatedconstant.HANG_UP) are implicitly - /// monitored. - /// - /// # Arguments - /// - /// * source: object that wraps a file descriptor to be associated with `events` - /// - /// # Example - /// - /// ```rust - /// # use event_manager::Events; - /// # use vmm_sys_util::eventfd::EventFd; - /// let eventfd = EventFd::new(0).unwrap(); - /// let ev_set = Events::empty(&eventfd); - /// ``` - pub fn empty(source: &T) -> Self { - Self::empty_raw(source.as_raw_fd()) - } - - /// Create an empty event set associated with the supplied `RawFd` value. - /// - /// No explicit events are monitored for the associated file descriptor. - /// Nevertheless, [`EventSet::ERROR`](struct.EventSet.html#associatedconstant.ERROR) and - /// [`EventSet::HANG_UP`](struct.EventSet.html#associatedconstant.HANG_UP) are implicitly - /// monitored. - /// - /// # Example - /// - /// ```rust - /// # use event_manager::Events; - /// # use std::os::unix::io::AsRawFd; - /// # use vmm_sys_util::eventfd::EventFd; - /// let eventfd = EventFd::new(0).unwrap(); - /// let ev_set = Events::empty_raw(eventfd.as_raw_fd()); - /// ``` - pub fn empty_raw(fd: RawFd) -> Self { - Self::new_raw(fd, EventSet::empty()) - } - - /// Create an event with `source` and the associated `events` for monitoring. - /// - /// # Arguments - /// - /// * source: object that wraps a file descriptor to be associated with `events` - /// * events: events to monitor on the provided `source`; - /// [`EventSet::ERROR`](struct.EventSet.html#associatedconstant.ERROR) and - /// [`EventSet::HANG_UP`](struct.EventSet.html#associatedconstant.HANG_UP) are - /// always monitored and don't need to be explicitly added to the list. - /// - /// # Example - /// - /// ```rust - /// # use event_manager::{Events, EventSet}; - /// # use vmm_sys_util::eventfd::EventFd; - /// let eventfd = EventFd::new(0).unwrap(); - /// let event_set = EventSet::IN; - /// let ev_set = Events::new(&eventfd, event_set); - /// ``` - pub fn new(source: &T, events: EventSet) -> Self { - Self::new_raw(source.as_raw_fd(), events) - } - - /// Create an event with the supplied `RawFd` value and `events` for monitoring. - /// - /// # Arguments - /// - /// * source: file descriptor on which to monitor the `events` - /// * events: events to monitor on the provided `source`; - /// [`EventSet::ERROR`](struct.EventSet.html#associatedconstant.ERROR) and - /// [`EventSet::HANG_UP`](struct.EventSet.html#associatedconstant.HANG_UP) are - /// always monitored and don't need to be explicitly added to the list. - /// # Example - /// - /// ```rust - /// # use event_manager::{Events, EventSet}; - /// # use vmm_sys_util::eventfd::EventFd; - /// # use std::os::unix::io::AsRawFd; - /// let eventfd = EventFd::new(0).unwrap(); - /// let event_set = EventSet::IN; - /// let ev_set = Events::new_raw(eventfd.as_raw_fd(), event_set); - /// ``` - pub fn new_raw(source: RawFd, events: EventSet) -> Self { - Self::with_data_raw(source, 0, events) - } - - /// Create an event set associated with the underlying file descriptor of the source, active - /// events, and data. - /// - /// # Arguments - /// * source: object that wraps a file descriptor to be associated with `events` - /// * data: custom user data associated with the file descriptor; the data can be used for - /// uniquely identify monitored events instead of using the file descriptor. - /// * events: events to monitor on the provided `source`; - /// [`EventSet::ERROR`](struct.EventSet.html#associatedconstant.ERROR) and - /// [`EventSet::HANG_UP`](struct.EventSet.html#associatedconstant.HANG_UP) are - /// always monitored and don't need to be explicitly added to the list. - /// - /// # Examples - /// - /// ```rust - /// # use event_manager::{Events, EventSet}; - /// # use vmm_sys_util::eventfd::EventFd; - /// let eventfd = EventFd::new(0).unwrap(); - /// let event_set = EventSet::IN; - /// let custom_data = 42; - /// let ev_set = Events::with_data(&eventfd, custom_data, event_set); - /// ``` - pub fn with_data(source: &T, data: u32, events: EventSet) -> Self { - Self::with_data_raw(source.as_raw_fd(), data, events) - } - - /// Create an event set associated with the supplied `RawFd` value, active events, and data. - /// - /// # Arguments - /// * source: file descriptor to be associated with `events` - /// * data: custom user data associated with the file descriptor; the data can be used for - /// uniquely identify monitored events instead of using the file descriptor. - /// * events: events to monitor on the provided `source`; - /// [`EventSet::ERROR`](struct.EventSet.html#associatedconstant.ERROR) and - /// [`EventSet::HANG_UP`](struct.EventSet.html#associatedconstant.HANG_UP) are - /// always monitored and don't need to be explicitly added to the list. - /// - /// # Examples - /// - /// ```rust - /// # use event_manager::{Events, EventSet}; - /// # use std::os::unix::io::AsRawFd; - /// # use vmm_sys_util::eventfd::EventFd; - /// let eventfd = EventFd::new(0).unwrap(); - /// let event_set = EventSet::IN; - /// let custom_data = 42; - /// let ev_set = Events::with_data_raw(eventfd.as_raw_fd(), custom_data, event_set); - /// ``` - pub fn with_data_raw(source: RawFd, data: u32, events: EventSet) -> Self { - let inner_data = ((data as u64) << 32) + (source as u64); - Events { - inner: EpollEvent::new(events, inner_data), - } - } - - /// Return the inner fd value. - pub fn fd(&self) -> RawFd { - self.inner.data() as RawFd - } - - /// Return the inner data value. - pub fn data(&self) -> u32 { - (self.inner.data() >> 32) as u32 - } - - /// Return the active event set. - pub fn event_set(&self) -> EventSet { - self.inner.event_set() - } - - /// Return the inner `EpollEvent`. - pub fn epoll_event(&self) -> EpollEvent { - self.inner - } -} - -/// Opaque object associated with an `EventSubscriber` that allows the addition, modification, and -/// removal of events in the watchlist. -// Right now this is a concrete object, but going further it can be turned into a trait and -// passed around as a trait object. -pub struct EventOps<'a> { - // Mutable reference to the EpollContext of an EventManager. - epoll_wrapper: &'a mut EpollWrapper, - // The id of the event subscriber this object stands for. - subscriber_id: SubscriberId, -} - -impl<'a> EventOps<'a> { - pub(crate) fn new(epoll_wrapper: &'a mut EpollWrapper, subscriber_id: SubscriberId) -> Self { - EventOps { - epoll_wrapper, - subscriber_id, - } - } - - // Apply the provided control operation for the given events on the inner epoll wrapper. - fn ctl(&self, op: ControlOperation, events: Events) -> Result<()> { - self.epoll_wrapper - .epoll - .ctl(op, events.fd(), events.epoll_event()) - .map_err(|e| Error::Epoll(Errno::from(e))) - } - - /// Add the provided events to the inner epoll event set. - pub fn add(&mut self, events: Events) -> Result<()> { - let fd = events.fd(); - if self.epoll_wrapper.fd_dispatch.contains_key(&fd) { - return Err(Error::FdAlreadyRegistered); - } - - self.ctl(ControlOperation::Add, events)?; - - self.epoll_wrapper - .fd_dispatch - .insert(fd, self.subscriber_id); - - self.epoll_wrapper - .subscriber_watch_list - .entry(self.subscriber_id) - .or_insert_with(Vec::new) - .push(fd); - - Ok(()) - } - - /// Submit the provided changes to the inner epoll event set. - pub fn modify(&self, events: Events) -> Result<()> { - self.ctl(ControlOperation::Modify, events) - } - - /// Remove the specified events from the inner epoll event set. - pub fn remove(&mut self, events: Events) -> Result<()> { - // TODO: Add some more checks here? - self.ctl(ControlOperation::Delete, events)?; - self.epoll_wrapper.remove_event(events.fd()); - - if let Some(watch_list) = self - .epoll_wrapper - .subscriber_watch_list - .get_mut(&self.subscriber_id) - { - if let Some(index) = watch_list.iter().position(|&x| x == events.fd()) { - watch_list.remove(index); - } - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use vmm_sys_util::eventfd::EventFd; - - #[test] - fn test_empty_events() { - let event_fd = EventFd::new(0).unwrap(); - - let events_raw = Events::empty_raw(event_fd.as_raw_fd()); - let events = Events::empty(&event_fd); - - assert_eq!(events, events_raw); - - assert_eq!(events.event_set(), EventSet::empty()); - assert_eq!(events.data(), 0); - assert_eq!(events.fd(), event_fd.as_raw_fd()); - } - - #[test] - fn test_events_no_data() { - let event_fd = EventFd::new(0).unwrap(); - let event_set = EventSet::IN; - - let events_raw = Events::new_raw(event_fd.as_raw_fd(), event_set); - let events = Events::new(&event_fd, event_set); - - assert_eq!(events_raw, events); - - assert_eq!(events.data(), 0); - assert_eq!(events.fd(), event_fd.as_raw_fd()); - assert_eq!(events.event_set(), event_set); - } - - #[test] - fn test_events_data() { - let event_fd = EventFd::new(0).unwrap(); - let event_set = EventSet::IN; - - let events_raw = Events::with_data_raw(event_fd.as_raw_fd(), 42, event_set); - let events = Events::with_data(&event_fd, 43, event_set); - - assert_ne!(events_raw, events); - - assert_eq!(events.data(), 43); - assert_eq!(events_raw.data(), 42); - } -} diff --git a/src/lib.rs b/src/lib.rs index feaaf43..c729516 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,282 +1,299 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +use std::collections::HashMap; +use std::os::unix::io::{AsRawFd, RawFd}; -//! Event Manager traits and implementation. -#![deny(missing_docs)] +use vmm_sys_util::epoll::EventSet; -use std::cell::RefCell; -use std::ops::{Deref, DerefMut}; -use std::rc::Rc; -use std::result; -use std::sync::{Arc, Mutex}; +/// The function thats runs when an event occurs. +type Action = Box; -use vmm_sys_util::errno::Error as Errno; - -/// The type of epoll events we can monitor a file descriptor for. -pub use vmm_sys_util::epoll::EventSet; - -mod epoll; -mod events; -mod manager; -mod subscribers; -#[doc(hidden)] -#[cfg(feature = "test_utilities")] -pub mod utilities; - -pub use events::{EventOps, Events}; -pub use manager::{EventManager, MAX_READY_EVENTS_CAPACITY}; - -#[cfg(feature = "remote_endpoint")] -mod endpoint; -#[cfg(feature = "remote_endpoint")] -pub use endpoint::RemoteEndpoint; - -/// Error conditions that may appear during `EventManager` related operations. -#[derive(Debug, Eq, PartialEq)] -pub enum Error { - #[cfg(feature = "remote_endpoint")] - /// Cannot send message on channel. - ChannelSend, - #[cfg(feature = "remote_endpoint")] - /// Cannot receive message on channel. - ChannelRecv, - #[cfg(feature = "remote_endpoint")] - /// Operation on `eventfd` failed. - EventFd(Errno), - /// Operation on `libc::epoll` failed. - Epoll(Errno), - // TODO: should we allow fds to be registered multiple times? - /// The fd is already associated with an existing subscriber. - FdAlreadyRegistered, - /// The Subscriber ID does not exist or is no longer associated with a Subscriber. - InvalidId, - /// The ready list capacity passed to `EventManager::new` is invalid. - InvalidCapacity, -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - #[cfg(feature = "remote_endpoint")] - Error::ChannelSend => write!( - f, - "event_manager: failed to send message to remote endpoint" - ), - #[cfg(feature = "remote_endpoint")] - Error::ChannelRecv => write!( - f, - "event_manager: failed to receive message from remote endpoint" - ), - #[cfg(feature = "remote_endpoint")] - Error::EventFd(e) => write!( - f, - "event_manager: failed to manage EventFd file descriptor: {}", - e - ), - Error::Epoll(e) => write!( - f, - "event_manager: failed to manage epoll file descriptor: {}", - e - ), - Error::FdAlreadyRegistered => write!( - f, - "event_manager: file descriptor has already been registered" - ), - Error::InvalidId => write!(f, "event_manager: invalid subscriber Id"), - Error::InvalidCapacity => write!(f, "event_manager: invalid ready_list capacity"), - } - } +fn errno() -> i32 { + // SAFETY: Always safe. + unsafe { *libc::__errno_location() } } -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - #[cfg(feature = "remote_endpoint")] - Error::ChannelSend => None, - #[cfg(feature = "remote_endpoint")] - Error::ChannelRecv => None, - #[cfg(feature = "remote_endpoint")] - Error::EventFd(e) => Some(e), - Error::Epoll(e) => Some(e), - Error::FdAlreadyRegistered => None, - Error::InvalidId => None, - Error::InvalidCapacity => None, - } - } +pub struct BufferedEventManager { + event_manager: EventManager, + // TODO The length is always unused, a custom type could thus save `size_of::()` bytes. + buffer: Vec, } -/// Generic result type that may return `EventManager` errors. -pub type Result = result::Result; - -/// Opaque object that uniquely represents a subscriber registered with an `EventManager`. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -pub struct SubscriberId(u64); - -/// Allows the interaction between an `EventManager` and different event subscribers that do not -/// require a `&mut self` borrow to perform `init` and `process`. -/// -/// Any type implementing this also trivially implements `MutEventSubscriber`. The main role of -/// `EventSubscriber` is to allow wrappers such as `Arc` and `Rc` to implement `EventSubscriber` -/// themselves when the inner type is also an implementor. -pub trait EventSubscriber { - /// Process `events` triggered in the event manager loop. +impl BufferedEventManager { + /// Add an entry to the interest list of the epoll file descriptor. /// - /// Optionally, the subscriber can use `ops` to update the events it monitors. - fn process(&self, events: Events, ops: &mut EventOps); - - /// Initialization called by the [EventManager](struct.EventManager.html) when the subscriber - /// is registered. + /// # Errors /// - /// The subscriber is expected to use `ops` to register the events it wants to monitor. - fn init(&self, ops: &mut EventOps); -} + /// When [`libc::epoll_ctl`] returns `-1`. + pub fn add(&mut self, fd: T, events: EventSet, f: Action) -> Result<(), i32> { + let res = self.event_manager.add(fd, events, f); + self.buffer.reserve(self.event_manager.events.len()); + res + } -/// Allows the interaction between an `EventManager` and different event subscribers. Methods -/// are invoked with a mutable `self` borrow. -pub trait MutEventSubscriber { - /// Process `events` triggered in the event manager loop. + /// Remove (deregister) the target file descriptor fd from the interest list. /// - /// Optionally, the subscriber can use `ops` to update the events it monitors. - fn process(&mut self, events: Events, ops: &mut EventOps); - - /// Initialization called by the [EventManager](struct.EventManager.html) when the subscriber - /// is registered. + /// Returns `Ok(true)` when the given `fd` was present and `Ok(false)` when it wasn't. /// - /// The subscriber is expected to use `ops` to register the events it wants to monitor. - fn init(&mut self, ops: &mut EventOps); -} - -/// API that allows users to add, remove, and interact with registered subscribers. -pub trait SubscriberOps { - /// Subscriber type for which the operations apply. - type Subscriber: MutEventSubscriber; + /// # Errors + /// + /// When [`libc::epoll_ctl`] returns `-1`. + pub fn del(&mut self, fd: T) -> Result { + self.event_manager.del(fd) + } - /// Registers a new subscriber and returns the ID associated with it. + /// Waits until an event fires then triggers the respective action returning `Ok(x)`. If + /// timeout is `Some(_)` it may also return after the given number of milliseconds with + /// `Ok(0)`. /// - /// # Panics + /// # Errors /// - /// This function might panic if the subscriber is already registered. Whether a panic - /// is triggered depends on the implementation of - /// [Subscriber::init()](trait.EventSubscriber.html#tymethod.init). + /// When [`libc::epoll_wait`] returns `-1`. /// - /// Typically, in the `init` function, the subscriber adds fds to its interest list. The same - /// fd cannot be added twice and the `EventManager` will return - /// [Error::FdAlreadyRegistered](enum.Error.html). Using `unwrap` in init in this situation - /// triggers a panic. - fn add_subscriber(&mut self, subscriber: Self::Subscriber) -> SubscriberId; - - /// Removes the subscriber corresponding to `subscriber_id` from the watch list. - fn remove_subscriber(&mut self, subscriber_id: SubscriberId) -> Result; - - /// Returns a mutable reference to the subscriber corresponding to `subscriber_id`. - fn subscriber_mut(&mut self, subscriber_id: SubscriberId) -> Result<&mut Self::Subscriber>; - - /// Creates an event operations wrapper for the subscriber corresponding to `subscriber_id`. + /// # Panics /// - /// The event operations can be used to update the events monitored by the subscriber. - fn event_ops(&mut self, subscriber_id: SubscriberId) -> Result; -} - -impl EventSubscriber for Arc { - fn process(&self, events: Events, ops: &mut EventOps) { - self.deref().process(events, ops); - } - - fn init(&self, ops: &mut EventOps) { - self.deref().init(ops); - } -} - -impl MutEventSubscriber for Arc { - fn process(&mut self, events: Events, ops: &mut EventOps) { - self.deref().process(events, ops); - } - - fn init(&mut self, ops: &mut EventOps) { - self.deref().init(ops); + /// When the value given in timeout does not fit within an `i32` e.g. + /// `timeout.map(|u| i32::try_from(u).unwrap())`. + pub fn wait(&mut self, timeout: Option) -> Result { + unsafe { + self.buffer.set_len(self.buffer.capacity()); + } + self.event_manager.wait(timeout, &mut self.buffer) } -} -impl EventSubscriber for Rc { - fn process(&self, events: Events, ops: &mut EventOps) { - self.deref().process(events, ops); + /// Creates new event manager. + /// + /// # Errors + /// + /// When [`libc::epoll_create1`] returns `-1`. + pub fn new(close_exec: bool) -> Result { + Ok(BufferedEventManager { + event_manager: EventManager::new(close_exec)?, + buffer: Vec::new(), + }) } - - fn init(&self, ops: &mut EventOps) { - self.deref().init(ops); + pub fn with_capacity(close_exec: bool, capacity: usize) -> Result { + Ok(BufferedEventManager { + event_manager: EventManager::new(close_exec)?, + buffer: Vec::with_capacity(capacity), + }) } } -impl MutEventSubscriber for Rc { - fn process(&mut self, events: Events, ops: &mut EventOps) { - self.deref().process(events, ops); - } - - fn init(&mut self, ops: &mut EventOps) { - self.deref().init(ops); +impl Default for BufferedEventManager { + fn default() -> Self { + Self::new(false).unwrap() } } -impl EventSubscriber for RefCell { - fn process(&self, events: Events, ops: &mut EventOps) { - self.borrow_mut().process(events, ops); - } - - fn init(&self, ops: &mut EventOps) { - self.borrow_mut().init(ops); - } -} - -impl MutEventSubscriber for RefCell { - fn process(&mut self, events: Events, ops: &mut EventOps) { - self.borrow_mut().process(events, ops); - } - - fn init(&mut self, ops: &mut EventOps) { - self.borrow_mut().init(ops); - } +pub struct EventManager { + epfd: RawFd, + events: HashMap, } -impl EventSubscriber for Mutex { - fn process(&self, events: Events, ops: &mut EventOps) { - self.lock().unwrap().process(events, ops); +impl EventManager { + /// Add an entry to the interest list of the epoll file descriptor. + /// + /// # Errors + /// + /// When [`libc::epoll_ctl`] returns `-1`. + pub fn add(&mut self, fd: T, events: EventSet, f: Action) -> Result<(), i32> { + let mut event = libc::epoll_event { + events: events.bits(), + r#u64: u64::try_from(fd.as_raw_fd()).unwrap(), + }; + // SAFETY: Safe when `fd` is a valid file descriptor. + match unsafe { libc::epoll_ctl(self.epfd, libc::EPOLL_CTL_ADD, fd.as_raw_fd(), &mut event) } + { + 0 => { + self.events.insert(fd.as_raw_fd(), f); + Ok(()) + } + -1 => Err(errno()), + _ => unreachable!(), + } } - fn init(&self, ops: &mut EventOps) { - self.lock().unwrap().init(ops); + /// Remove (deregister) the target file descriptor fd from the interest list. + /// + /// Returns `Ok(true)` when the given `fd` was present and `Ok(false)` when it wasn't. + /// + /// # Errors + /// + /// When [`libc::epoll_ctl`] returns `-1`. + pub fn del(&mut self, fd: T) -> Result { + match self.events.remove(&fd.as_raw_fd()) { + Some(_) => { + // SAFETY: Safe when `fd` is a valid file descriptor. + match unsafe { + libc::epoll_ctl( + self.epfd, + libc::EPOLL_CTL_DEL, + fd.as_raw_fd(), + std::ptr::null_mut(), + ) + } { + 0 => Ok(true), + -1 => Err(errno()), + _ => unreachable!(), + } + } + None => Ok(false), + } } -} -impl MutEventSubscriber for Mutex { - fn process(&mut self, events: Events, ops: &mut EventOps) { - // If another user of this mutex panicked while holding the mutex, then - // we terminate the process. - self.get_mut().unwrap().process(events, ops); + /// Waits until an event fires then triggers the respective action returning `Ok(x)`. If + /// timeout is `Some(_)` it may also return after the given number of milliseconds with + /// `Ok(0)`. + /// + /// # Errors + /// + /// When [`libc::epoll_wait`] returns `-1`. + /// + /// # Panics + /// + /// When the value given in timeout does not fit within an `i32` e.g. + /// `timeout.map(|u| i32::try_from(u).unwrap())`. + pub fn wait( + &mut self, + timeout: Option, + buffer: &mut [libc::epoll_event], + ) -> Result { + // SAFETY: Always safe. + match unsafe { + libc::epoll_wait( + self.epfd, + buffer.as_mut_ptr(), + buffer.len().try_into().unwrap(), + timeout.map_or(-1i32, |u| i32::try_from(u).unwrap()), + ) + } { + -1 => Err(errno()), + // SAFETY: `x` elements are initialized by `libc::epoll_wait`. + n @ 0.. => unsafe { + for i in 0..usize::try_from(n).unwrap_unchecked() { + let event = buffer[i]; + // For all events which can fire there exists an entry within `self.events` thus + // it is safe to unwrap here. + let f: *const dyn Fn(&mut EventManager, EventSet) = self + .events + .get(&i32::try_from(event.u64).unwrap_unchecked()) + .unwrap_unchecked(); + (*f)(self, EventSet::from_bits_unchecked(event.events)); + } + Ok(n) + }, + _ => unreachable!(), + } } - fn init(&mut self, ops: &mut EventOps) { - // If another user of this mutex panicked while holding the mutex, then - // we terminate the process. - self.get_mut().unwrap().init(ops); + /// Creates new event manager. + /// + /// # Errors + /// + /// When [`libc::epoll_create1`] returns `-1`. + pub fn new(close_exec: bool) -> Result { + // SAFETY: Always safe. + match unsafe { libc::epoll_create1(if close_exec { libc::EPOLL_CLOEXEC } else { 0 }) } { + -1 => Err(errno()), + epfd => Ok(Self { + epfd, + events: HashMap::new(), + }), + } } } -impl EventSubscriber for Box { - fn process(&self, events: Events, ops: &mut EventOps) { - self.deref().process(events, ops); - } - - fn init(&self, ops: &mut EventOps) { - self.deref().init(ops); +impl Default for EventManager { + fn default() -> Self { + Self::new(false).unwrap() } } -impl MutEventSubscriber for Box { - fn process(&mut self, events: Events, ops: &mut EventOps) { - self.deref_mut().process(events, ops); +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicBool, Ordering}; + + #[test] + fn delete() { + static COUNT: AtomicBool = AtomicBool::new(false); + let mut manager = BufferedEventManager::default(); + // We set value to 1 so it will trigger on a read event. + // SAFETY: Always safe. + let event_fd = unsafe { + let fd = libc::eventfd(1, 0); + assert_ne!(fd, -1); + fd + }; + manager + .add( + event_fd, + EventSet::IN, + // A closure which will flip the atomic boolean then remove the event fd from the + // interest list. + Box::new(move |x: &mut EventManager, _: EventSet| { + // Flips the atomic. + let cur = COUNT.load(Ordering::SeqCst); + COUNT.store(!cur, Ordering::SeqCst); + // Calls `EventManager::del` which removes the target file descriptor fd from + // the interest list of the inner epoll. + x.del(event_fd).unwrap(); + }), + ) + .unwrap(); + + // Assert the initial state of the atomic boolean. + assert!(!COUNT.load(Ordering::SeqCst)); + + // The file descriptor has been pre-armed, this will immediately call the respective + // closure. + assert_eq!(manager.wait(Some(10)), Ok(1)); + // As the closure will flip the atomic boolean we assert it has flipped correctly. + assert!(COUNT.load(Ordering::SeqCst)); + + // At this point we have called the closure, since the closure removes the event fd from the + // interest list of the inner epoll, calling this again should timeout as there are no event + // fd in the inner epolls interest list which could trigger. + assert_eq!(manager.wait(Some(10)), Ok(0)); + // As the `EventManager::wait` should timeout the value of the atomic boolean should not be + // flipped. + assert!(COUNT.load(Ordering::SeqCst)); } - fn init(&mut self, ops: &mut EventOps) { - self.deref_mut().init(ops); + #[test] + fn flip() { + static COUNT: AtomicBool = AtomicBool::new(false); + let mut manager = BufferedEventManager::default(); + // We set value to 1 so it will trigger on a read event. + // SAFETY: Always safe. + let event_fd = unsafe { + let fd = libc::eventfd(1, 0); + assert_ne!(fd, -1); + fd + }; + manager + .add( + event_fd, + EventSet::IN, + Box::new(|_: &mut EventManager, _: EventSet| { + // Flips the atomic. + let cur = COUNT.load(Ordering::SeqCst); + COUNT.store(!cur, Ordering::SeqCst); + }), + ) + .unwrap(); + + // Assert the initial state of the atomic boolean. + assert!(!COUNT.load(Ordering::SeqCst)); + + // As the closure will flip the atomic boolean we assert it has flipped correctly. + assert_eq!(manager.wait(Some(10)), Ok(1)); + // As the closure will flip the atomic boolean we assert it has flipped correctly. + assert!(COUNT.load(Ordering::SeqCst)); + + // The file descriptor has been pre-armed, this will immediately call the respective + // closure. + assert_eq!(manager.wait(Some(10)), Ok(1)); + // As the closure will flip the atomic boolean we assert it has flipped correctly. + assert!(!COUNT.load(Ordering::SeqCst)); } } diff --git a/src/manager.rs b/src/manager.rs deleted file mode 100644 index 99bab03..0000000 --- a/src/manager.rs +++ /dev/null @@ -1,483 +0,0 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -use std::mem::size_of; - -use vmm_sys_util::epoll::EpollEvent; -#[cfg(feature = "remote_endpoint")] -use vmm_sys_util::epoll::{ControlOperation, EventSet}; - -#[cfg(feature = "remote_endpoint")] -use super::endpoint::{EventManagerChannel, RemoteEndpoint}; -use super::epoll::EpollWrapper; -use super::subscribers::Subscribers; -#[cfg(feature = "remote_endpoint")] -use super::Errno; -use super::{Error, EventOps, Events, MutEventSubscriber, Result, SubscriberId, SubscriberOps}; - -/// Allows event subscribers to be registered, connected to the event loop, and later removed. -pub struct EventManager { - subscribers: Subscribers, - epoll_context: EpollWrapper, - - #[cfg(feature = "remote_endpoint")] - channel: EventManagerChannel, -} - -/// Maximum capacity of ready events that can be passed when initializing the `EventManager`. -// This constant is not defined inside the `EventManager` implementation because it would -// make it really weird to use as the `EventManager` uses generics (S: MutEventSubscriber). -// That means that when using this const, you could not write -// EventManager::MAX_READY_EVENTS_CAPACITY because the type `S` could not be inferred. -// -// This value is taken from: https://elixir.bootlin.com/linux/latest/source/fs/eventpoll.c#L101 -pub const MAX_READY_EVENTS_CAPACITY: usize = i32::MAX as usize / size_of::(); - -impl SubscriberOps for EventManager { - type Subscriber = T; - - /// Register a subscriber with the event event_manager and returns the associated ID. - fn add_subscriber(&mut self, subscriber: T) -> SubscriberId { - let subscriber_id = self.subscribers.add(subscriber); - self.subscribers - .get_mut_unchecked(subscriber_id) - // The index is valid because we've just added the subscriber. - .init(&mut self.epoll_context.ops_unchecked(subscriber_id)); - subscriber_id - } - - /// Unregisters and returns the subscriber associated with the provided ID. - fn remove_subscriber(&mut self, subscriber_id: SubscriberId) -> Result { - let subscriber = self - .subscribers - .remove(subscriber_id) - .ok_or(Error::InvalidId)?; - self.epoll_context.remove(subscriber_id); - Ok(subscriber) - } - - /// Return a mutable reference to the subscriber associated with the provided id. - fn subscriber_mut(&mut self, subscriber_id: SubscriberId) -> Result<&mut T> { - if self.subscribers.contains(subscriber_id) { - return Ok(self.subscribers.get_mut_unchecked(subscriber_id)); - } - Err(Error::InvalidId) - } - - /// Returns a `EventOps` object for the subscriber associated with the provided ID. - fn event_ops(&mut self, subscriber_id: SubscriberId) -> Result { - // Check if the subscriber_id is valid. - if self.subscribers.contains(subscriber_id) { - // The index is valid because the result of `find` was not `None`. - return Ok(self.epoll_context.ops_unchecked(subscriber_id)); - } - Err(Error::InvalidId) - } -} - -impl EventManager { - const DEFAULT_READY_EVENTS_CAPACITY: usize = 256; - - /// Create a new `EventManger` object. - pub fn new() -> Result { - Self::new_with_capacity(Self::DEFAULT_READY_EVENTS_CAPACITY) - } - - /// Creates a new `EventManger` object with specified event capacity. - /// - /// # Arguments - /// - /// * `ready_events_capacity`: maximum number of ready events to be - /// processed a single `run`. The maximum value of this - /// parameter is `EventManager::MAX_READY_EVENTS_CAPACITY`. - pub fn new_with_capacity(ready_events_capacity: usize) -> Result { - if ready_events_capacity > MAX_READY_EVENTS_CAPACITY { - return Err(Error::InvalidCapacity); - } - - let manager = EventManager { - subscribers: Subscribers::new(), - epoll_context: EpollWrapper::new(ready_events_capacity)?, - #[cfg(feature = "remote_endpoint")] - channel: EventManagerChannel::new()?, - }; - - #[cfg(feature = "remote_endpoint")] - manager - .epoll_context - .epoll - .ctl( - ControlOperation::Add, - manager.channel.fd(), - EpollEvent::new(EventSet::IN, manager.channel.fd() as u64), - ) - .map_err(|e| Error::Epoll(Errno::from(e)))?; - Ok(manager) - } - - /// Run the event loop blocking until events are triggered or an error is returned. - /// Calls [subscriber.process()](trait.EventSubscriber.html#tymethod.process) for each event. - /// - /// On success, it returns number of dispatched events or 0 when interrupted by a signal. - pub fn run(&mut self) -> Result { - self.run_with_timeout(-1) - } - - /// Wait for events for a maximum timeout of `miliseconds` or until an error is returned. - /// Calls [subscriber.process()](trait.EventSubscriber.html#tymethod.process) for each event. - /// - /// On success, it returns number of dispatched events or 0 when interrupted by a signal. - pub fn run_with_timeout(&mut self, milliseconds: i32) -> Result { - let event_count = self.epoll_context.poll(milliseconds)?; - self.dispatch_events(event_count); - - Ok(event_count) - } - - fn dispatch_events(&mut self, event_count: usize) { - // EpollEvent doesn't implement Eq or PartialEq, so... - let default_event: EpollEvent = EpollEvent::default(); - - // Used to record whether there's an endpoint event that needs to be handled. - #[cfg(feature = "remote_endpoint")] - let mut endpoint_event = None; - - for ev_index in 0..event_count { - let event = self.epoll_context.ready_events[ev_index]; - let fd = event.fd(); - - // Check whether this event has been discarded. - // EpollWrapper::remove_event() discards an IO event by setting it to default value. - if event.events() == default_event.events() && fd == default_event.fd() { - continue; - } - - if let Some(subscriber_id) = self.epoll_context.subscriber_id(fd) { - self.subscribers.get_mut_unchecked(subscriber_id).process( - Events::with_inner(event), - // The `subscriber_id` is valid because we checked it before. - &mut self.epoll_context.ops_unchecked(subscriber_id), - ); - } else { - #[cfg(feature = "remote_endpoint")] - { - // If we got here, there's a chance the event was triggered by the remote - // endpoint fd. Only check for incoming endpoint events right now, and defer - // actually handling them until all subscriber events have been handled first. - // This prevents subscribers whose events are about to be handled from being - // removed by an endpoint request (or other similar situations). - if fd == self.channel.fd() { - endpoint_event = Some(event); - continue; - } - } - - // This should not occur during normal operation. - unreachable!("Received event on fd from subscriber that is not registered"); - } - } - - #[cfg(feature = "remote_endpoint")] - self.dispatch_endpoint_event(endpoint_event); - } -} - -#[cfg(feature = "remote_endpoint")] -impl EventManager { - /// Return a `RemoteEndpoint` object, that allows interacting with the `EventManager` from a - /// different thread. Using `RemoteEndpoint::call_blocking` on the same thread the event loop - /// runs on leads to a deadlock. - pub fn remote_endpoint(&self) -> RemoteEndpoint { - self.channel.remote_endpoint() - } - - fn dispatch_endpoint_event(&mut self, endpoint_event: Option) { - if let Some(event) = endpoint_event { - if event.event_set() != EventSet::IN { - // This situation is virtually impossible to occur. If it does we have - // a programming error in our code. - unreachable!(); - } - self.handle_endpoint_calls(); - } - } - - fn handle_endpoint_calls(&mut self) { - // Clear the inner event_fd. We don't do anything about an error here at this point. - let _ = self.channel.event_fd.read(); - - // Process messages. We consider only `Empty` errors can appear here; we don't check - // for `Disconnected` errors because we keep at least one clone of `channel.sender` alive - // at all times ourselves. - while let Ok(msg) = self.channel.receiver.try_recv() { - match msg.sender { - Some(sender) => { - // We call the inner closure and attempt to send back the result, but can't really do - // anything in case of error here. - let _ = sender.send((msg.fnbox)(self)); - } - None => { - // Just call the function and discard the result. - let _ = (msg.fnbox)(self); - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::super::Error; - use super::*; - - use std::os::unix::io::{AsRawFd, RawFd}; - use std::sync::{Arc, Mutex}; - - use vmm_sys_util::{epoll::EventSet, eventfd::EventFd}; - - struct DummySubscriber { - event_fd_1: EventFd, - event_fd_2: EventFd, - - // Flags used for checking that the event event_manager called the `process` - // function for ev1/ev2. - processed_ev1_out: bool, - processed_ev2_out: bool, - processed_ev1_in: bool, - - // Flags used for driving register/unregister/modify of events from - // outside of the `process` function. - register_ev2: bool, - unregister_ev1: bool, - modify_ev1: bool, - } - - impl DummySubscriber { - fn new() -> Self { - DummySubscriber { - event_fd_1: EventFd::new(0).unwrap(), - event_fd_2: EventFd::new(0).unwrap(), - processed_ev1_out: false, - processed_ev2_out: false, - processed_ev1_in: false, - register_ev2: false, - unregister_ev1: false, - modify_ev1: false, - } - } - } - - impl DummySubscriber { - fn register_ev2(&mut self) { - self.register_ev2 = true; - } - - fn unregister_ev1(&mut self) { - self.unregister_ev1 = true; - } - - fn modify_ev1(&mut self) { - self.modify_ev1 = true; - } - - fn processed_ev1_out(&self) -> bool { - self.processed_ev1_out - } - - fn processed_ev2_out(&self) -> bool { - self.processed_ev2_out - } - - fn processed_ev1_in(&self) -> bool { - self.processed_ev1_in - } - - fn reset_state(&mut self) { - self.processed_ev1_out = false; - self.processed_ev2_out = false; - self.processed_ev1_in = false; - } - - fn handle_updates(&mut self, event_manager: &mut EventOps) { - if self.register_ev2 { - event_manager - .add(Events::new(&self.event_fd_2, EventSet::OUT)) - .unwrap(); - self.register_ev2 = false; - } - - if self.unregister_ev1 { - event_manager - .remove(Events::new_raw( - self.event_fd_1.as_raw_fd(), - EventSet::empty(), - )) - .unwrap(); - self.unregister_ev1 = false; - } - - if self.modify_ev1 { - event_manager - .modify(Events::new(&self.event_fd_1, EventSet::IN)) - .unwrap(); - self.modify_ev1 = false; - } - } - - fn handle_in(&mut self, source: RawFd) { - if self.event_fd_1.as_raw_fd() == source { - self.processed_ev1_in = true; - } - } - - fn handle_out(&mut self, source: RawFd) { - match source { - _ if self.event_fd_1.as_raw_fd() == source => { - self.processed_ev1_out = true; - } - _ if self.event_fd_2.as_raw_fd() == source => { - self.processed_ev2_out = true; - } - _ => {} - } - } - } - - impl MutEventSubscriber for DummySubscriber { - fn process(&mut self, events: Events, ops: &mut EventOps) { - let source = events.fd(); - let event_set = events.event_set(); - - // We only know how to treat EPOLLOUT and EPOLLIN. - // If we received anything else just stop processing the event. - let all_but_in_out = EventSet::all() - EventSet::OUT - EventSet::IN; - if event_set.intersects(all_but_in_out) { - return; - } - - self.handle_updates(ops); - - match event_set { - EventSet::IN => self.handle_in(source), - EventSet::OUT => self.handle_out(source), - _ => {} - } - } - - fn init(&mut self, ops: &mut EventOps) { - let event = Events::new(&self.event_fd_1, EventSet::OUT); - ops.add(event).unwrap(); - } - } - - #[test] - fn test_register() { - use super::SubscriberOps; - - let mut event_manager = EventManager::>>::new().unwrap(); - let dummy_subscriber = Arc::new(Mutex::new(DummySubscriber::new())); - - event_manager.add_subscriber(dummy_subscriber.clone()); - - dummy_subscriber.lock().unwrap().register_ev2(); - - // When running the loop the first time, ev1 should be processed, but ev2 shouldn't - // because it was just added as part of processing ev1. - event_manager.run().unwrap(); - assert!(dummy_subscriber.lock().unwrap().processed_ev1_out()); - assert!(!dummy_subscriber.lock().unwrap().processed_ev2_out()); - - // Check that both ev1 and ev2 are processed. - dummy_subscriber.lock().unwrap().reset_state(); - event_manager.run().unwrap(); - assert!(dummy_subscriber.lock().unwrap().processed_ev1_out()); - assert!(dummy_subscriber.lock().unwrap().processed_ev2_out()); - } - - #[test] - #[should_panic(expected = "FdAlreadyRegistered")] - fn test_add_invalid_subscriber() { - let mut event_manager = EventManager::>>::new().unwrap(); - let subscriber = Arc::new(Mutex::new(DummySubscriber::new())); - - event_manager.add_subscriber(subscriber.clone()); - event_manager.add_subscriber(subscriber); - } - - // Test that unregistering an event while processing another one works. - #[test] - fn test_unregister() { - let mut event_manager = EventManager::>>::new().unwrap(); - let dummy_subscriber = Arc::new(Mutex::new(DummySubscriber::new())); - - event_manager.add_subscriber(dummy_subscriber.clone()); - - // Disable ev1. We should only receive this event once. - dummy_subscriber.lock().unwrap().unregister_ev1(); - - event_manager.run().unwrap(); - assert!(dummy_subscriber.lock().unwrap().processed_ev1_out()); - - dummy_subscriber.lock().unwrap().reset_state(); - - // We expect no events to be available. Let's run with timeout so that run exists. - event_manager.run_with_timeout(100).unwrap(); - assert!(!dummy_subscriber.lock().unwrap().processed_ev1_out()); - } - - #[test] - fn test_modify() { - let mut event_manager = EventManager::>>::new().unwrap(); - let dummy_subscriber = Arc::new(Mutex::new(DummySubscriber::new())); - - event_manager.add_subscriber(dummy_subscriber.clone()); - - // Modify ev1 so that it waits for EPOLL_IN. - dummy_subscriber.lock().unwrap().modify_ev1(); - event_manager.run().unwrap(); - assert!(dummy_subscriber.lock().unwrap().processed_ev1_out()); - assert!(!dummy_subscriber.lock().unwrap().processed_ev2_out()); - - dummy_subscriber.lock().unwrap().reset_state(); - - // Make sure ev1 is ready for IN so that we don't loop forever. - dummy_subscriber - .lock() - .unwrap() - .event_fd_1 - .write(1) - .unwrap(); - - event_manager.run().unwrap(); - assert!(!dummy_subscriber.lock().unwrap().processed_ev1_out()); - assert!(!dummy_subscriber.lock().unwrap().processed_ev2_out()); - assert!(dummy_subscriber.lock().unwrap().processed_ev1_in()); - } - - #[test] - fn test_remove_subscriber() { - let mut event_manager = EventManager::>>::new().unwrap(); - let dummy_subscriber = Arc::new(Mutex::new(DummySubscriber::new())); - - let subscriber_id = event_manager.add_subscriber(dummy_subscriber.clone()); - event_manager.run().unwrap(); - assert!(dummy_subscriber.lock().unwrap().processed_ev1_out()); - - dummy_subscriber.lock().unwrap().reset_state(); - - event_manager.remove_subscriber(subscriber_id).unwrap(); - - // We expect no events to be available. Let's run with timeout so that run exits. - event_manager.run_with_timeout(100).unwrap(); - assert!(!dummy_subscriber.lock().unwrap().processed_ev1_out()); - - // Removing the subscriber twice should return an error. - assert_eq!( - event_manager - .remove_subscriber(subscriber_id) - .err() - .unwrap(), - Error::InvalidId - ); - } -} diff --git a/src/subscribers.rs b/src/subscribers.rs deleted file mode 100644 index fb88aa1..0000000 --- a/src/subscribers.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -use super::SubscriberId; -use std::collections::HashMap; - -// Internal structure used to keep the set of subscribers registered with an EventManger. -// This structure is a thin wrapper over a `HashMap` in which the keys are uniquely -// generated when calling `add`. -pub(crate) struct Subscribers { - // The key is the unique id of the subscriber and the entry is the `Subscriber`. - subscribers: HashMap, - // We are generating the unique ids by incrementing this counter for each added subscriber, - // and rely on the large value range of u64 to ensure each value is effectively - // unique over the runtime of any VMM. - next_id: u64, -} - -impl Subscribers { - pub(crate) fn new() -> Self { - Subscribers { - subscribers: HashMap::new(), - next_id: 1, - } - } - - // Adds a subscriber and generates an unique id to represent it. - pub(crate) fn add(&mut self, subscriber: T) -> SubscriberId { - let id = SubscriberId(self.next_id); - self.next_id += 1; - - self.subscribers.insert(id, subscriber); - - id - } - - // Remove and return the subscriber associated with the given id, if it exists. - pub(crate) fn remove(&mut self, subscriber_id: SubscriberId) -> Option { - self.subscribers.remove(&subscriber_id) - } - - // Checks whether a subscriber with `subscriber_id` is registered. - pub(crate) fn contains(&mut self, subscriber_id: SubscriberId) -> bool { - self.subscribers.contains_key(&subscriber_id) - } - - // Return a mutable reference to the subriber represented by `subscriber_id`. - // - // This method should only be called for indices that are known to be valid, otherwise - // panics can occur. - pub(crate) fn get_mut_unchecked(&mut self, subscriber_id: SubscriberId) -> &mut T { - self.subscribers.get_mut(&subscriber_id).unwrap() - } -} diff --git a/src/utilities/mod.rs b/src/utilities/mod.rs deleted file mode 100644 index c2d53d1..0000000 --- a/src/utilities/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Helper module for tests utilities. -// -// This module is only compiled with `test_utilities` feature on purpose. -// For production code, we do not want to export this functionality. -// At the same time, we need the test utilities to be public such that they can -// be used by multiple categories of tests. Two examples that deem this module -// necessary are the benchmark tests and the integration tests where the implementations -// of subscribers are shared. -// -// Having this module as a feature comes with a disadvantage that needs to be kept in mind. -// `cargo test` will only work when ran with `--feature test-utilities` (or with --all-features). A -// much nicer way to implement this would've been with a utilities crate that is used as -// `dev-dependencies`. Unfortunately, this is not possible because it would introduce a cyclic -// dependency. The `utilities` module has a dependency on `event-manager` because it needs to -// implement the `EventSubscriber` traits, and `event-manager` has a dependency on utilities so -// that they can be used in tests. -#![doc(hidden)] -pub mod subscribers; diff --git a/src/utilities/subscribers.rs b/src/utilities/subscribers.rs deleted file mode 100644 index 95ad790..0000000 --- a/src/utilities/subscribers.rs +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause -// -// Cargo thinks that some of the methods in this module are not used because -// not all of them are used in all the separate integration test modules. -// Cargo bug: https://github.com/rust-lang/rust/issues/46379 -// Let's allow dead code so that we don't get warnings all the time. -/// This module defines common subscribers that showcase the usage of the event-manager. -/// -/// 1. CounterSubscriber: -/// - a dummy subscriber that increments a counter on event -/// - only uses one event -/// - it has to be explicitly mutated; for this reason it implements `MutEventSubscriber`. -/// -/// 2. CounterSubscriberWithData: -/// - a dummy subscriber that increments a counter on events -/// - this subscriber takes care of multiple events and makes use of `Events::with_data` so -/// that in the `process` function it identifies the trigger of an event using the data -/// instead of the file descriptor -/// - it has to be explicitly mutated; for this reason it implements `MutEventSubscriber`. -/// -/// 3. CounterInnerMutSubscriber: -/// - a dummy subscriber that increments a counter on events -/// - the subscriber makes use of inner mutability; multi-threaded applications might want to -/// use inner mutability instead of having something heavy weight (i.e. Arc). -/// - this subscriber implement `EventSubscriber`. -use std::fmt::{Display, Formatter, Result}; -use std::os::unix::io::AsRawFd; -use std::sync::atomic::AtomicU64; -use std::sync::atomic::Ordering; - -use vmm_sys_util::{epoll::EventSet, eventfd::EventFd}; - -use crate::{EventOps, EventSubscriber, Events, MutEventSubscriber}; - -/// A `Counter` is a helper structure for creating subscribers that increment a value -/// each time an event is triggered. -/// The `Counter` allows users to assert and de-assert an event, and to query the counter value. -pub struct Counter { - event_fd: EventFd, - counter: u64, -} - -impl Display for Counter { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - write!( - f, - "(event_fd = {}, counter = {})", - self.event_fd.as_raw_fd(), - self.counter - ) - } -} - -impl Counter { - pub fn new() -> Self { - Self { - event_fd: EventFd::new(0).unwrap(), - counter: 0, - } - } - - pub fn trigger_event(&mut self) { - let _ = self.event_fd.write(1); - } - - pub fn clear_event(&self) { - let _ = self.event_fd.read(); - } - - pub fn counter(&self) -> u64 { - self.counter - } -} - -impl Default for Counter { - fn default() -> Self { - Self::new() - } -} - -// A dummy subscriber that increments a counter whenever it processes -// a new request. -pub struct CounterSubscriber(Counter); - -impl std::ops::Deref for CounterSubscriber { - type Target = Counter; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::ops::DerefMut for CounterSubscriber { - fn deref_mut(&mut self) -> &mut Counter { - &mut self.0 - } -} - -impl Default for CounterSubscriber { - fn default() -> Self { - Self(Counter::new()) - } -} - -impl MutEventSubscriber for CounterSubscriber { - fn process(&mut self, events: Events, event_ops: &mut EventOps) { - match events.event_set() { - EventSet::IN => { - self.counter += 1; - } - EventSet::ERROR => { - eprintln!("Got error on the monitored event."); - } - EventSet::HANG_UP => { - event_ops - .remove(events) - .expect("Encountered error during cleanup."); - panic!("Cannot continue execution. Associated fd was closed."); - } - _ => { - eprintln!( - "Received spurious event from the event manager {:#?}.", - events.event_set() - ); - } - } - } - - fn init(&mut self, ops: &mut EventOps) { - ops.add(Events::new(&self.event_fd, EventSet::IN)) - .expect("Cannot register event."); - } -} - -// A dummy subscriber that makes use of the optional data in `Events` when -// registering & processing events. -// Using 3 counters each having associated event data to showcase the implementation of -// EventSubscriber trait with this scenario. -pub struct CounterSubscriberWithData { - counter_1: Counter, - counter_2: Counter, - counter_3: Counter, - - first_data: u32, - toggle_registry: bool, -} - -impl CounterSubscriberWithData { - // `first_data` represents the first event data that can be used by this subscriber. - pub fn new(first_data: u32) -> Self { - Self { - counter_1: Counter::new(), - counter_2: Counter::new(), - counter_3: Counter::new(), - // Using consecutive numbers for the event data helps the compiler to optimize - // match statements on counter_1_data, counter_2_data, counter_3_data using - // a jump table. - first_data, - toggle_registry: false, - } - } - - pub fn trigger_all_counters(&mut self) { - self.counter_1.trigger_event(); - self.counter_2.trigger_event(); - self.counter_3.trigger_event(); - } - - pub fn get_all_counter_values(&self) -> Vec { - vec![ - self.counter_1.counter(), - self.counter_2.counter(), - self.counter_3.counter(), - ] - } - - pub fn set_toggle_registry(&mut self, toggle: bool) { - self.toggle_registry = toggle; - } -} - -impl MutEventSubscriber for CounterSubscriberWithData { - fn process(&mut self, events: Events, ops: &mut EventOps) { - if self.toggle_registry { - self.toggle_registry = false; - - ops.remove(Events::with_data( - &self.counter_1.event_fd, - self.first_data, - EventSet::IN, - )) - .expect("Cannot remove event."); - ops.remove(Events::with_data( - &self.counter_2.event_fd, - self.first_data + 1, - EventSet::IN, - )) - .expect("Cannot remove event."); - ops.remove(Events::with_data( - &self.counter_3.event_fd, - self.first_data + 2, - EventSet::IN, - )) - .expect("Cannot remove event."); - - ops.add(Events::with_data( - &self.counter_1.event_fd, - self.first_data, - EventSet::IN, - )) - .expect("Cannot register event."); - ops.add(Events::with_data( - &self.counter_2.event_fd, - self.first_data + 1, - EventSet::IN, - )) - .expect("Cannot register event."); - ops.add(Events::with_data( - &self.counter_3.event_fd, - self.first_data + 2, - EventSet::IN, - )) - .expect("Cannot register event."); - } - match events.event_set() { - EventSet::IN => { - let event_id = events.data() - self.first_data; - match event_id { - 0 => { - self.counter_1.counter += 1; - } - 1 => { - self.counter_2.counter += 1; - } - 2 => { - self.counter_3.counter += 1; - } - _ => { - eprintln!("Received spurious event."); - } - }; - } - EventSet::ERROR => { - eprintln!("Got error on the monitored event."); - } - EventSet::HANG_UP => { - ops.remove(events) - .expect("Encountered error during cleanup."); - panic!("Cannot continue execution. Associated fd was closed."); - } - _ => {} - } - } - - fn init(&mut self, ops: &mut EventOps) { - ops.add(Events::with_data( - &self.counter_1.event_fd, - self.first_data, - EventSet::IN, - )) - .expect("Cannot register event."); - ops.add(Events::with_data( - &self.counter_2.event_fd, - self.first_data + 1, - EventSet::IN, - )) - .expect("Cannot register event."); - ops.add(Events::with_data( - &self.counter_3.event_fd, - self.first_data + 2, - EventSet::IN, - )) - .expect("Cannot register event."); - } -} - -pub struct CounterInnerMutSubscriber { - event_fd: EventFd, - counter: AtomicU64, -} - -impl Default for CounterInnerMutSubscriber { - fn default() -> Self { - Self { - event_fd: EventFd::new(0).unwrap(), - counter: AtomicU64::new(0), - } - } -} - -impl CounterInnerMutSubscriber { - pub fn trigger_event(&self) { - let _ = self.event_fd.write(1); - } - - pub fn clear_event(&self) { - let _ = self.event_fd.read(); - } - - pub fn counter(&self) -> u64 { - self.counter.load(Ordering::Relaxed) - } -} - -impl EventSubscriber for CounterInnerMutSubscriber { - fn process(&self, events: Events, ops: &mut EventOps) { - match events.event_set() { - EventSet::IN => { - self.counter.fetch_add(1, Ordering::Relaxed); - } - EventSet::ERROR => { - eprintln!("Got error on the monitored event."); - } - EventSet::HANG_UP => { - ops.remove(events) - .expect("Encountered error during cleanup."); - panic!("Cannot continue execution. Associated fd was closed."); - } - _ => {} - } - } - - fn init(&self, ops: &mut EventOps) { - ops.add(Events::new(&self.event_fd, EventSet::IN)) - .expect("Cannot register event."); - } -} diff --git a/tests/basic_event_manager.rs b/tests/basic_event_manager.rs deleted file mode 100644 index 8930ebf..0000000 --- a/tests/basic_event_manager.rs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause -// -// A basic, single threaded application that only needs one type of -// subscriber: `CounterSubscriber`. -// -// The application has an `EventManager` and can register multiple subscribers -// of type `CounterSubscriber`. - -use std::ops::Drop; - -use event_manager::utilities::subscribers::CounterSubscriber; -use event_manager::{EventManager, SubscriberId, SubscriberOps}; - -struct App { - event_manager: EventManager, - subscribers_id: Vec, -} - -impl App { - fn new() -> Self { - Self { - event_manager: EventManager::::new().unwrap(), - subscribers_id: vec![], - } - } - - fn add_subscriber(&mut self) { - let counter_subscriber = CounterSubscriber::default(); - let id = self.event_manager.add_subscriber(counter_subscriber); - self.subscribers_id.push(id); - } - - fn run(&mut self) { - let _ = self.event_manager.run_with_timeout(100); - } - - fn inject_events_for(&mut self, subscriber_index: &[usize]) { - for i in subscriber_index { - let subscriber = self - .event_manager - .subscriber_mut(self.subscribers_id[*i]) - .unwrap(); - subscriber.trigger_event(); - } - } - - fn clear_events_for(&mut self, subscriber_indices: &[usize]) { - for i in subscriber_indices { - let subscriber = self - .event_manager - .subscriber_mut(self.subscribers_id[*i]) - .unwrap(); - subscriber.clear_event(); - } - } - - fn get_counters(&mut self) -> Vec { - let mut result = Vec::::new(); - for subscriber_id in &self.subscribers_id { - let subscriber = self.event_manager.subscriber_mut(*subscriber_id).unwrap(); - result.push(subscriber.counter()); - } - - result - } - - // Whenever the App does not need to monitor events anymore, it should explicitly call - // the `remove_subscriber` function. - fn cleanup(&mut self) { - for id in &self.subscribers_id { - let _ = self.event_manager.remove_subscriber(*id); - } - } -} - -impl Drop for App { - fn drop(&mut self) { - self.cleanup(); - } -} - -#[test] -fn test_single_threaded() { - let mut app = App::new(); - for _ in 0..100 { - app.add_subscriber(); - } - - // Random subscribers, in the sense that I randomly picked those numbers :) - let triggered_subscribers: Vec = vec![1, 3, 50, 97]; - app.inject_events_for(&triggered_subscribers); - app.run(); - - let counters = app.get_counters(); - for (i, &item) in counters.iter().enumerate() { - assert_eq!(item, 1 & (triggered_subscribers.contains(&i) as u64)); - } - - app.clear_events_for(&triggered_subscribers); - app.run(); - let counters = app.get_counters(); - for (i, &item) in counters.iter().enumerate() { - assert_eq!(item, 1 & (triggered_subscribers.contains(&i) as u64)); - } - - // Once the app does not need events anymore, the cleanup needs to be called. - // This is particularly important when the app continues the execution, but event monitoring - // is not necessary. - app.cleanup(); -} diff --git a/tests/endpoint.rs b/tests/endpoint.rs deleted file mode 100644 index 61a18a1..0000000 --- a/tests/endpoint.rs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -#![cfg(feature = "remote_endpoint")] - -use std::any::Any; -use std::result; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::Arc; -use std::thread; -use std::time::Duration; - -use event_manager::utilities::subscribers::CounterSubscriber; -use event_manager::{self, EventManager, MutEventSubscriber, SubscriberId}; - -trait GenericSubscriber: MutEventSubscriber { - fn as_mut_any(&mut self) -> &mut dyn Any; -} - -impl GenericSubscriber for CounterSubscriber { - fn as_mut_any(&mut self) -> &mut dyn Any { - self - } -} - -// This test showcases the use of the three `RemoteEndpoint` methods: `call_blocking`, `fire`, -// and `kick`. The setting is we're moving an `EventManager` to a separate thread, and we want -// to register subscribers that remain fully owned by the manager (so, for example, it's not -// necessary to wrap them in a `Mutex` if event handling needs mutable access to their inner -// state). We also use the `GenericSubscriber` trait defined above to show how we can still -// obtain and interact with references to the actual types when event subscribers are registered -// as trait objects. -#[test] -fn test_endpoint() { - let mut event_manager = EventManager::>::new().unwrap(); - let endpoint = event_manager.remote_endpoint(); - let sub = Box::::default(); - - let run_count = Arc::new(AtomicU64::new(0)); - let keep_running = Arc::new(AtomicBool::new(true)); - - // These get moved into the following closure. - let run_count_clone = run_count.clone(); - let keep_running_clone = keep_running.clone(); - - // We spawn the thread that runs the event loop. - let thread_handle = thread::spawn(move || { - loop { - // The manager gets moved to the new thread here. - event_manager.run().unwrap(); - // Increment this counter every time the `run` method call above returns. Ordering - // is not that important here because the other thread only reads the value. - run_count_clone.fetch_add(1, Ordering::Relaxed); - - // When `keep_running` is no longer `true`, we break the loop and the - // thread will exit. - if !keep_running_clone.load(Ordering::Acquire) { - break; - } - } - }); - - // We're back to the main program thread from now on. - - // `run_count` should not change for now as there's no possible activity for the manager. - thread::sleep(Duration::from_millis(100)); - assert_eq!(run_count.load(Ordering::Relaxed), 0); - - // Use `call_blocking` to register a subscriber (and move it to the event loop thread under - // the ownership of the manager). `call_block` also allows us to inspect the result of the - // operation, but it blocks waiting for it and cannot be invoked from the same thread as - // the event loop. - let sub_id = endpoint - .call_blocking( - |sub_ops| -> result::Result { - Ok(sub_ops.add_subscriber(sub)) - }, - ) - .unwrap(); - - // We've added a subscriber. No subscriber events are fired yet, but the manager run - // loop went through one iteration when the endpoint message was received, so `run_count` - // has benn incremented. - thread::sleep(Duration::from_millis(100)); - assert_eq!(run_count.load(Ordering::Relaxed), 1); - - // Now let's activate the subscriber event. It's going to generate continuous activity until - // we explicitly clear it. We use `endpoint` to interact with the subscriber, because the - // latter is fully owned by the manager. Also, we make use of the `as_mut_any` method - // from our `GenericSubscriber` trait to get a reference to the actual subscriber type - // (which has been erased as a trait object from the manager's perspective). We use `fire` - // here, so we don't get a result. - // - // `fire` can also be used from the same thread as the `event_manager` runs on without causing - // a deadlock, because it doesn't get a result from the closure. For example, we can pass an - // endpoint to a subscriber, and use `fire` as part of `process` if it's helpful for that - // particular use case. - // - // Not getting a result from the closure means we have to deal with error conditions within. - // We use `unwrap` here, but that's ok because if the subscriber associated with `sub_id` is - // not present, then we have a serious error in our program logic. - endpoint - .fire(move |sub_ops| { - let sub = sub_ops.subscriber_mut(sub_id).unwrap(); - // The following `unwrap` cannot fail because we know the type is `CounterSubscriber`. - sub.as_mut_any() - .downcast_mut::() - .unwrap() - .trigger_event() - }) - .unwrap(); - - // The event will start triggering at this point, so `run_count` will increase. - thread::sleep(Duration::from_millis(100)); - assert!(run_count.load(Ordering::Relaxed) > 1); - - // Let's clear the subscriber event. Using `fire` again. - endpoint - .fire(move |sub_ops| { - let sub = sub_ops.subscriber_mut(sub_id).unwrap(); - // The following `unwrap` cannot fail because we know the actual type - // is `CounterSubscriber`. - sub.as_mut_any() - .downcast_mut::() - .unwrap() - .clear_event() - }) - .unwrap(); - - // We wait a bit more. The manager will be once more become blocked waiting for events. - thread::sleep(Duration::from_millis(100)); - - keep_running.store(false, Ordering::Release); - - // Trying to `join` the manager here would lead to a deadlock, because it will never read - // the value of `keep_running` to break the loop while stuck waiting for events. We use the - // `kick` endpoint method to force `EventManager::run()` to return. - - endpoint.kick().unwrap(); - - // We can `join` the manager thread and finalize now. - thread_handle.join().unwrap(); -} diff --git a/tests/multi_threaded.rs b/tests/multi_threaded.rs deleted file mode 100644 index 8f1d653..0000000 --- a/tests/multi_threaded.rs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -use std::sync::{Arc, Mutex}; -use std::thread; - -use event_manager::utilities::subscribers::{ - CounterInnerMutSubscriber, CounterSubscriber, CounterSubscriberWithData, -}; -use event_manager::{EventManager, EventSubscriber, MutEventSubscriber, SubscriberOps}; - -// Showcase how you can manage subscribers of different types using the same event manager -// in a multithreaded context. -#[test] -fn test_diff_type_subscribers() { - let mut event_manager = EventManager::>>::new() - .expect("Cannot create event manager."); - - // The `CounterSubscriberWithData` expects to receive a number as a parameter that represents - // the number it can use as its inner Events data. - let data_subscriber = Arc::new(Mutex::new(CounterSubscriberWithData::new(1))); - data_subscriber.lock().unwrap().trigger_all_counters(); - - let counter_subscriber = Arc::new(Mutex::new(CounterSubscriber::default())); - counter_subscriber.lock().unwrap().trigger_event(); - - // Saving the ids allows to modify the subscribers in the event manager context (i.e. removing - // the subscribers from the event manager loop). - let ds_id = event_manager.add_subscriber(data_subscriber); - let cs_id = event_manager.add_subscriber(counter_subscriber); - - let thread_handle = thread::spawn(move || { - // In a typical application this would be an infinite loop with some break condition. - // For tests, 100 iterations should suffice. - for _ in 0..100 { - // When the event manager loop exits, it will return the number of events it - // handled. - let ev_count = event_manager.run().unwrap(); - // We are expecting the ev_count to be: - // 4 = 3 (events triggered from data_subscriber) + 1 (event from counter_subscriber). - assert_eq!(ev_count, 4); - } - - // One really important thing is to always remove the subscribers once you don't need them - // any more. - let _ = event_manager.remove_subscriber(ds_id).unwrap(); - let _ = event_manager.remove_subscriber(cs_id).unwrap(); - }); - thread_handle.join().unwrap(); -} - -// Showcase how you can manage a single type of subscriber using the same event manager -// in a multithreaded context. -#[test] -fn test_one_type_subscriber() { - let mut event_manager = - EventManager::::new().expect("Cannot create event manager"); - - // The `CounterSubscriberWithData` expects to receive the number it can use as its inner - // `Events` data as a parameter. - // Let's make sure that the inner data of the two subscribers will not overlap by keeping some - // numbers between the first_data of each subscriber. - let data_subscriber_1 = CounterSubscriberWithData::new(1); - let data_subscriber_2 = CounterSubscriberWithData::new(1999); - - // Saving the ids allows to modify the subscribers in the event manager context (i.e. removing - // the subscribers from the event manager loop). - let ds_id_1 = event_manager.add_subscriber(data_subscriber_1); - let ds_id_2 = event_manager.add_subscriber(data_subscriber_2); - - // Since we moved the ownership of the subscriber to the event manager, now we need to get - // a mutable reference from it so that we can modify them. - // Enclosing this in a code block so that the references are dropped when they're no longer - // needed. - { - let data_subscriber_1 = event_manager.subscriber_mut(ds_id_1).unwrap(); - data_subscriber_1.trigger_all_counters(); - - let data_subscriber_2 = event_manager.subscriber_mut(ds_id_2).unwrap(); - data_subscriber_2.trigger_all_counters(); - } - - let thread_handle = thread::spawn(move || { - // In a typical application this would be an infinite loop with some break condition. - // For tests, 100 iterations should suffice. - for _ in 0..100 { - // When the event manager loop exits, it will return the number of events it - // handled. - let ev_count = event_manager.run().unwrap(); - // Since we triggered all counters, we're expecting the number of events to always be - // 6 (3 from one subscriber and 3 from the other). - assert_eq!(ev_count, 6); - } - - // One really important thing is to always remove the subscribers once you don't need them - // any more. - // When calling remove_subscriber, you receive ownership of the subscriber object. - let data_subscriber_1 = event_manager.remove_subscriber(ds_id_1).unwrap(); - let data_subscriber_2 = event_manager.remove_subscriber(ds_id_2).unwrap(); - - // Let's check how the subscribers look after 100 iterations. - // Each subscriber's counter start from 0. When an event is triggered, each counter - // is incremented. So after 100 iterations when they're all triggered, we expect them - // to be 100. - let expected_subscriber_counters = vec![100, 100, 100]; - assert_eq!( - data_subscriber_1.get_all_counter_values(), - expected_subscriber_counters - ); - assert_eq!( - data_subscriber_2.get_all_counter_values(), - expected_subscriber_counters - ); - }); - - thread_handle.join().unwrap(); -} - -// Showcase how you can manage subscribers that make use of inner mutability using the event manager -// in a multithreaded context. -#[test] -fn test_subscriber_inner_mut() { - // We are using just the `CounterInnerMutSubscriber` subscriber, so we could've initialize - // the event manager as: EventManager::::new(). - // The typical application will have more than one subscriber type, so let's just pretend - // this is the case in this example as well. - let mut event_manager = EventManager::>::new() - .expect("Cannot create event manager"); - - let subscriber = Arc::new(CounterInnerMutSubscriber::default()); - subscriber.trigger_event(); - - // Let's just clone the subscriber before adding it to the event manager. This will allow us - // to use it from this thread without the need to call into EventManager::subscriber_mut(). - let subscriber_id = event_manager.add_subscriber(subscriber.clone()); - - let thread_handle = thread::spawn(move || { - for _ in 0..100 { - // When the event manager loop exits, it will return the number of events it - // handled. - let ev_count = event_manager.run().unwrap(); - assert_eq!(ev_count, 1); - } - - assert!(event_manager.remove_subscriber(subscriber_id).is_ok()); - }); - thread_handle.join().unwrap(); - - assert_eq!(subscriber.counter(), 100); -} diff --git a/tests/negative_tests.rs b/tests/negative_tests.rs deleted file mode 100644 index ec53ae9..0000000 --- a/tests/negative_tests.rs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -use std::os::unix::{io::AsRawFd, net::UnixStream}; -use std::sync::{ - atomic::{AtomicU64, Ordering}, - Arc, -}; - -use event_manager::{ - EventManager, EventOps, EventSubscriber, Events, SubscriberOps, MAX_READY_EVENTS_CAPACITY, -}; -use vmm_sys_util::epoll::EventSet; - -#[derive(Debug)] -struct UnixStreamSubscriber { - stream: UnixStream, - rhup_count: AtomicU64, - // When this flag is used, in the process function the subscriber will - // unregister the fd where an error was received. - // The errors that need to be handled: `EventSet::HANG_UP`, `EventSet::ERROR`. - with_unregister_on_err: bool, -} - -impl UnixStreamSubscriber { - fn new(stream: UnixStream) -> UnixStreamSubscriber { - Self { - stream, - rhup_count: AtomicU64::new(0), - with_unregister_on_err: false, - } - } - - fn new_with_unregister_on_err(stream: UnixStream) -> UnixStreamSubscriber { - Self { - stream, - rhup_count: AtomicU64::new(0), - with_unregister_on_err: true, - } - } -} - -impl EventSubscriber for UnixStreamSubscriber { - fn process(&self, events: Events, ops: &mut EventOps<'_>) { - if events.event_set().contains(EventSet::HANG_UP) { - let _ = self.rhup_count.fetch_add(1, Ordering::Relaxed); - if self.with_unregister_on_err { - ops.remove(Events::empty(&self.stream)).unwrap(); - } - } - } - - fn init(&self, ops: &mut EventOps<'_>) { - ops.add(Events::new(&self.stream, EventSet::IN)).unwrap(); - } -} - -#[test] -fn test_handling_errors_in_subscriber() { - let (sock1, sock2) = UnixStream::pair().unwrap(); - - let mut event_manager = EventManager::>::new().unwrap(); - let subscriber = Arc::new(UnixStreamSubscriber::new(sock1)); - event_manager.add_subscriber(subscriber.clone()); - - // SAFETY: safe because `sock2` is a valid Unix socket, as asserted by the `unwrap` above. - unsafe { libc::close(sock2.as_raw_fd()) }; - - event_manager.run_with_timeout(100).unwrap(); - event_manager.run_with_timeout(100).unwrap(); - event_manager.run_with_timeout(100).unwrap(); - - // Since the subscriber did not remove the event from its watch list, the - // `EPOLLRHUP` error will continuously be a ready event each time `run` is called. - // We called `run_with_timeout` 3 times, hence we expect `rhup_count` to be 3. - assert_eq!(subscriber.rhup_count.load(Ordering::Relaxed), 3); - - let (sock1, sock2) = UnixStream::pair().unwrap(); - let subscriber_with_unregister = - Arc::new(UnixStreamSubscriber::new_with_unregister_on_err(sock1)); - event_manager.add_subscriber(subscriber_with_unregister); - - // SAFETY: safe because `sock2` is a valid Unix socket, as asserted by the `unwrap` above. - unsafe { libc::close(sock2.as_raw_fd()) }; - - let ready_list_len = event_manager.run_with_timeout(100).unwrap(); - assert_eq!(ready_list_len, 2); - // At this point the `subscriber_with_unregister` should not yield events anymore. - // We expect the number of ready fds to be 1. - let ready_list_len = event_manager.run_with_timeout(100).unwrap(); - assert_eq!(ready_list_len, 1); -} - -#[test] -fn test_max_ready_list_size() { - assert!( - EventManager::>::new_with_capacity(MAX_READY_EVENTS_CAPACITY) - .is_ok() - ); - assert!(EventManager::>::new_with_capacity( - MAX_READY_EVENTS_CAPACITY + 1 - ) - .is_err()); - assert!(EventManager::>::new_with_capacity(usize::MAX).is_err()) -} diff --git a/tests/regressions.rs b/tests/regressions.rs deleted file mode 100644 index 92c83ec..0000000 --- a/tests/regressions.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2020 Alibaba Cloud. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -use event_manager::utilities::subscribers::CounterSubscriberWithData; -use event_manager::{EventManager, SubscriberOps}; - -// Test for the race condition reported by: Karthik.n -// FYI: https://github.com/rust-vmm/event-manager/issues/41 -#[test] -fn test_reuse_file_descriptor() { - let mut event_manager = EventManager::::new().unwrap(); - let mut counter_subscriber = CounterSubscriberWithData::new(0); - - // Set flag to toggle the registration of all three fds on the first epoll event, so the final - // event counter should be 1. - counter_subscriber.set_toggle_registry(true); - counter_subscriber.trigger_all_counters(); - let id = event_manager.add_subscriber(counter_subscriber); - - event_manager.run().unwrap(); - let c_ref = event_manager.subscriber_mut(id).unwrap(); - let counters = c_ref.get_all_counter_values(); - assert_eq!(counters[0] + counters[1] + counters[2], 1); -}