@@ -2,8 +2,9 @@ use std::cell::RefCell;
2
2
use std:: collections:: BTreeMap ;
3
3
use std:: io;
4
4
use std:: rc:: { Rc , Weak } ;
5
+ use std:: time:: Duration ;
5
6
6
- use crate :: shims:: unix:: fd:: { FdId , FileDescriptionRef } ;
7
+ use crate :: shims:: unix:: fd:: { FdId , FileDescriptionRef , WeakFileDescriptionRef } ;
7
8
use crate :: shims:: unix:: * ;
8
9
use crate :: * ;
9
10
@@ -19,6 +20,8 @@ struct Epoll {
19
20
// This is an Rc because EpollInterest need to hold a reference to update
20
21
// it.
21
22
ready_list : Rc < RefCell < BTreeMap < ( FdId , i32 ) , EpollEventInstance > > > ,
23
+ /// A list of thread ids blocked on this epoll instance.
24
+ thread_id : RefCell < Vec < ThreadId > > ,
22
25
}
23
26
24
27
/// EpollEventInstance contains information that will be returned by epoll_wait.
@@ -58,6 +61,8 @@ pub struct EpollEventInterest {
58
61
data : u64 ,
59
62
/// Ready list of the epoll instance under which this EpollEventInterest is registered.
60
63
ready_list : Rc < RefCell < BTreeMap < ( FdId , i32 ) , EpollEventInstance > > > ,
64
+ /// The file descriptor value that this EpollEventInterest is registered under.
65
+ epfd : i32 ,
61
66
}
62
67
63
68
/// EpollReadyEvents reflects the readiness of a file description.
@@ -338,6 +343,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
338
343
events,
339
344
data,
340
345
ready_list : Rc :: clone ( ready_list) ,
346
+ epfd : epfd_value,
341
347
} ) ) ;
342
348
343
349
if op == epoll_ctl_add {
@@ -395,7 +401,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
395
401
396
402
/// The `timeout` argument specifies the number of milliseconds that
397
403
/// `epoll_wait()` will block. Time is measured against the
398
- /// CLOCK_MONOTONIC clock.
404
+ /// CLOCK_MONOTONIC clock. If the timeout is zero, the function will not block,
405
+ /// while if the timeout is -1, the function will block
406
+ /// until at least one event has been retrieved (or an error
407
+ /// occurred).
399
408
400
409
/// A call to `epoll_wait()` will block until either:
401
410
/// • a file descriptor delivers an event;
@@ -421,43 +430,115 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
421
430
events_op : & OpTy < ' tcx > ,
422
431
maxevents : & OpTy < ' tcx > ,
423
432
timeout : & OpTy < ' tcx > ,
424
- ) -> InterpResult < ' tcx , Scalar > {
433
+ dest : MPlaceTy < ' tcx > ,
434
+ ) -> InterpResult < ' tcx > {
425
435
let this = self . eval_context_mut ( ) ;
426
436
427
- let epfd = this. read_scalar ( epfd) ?. to_i32 ( ) ?;
437
+ let epfd_value = this. read_scalar ( epfd) ?. to_i32 ( ) ?;
428
438
let events = this. read_immediate ( events_op) ?;
429
439
let maxevents = this. read_scalar ( maxevents) ?. to_i32 ( ) ?;
430
440
let timeout = this. read_scalar ( timeout) ?. to_i32 ( ) ?;
431
441
432
- if epfd <= 0 || maxevents <= 0 {
442
+ if epfd_value <= 0 || maxevents <= 0 {
433
443
let einval = this. eval_libc ( "EINVAL" ) ;
434
444
this. set_last_error ( einval) ?;
435
- return Ok ( Scalar :: from_i32 ( -1 ) ) ;
445
+ this. write_int ( -1 , & dest) ?;
446
+ return Ok ( ( ) ) ;
436
447
}
437
448
438
449
// This needs to come after the maxevents value check, or else maxevents.try_into().unwrap()
439
450
// will fail.
440
- let events = this. deref_pointer_as (
451
+ let event = this. deref_pointer_as (
441
452
& events,
442
453
this. libc_array_ty_layout ( "epoll_event" , maxevents. try_into ( ) . unwrap ( ) ) ,
443
454
) ?;
444
455
445
- // FIXME: Implement blocking support
446
- if timeout != 0 {
447
- throw_unsup_format ! ( "epoll_wait: timeout value can only be 0" ) ;
456
+ let Some ( epfd) = this. machine . fds . get ( epfd_value) else {
457
+ let result_value: i32 = this. fd_not_found ( ) ?;
458
+ this. write_int ( result_value, & dest) ?;
459
+ return Ok ( ( ) ) ;
460
+ } ;
461
+ // Create a weak ref of epfd and pass it to callback so we will make sure that epfd
462
+ // is not close after the thread unblocks.
463
+ let weak_epfd = epfd. downgrade ( ) ;
464
+
465
+ // We just need to know if the ready list is empty and borrow the thread_ids out.
466
+ // The whole logic is wrapped inside a block so we don't need to manually drop epfd later.
467
+ let ready_list_empty;
468
+ let mut thread_ids;
469
+ {
470
+ let epoll_file_description = epfd
471
+ . downcast :: < Epoll > ( )
472
+ . ok_or_else ( || err_unsup_format ! ( "non-epoll FD passed to `epoll_wait`" ) ) ?;
473
+ let binding = epoll_file_description. get_ready_list ( ) ;
474
+ ready_list_empty = binding. borrow_mut ( ) . is_empty ( ) ;
475
+ thread_ids = epoll_file_description. thread_id . borrow_mut ( ) ;
476
+ }
477
+ if timeout == 0 || !ready_list_empty {
478
+ // If the ready list is not empty, or the timeout is 0, we can return immediately.
479
+ this. blocking_epoll_callback ( epfd_value, weak_epfd, & dest, & event) ?;
480
+ } else {
481
+ // Blocking
482
+ let timeout = match timeout {
483
+ 0 .. => {
484
+ let duration = Duration :: from_millis ( timeout. try_into ( ) . unwrap ( ) ) ;
485
+ Some ( ( TimeoutClock :: Monotonic , TimeoutAnchor :: Relative , duration) )
486
+ }
487
+ -1 => None ,
488
+ ..-1 => {
489
+ throw_unsup_format ! (
490
+ "epoll_wait: Only timeout values greater than -1 are supported."
491
+ ) ;
492
+ }
493
+ } ;
494
+ thread_ids. push ( this. active_thread ( ) ) ;
495
+ this. block_thread (
496
+ BlockReason :: Epoll ,
497
+ timeout,
498
+ callback ! (
499
+ @capture<' tcx> {
500
+ epfd_value: i32 ,
501
+ weak_epfd: WeakFileDescriptionRef ,
502
+ dest: MPlaceTy <' tcx>,
503
+ event: MPlaceTy <' tcx>,
504
+ }
505
+ @unblock = |this| {
506
+ this. blocking_epoll_callback( epfd_value, weak_epfd, & dest, & event) ?;
507
+ Ok ( ( ) )
508
+ }
509
+ @timeout = |this| {
510
+ // No notification after blocking timeout.
511
+ this. write_int( 0 , & dest) ?;
512
+ Ok ( ( ) )
513
+ }
514
+ ) ,
515
+ ) ;
448
516
}
517
+ Ok ( ( ) )
518
+ }
449
519
450
- let Some ( epfd) = this. machine . fds . get ( epfd) else {
451
- return Ok ( Scalar :: from_i32 ( this. fd_not_found ( ) ?) ) ;
520
+ /// Callback function after epoll_wait unblocks
521
+ fn blocking_epoll_callback (
522
+ & mut self ,
523
+ epfd_value : i32 ,
524
+ weak_epfd : WeakFileDescriptionRef ,
525
+ dest : & MPlaceTy < ' tcx > ,
526
+ event : & MPlaceTy < ' tcx > ,
527
+ ) -> InterpResult < ' tcx > {
528
+ let this = self . eval_context_mut ( ) ;
529
+
530
+ let Some ( epfd) = weak_epfd. upgrade ( ) else {
531
+ throw_unsup_format ! ( "epoll FD {epfd_value} is closed while blocking." )
452
532
} ;
533
+
453
534
let epoll_file_description = epfd
454
535
. downcast :: < Epoll > ( )
455
536
. ok_or_else ( || err_unsup_format ! ( "non-epoll FD passed to `epoll_wait`" ) ) ?;
456
537
457
538
let ready_list = epoll_file_description. get_ready_list ( ) ;
458
539
let mut ready_list = ready_list. borrow_mut ( ) ;
459
540
let mut num_of_events: i32 = 0 ;
460
- let mut array_iter = this. project_array_fields ( & events ) ?;
541
+ let mut array_iter = this. project_array_fields ( event ) ?;
461
542
462
543
while let Some ( des) = array_iter. next ( this) ? {
463
544
if let Some ( epoll_event_instance) = ready_list_next ( this, & mut ready_list) {
@@ -473,7 +554,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
473
554
break ;
474
555
}
475
556
}
476
- Ok ( Scalar :: from_i32 ( num_of_events) )
557
+ this. write_int ( num_of_events, dest) ?;
558
+ Ok ( ( ) )
477
559
}
478
560
479
561
/// For a specific file description, get its ready events and update the corresponding ready
@@ -483,17 +565,42 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
483
565
///
484
566
/// This *will* report an event if anyone is subscribed to it, without any further filtering, so
485
567
/// do not call this function when an FD didn't have anything happen to it!
486
- fn check_and_update_readiness ( & self , fd_ref : & FileDescriptionRef ) -> InterpResult < ' tcx , ( ) > {
487
- let this = self . eval_context_ref ( ) ;
568
+ fn check_and_update_readiness (
569
+ & mut self ,
570
+ fd_ref : & FileDescriptionRef ,
571
+ ) -> InterpResult < ' tcx , ( ) > {
572
+ let this = self . eval_context_mut ( ) ;
488
573
let id = fd_ref. get_id ( ) ;
574
+ let mut waiter = Vec :: new ( ) ;
489
575
// Get a list of EpollEventInterest that is associated to a specific file description.
490
576
if let Some ( epoll_interests) = this. machine . epoll_interests . get_epoll_interest ( id) {
491
577
for weak_epoll_interest in epoll_interests {
492
578
if let Some ( epoll_interest) = weak_epoll_interest. upgrade ( ) {
493
- check_and_update_one_event_interest ( fd_ref, epoll_interest, id, this) ?;
579
+ let is_updated = check_and_update_one_event_interest ( fd_ref, epoll_interest, id, this) ?;
580
+ if is_updated {
581
+ // Edge-triggered notification only notify one thread even if there are
582
+ // multiple threads block on the same epfd.
583
+ let epfd = this. machine . fds . get ( epoll_event_interest. epfd ) . unwrap ( ) ;
584
+ // FIXME: We can randomly pick a thread to unblock.
585
+
586
+ // This unwrap can never fail because if the current epoll instance were
587
+ // closed and its epfd value reused, the upgrade of weak_epoll_interest
588
+ // above would fail. This guarantee holds because only the epoll instance
589
+ // holds a strong ref to epoll_interest.
590
+ if let Some ( thread_id) =
591
+ epfd. downcast :: < Epoll > ( ) . unwrap ( ) . thread_id . borrow_mut ( ) . pop ( )
592
+ {
593
+ waiter. push ( thread_id) ;
594
+ } ;
595
+ }
494
596
}
495
597
}
496
598
}
599
+ waiter. sort ( ) ;
600
+ waiter. dedup ( ) ;
601
+ for thread_id in waiter {
602
+ this. unblock_thread ( thread_id, BlockReason :: Epoll ) ?;
603
+ }
497
604
Ok ( ( ) )
498
605
}
499
606
}
@@ -517,14 +624,15 @@ fn ready_list_next(
517
624
}
518
625
519
626
/// This helper function checks whether an epoll notification should be triggered for a specific
520
- /// epoll_interest and, if necessary, triggers the notification. Unlike check_and_update_readiness,
521
- /// this function sends a notification to only one epoll instance.
627
+ /// epoll_interest and, if necessary, triggers the notification, and returns whether the
628
+ /// event interest was updated. Unlike check_and_update_readiness, this function sends a
629
+ /// notification to only one epoll instance.
522
630
fn check_and_update_one_event_interest < ' tcx > (
523
631
fd_ref : & FileDescriptionRef ,
524
632
interest : Rc < RefCell < EpollEventInterest > > ,
525
633
id : FdId ,
526
634
ecx : & MiriInterpCx < ' tcx > ,
527
- ) -> InterpResult < ' tcx > {
635
+ ) -> InterpResult < ' tcx , bool > {
528
636
// Get the bitmask of ready events for a file description.
529
637
let ready_events_bitmask = fd_ref. get_epoll_ready_events ( ) ?. get_event_bitmask ( ecx) ;
530
638
let epoll_event_interest = interest. borrow ( ) ;
@@ -539,6 +647,7 @@ fn check_and_update_one_event_interest<'tcx>(
539
647
let event_instance = EpollEventInstance :: new ( flags, epoll_event_interest. data ) ;
540
648
// Triggers the notification by inserting it to the ready list.
541
649
ready_list. insert ( epoll_key, event_instance) ;
650
+ return Ok ( true ) ;
542
651
}
543
- Ok ( ( ) )
652
+ return Ok ( false ) ;
544
653
}
0 commit comments