44
55use std:: convert:: TryInto ;
66use std:: thread;
7- use std:: thread:: spawn;
87
98#[ path = "../../utils/libc.rs" ]
109mod libc_utils;
@@ -17,6 +16,7 @@ fn main() {
1716 test_notification_after_timeout ( ) ;
1817 test_epoll_race ( ) ;
1918 wakeup_on_new_interest ( ) ;
19+ multiple_events_wake_multiple_threads ( ) ;
2020}
2121
2222// Using `as` cast since `EPOLLET` wraps around
@@ -90,7 +90,7 @@ fn test_epoll_block_then_unblock() {
9090 // epoll_wait before triggering notification so it will block then get unblocked before timeout.
9191 let expected_event = u32:: try_from ( libc:: EPOLLIN | libc:: EPOLLOUT ) . unwrap ( ) ;
9292 let expected_value = fds[ 0 ] as u64 ;
93- let thread1 = spawn ( move || {
93+ let thread1 = thread :: spawn ( move || {
9494 thread:: yield_now ( ) ;
9595 let data = "abcde" . as_bytes ( ) . as_ptr ( ) ;
9696 let res = unsafe { libc_utils:: write_all ( fds[ 1 ] , data as * const libc:: c_void , 5 ) } ;
@@ -210,3 +210,53 @@ fn wakeup_on_new_interest() {
210210 // This should wake up the thread.
211211 t. join ( ) . unwrap ( ) ;
212212}
213+
214+ /// Ensure that if a single operation triggers multiple events, we wake up enough threads
215+ /// to consume them all.
216+ fn multiple_events_wake_multiple_threads ( ) {
217+ // Create an epoll instance.
218+ let epfd = unsafe { libc:: epoll_create1 ( 0 ) } ;
219+ assert_ne ! ( epfd, -1 ) ;
220+
221+ // Create an eventfd instance.
222+ let flags = libc:: EFD_NONBLOCK | libc:: EFD_CLOEXEC ;
223+ let fd1 = unsafe { libc:: eventfd ( 0 , flags) } ;
224+ // Make a duplicate so that we have two file descriptors for the same file description.
225+ let fd2 = unsafe { libc:: dup ( fd1) } ;
226+
227+ // Register both with epoll.
228+ let mut ev = libc:: epoll_event { events : EPOLL_IN_OUT_ET , u64 : fd1 as u64 } ;
229+ let res = unsafe { libc:: epoll_ctl ( epfd, libc:: EPOLL_CTL_ADD , fd1, & mut ev) } ;
230+ assert_eq ! ( res, 0 ) ;
231+ let mut ev = libc:: epoll_event { events : EPOLL_IN_OUT_ET , u64 : fd2 as u64 } ;
232+ let res = unsafe { libc:: epoll_ctl ( epfd, libc:: EPOLL_CTL_ADD , fd2, & mut ev) } ;
233+ assert_eq ! ( res, 0 ) ;
234+ // Consume the initial event.
235+ let expected = [ ( libc:: EPOLLOUT as u32 , fd1 as u64 ) , ( libc:: EPOLLOUT as u32 , fd2 as u64 ) ] ;
236+ check_epoll_wait :: < 8 > ( epfd, & expected, -1 ) ;
237+
238+ // Block two threads on the epoll, both wanting to get just one event.
239+ let t1 = thread:: spawn ( move || {
240+ let mut e = libc:: epoll_event { events : 0 , u64 : 0 } ;
241+ let res = unsafe { libc:: epoll_wait ( epfd, & raw mut e, 1 , -1 ) } ;
242+ assert ! ( res == 1 ) ;
243+ ( e. events , e. u64 )
244+ } ) ;
245+ let t2 = thread:: spawn ( move || {
246+ let mut e = libc:: epoll_event { events : 0 , u64 : 0 } ;
247+ let res = unsafe { libc:: epoll_wait ( epfd, & raw mut e, 1 , -1 ) } ;
248+ assert ! ( res == 1 ) ;
249+ ( e. events , e. u64 )
250+ } ) ;
251+ // Yield so both threads are waiting now.
252+ thread:: yield_now ( ) ;
253+
254+ // Trigger the eventfd. This triggers two events at once!
255+ libc_utils:: write_all_from_slice ( fd1, & 0_u64 . to_ne_bytes ( ) ) . unwrap ( ) ;
256+
257+ // Both threads should have been woken up so that both events can be consumed.
258+ let e1 = t1. join ( ) . unwrap ( ) ;
259+ let e2 = t2. join ( ) . unwrap ( ) ;
260+ // Ensure that across the two threads we got both events.
261+ assert ! ( expected == [ e1, e2] || expected == [ e2, e1] ) ;
262+ }
0 commit comments