mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
update timer scheduler to use crossbeam
This commit is contained in:
parent
32eb858a6a
commit
c893c8955d
4 changed files with 73 additions and 107 deletions
|
@ -2,15 +2,12 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crossbeam_channel::{self, TryRecvError};
|
||||
use ipc_channel::ipc::{self, IpcSender};
|
||||
use script_traits::{TimerEvent, TimerEventRequest, TimerSchedulerMsg};
|
||||
use std::cmp::{self, Ord};
|
||||
use std::collections::BinaryHeap;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub struct TimerScheduler;
|
||||
pub struct TimerScheduler(BinaryHeap<ScheduledEvent>);
|
||||
|
||||
struct ScheduledEvent {
|
||||
request: TimerEventRequest,
|
||||
|
@ -37,93 +34,40 @@ impl PartialEq for ScheduledEvent {
|
|||
}
|
||||
|
||||
impl TimerScheduler {
|
||||
pub fn start() -> IpcSender<TimerSchedulerMsg> {
|
||||
let (req_ipc_sender, req_ipc_receiver) = ipc::channel().expect("Channel creation failed.");
|
||||
let (req_sender, req_receiver) = crossbeam_channel::bounded(1);
|
||||
pub fn new() -> Self {
|
||||
TimerScheduler(BinaryHeap::<ScheduledEvent>::new())
|
||||
}
|
||||
|
||||
// We could do this much more directly with recv_timeout
|
||||
// (https://github.com/rust-lang/rfcs/issues/962).
|
||||
/// Dispatch any events whose due time is past,
|
||||
/// and return a timeout corresponding to the earliest scheduled event, if any.
|
||||
pub fn check_timers(&mut self) -> Option<Duration> {
|
||||
let now = Instant::now();
|
||||
loop {
|
||||
match self.0.peek() {
|
||||
// Dispatch the event if its due time is past
|
||||
Some(event) if event.for_time <= now => {
|
||||
let TimerEventRequest(ref sender, source, id, _) = event.request;
|
||||
let _ = sender.send(TimerEvent(source, id));
|
||||
},
|
||||
// Do not schedule a timeout.
|
||||
None => return None,
|
||||
// Schedule a timeout for the earliest event.
|
||||
Some(event) => return Some(event.for_time - now),
|
||||
}
|
||||
// Remove the event from the priority queue
|
||||
// (Note this only executes when the first event has been dispatched).
|
||||
self.0.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// util::thread doesn't give us access to the JoinHandle, which we need for park/unpark,
|
||||
// so we use the builder directly.
|
||||
let timeout_thread = thread::Builder::new()
|
||||
.name(String::from("TimerScheduler"))
|
||||
.spawn(move || {
|
||||
// We maintain a priority queue of future events, sorted by due time.
|
||||
let mut scheduled_events = BinaryHeap::<ScheduledEvent>::new();
|
||||
loop {
|
||||
let now = Instant::now();
|
||||
// Dispatch any events whose due time is past
|
||||
loop {
|
||||
match scheduled_events.peek() {
|
||||
// Dispatch the event if its due time is past
|
||||
Some(event) if event.for_time <= now => {
|
||||
let TimerEventRequest(ref sender, source, id, _) = event.request;
|
||||
let _ = sender.send(TimerEvent(source, id));
|
||||
},
|
||||
// Otherwise, we're done dispatching events
|
||||
_ => break,
|
||||
}
|
||||
// Remove the event from the priority queue
|
||||
// (Note this only executes when the first event has been dispatched
|
||||
scheduled_events.pop();
|
||||
}
|
||||
// Look to see if there are any incoming events
|
||||
match req_receiver.try_recv() {
|
||||
// If there is an event, add it to the priority queue
|
||||
Ok(TimerSchedulerMsg::Request(req)) => {
|
||||
let TimerEventRequest(_, _, _, delay) = req;
|
||||
let schedule = Instant::now() + Duration::from_millis(delay.get());
|
||||
let event = ScheduledEvent {
|
||||
request: req,
|
||||
for_time: schedule,
|
||||
};
|
||||
scheduled_events.push(event);
|
||||
},
|
||||
// If there is no incoming event, park the thread,
|
||||
// it will either be unparked when a new event arrives,
|
||||
// or by a timeout.
|
||||
Err(TryRecvError::Empty) => match scheduled_events.peek() {
|
||||
None => thread::park(),
|
||||
Some(event) => thread::park_timeout(event.for_time - now),
|
||||
},
|
||||
// If the channel is closed or we are shutting down, we are done.
|
||||
Ok(TimerSchedulerMsg::Exit) | Err(TryRecvError::Disconnected) => break,
|
||||
}
|
||||
}
|
||||
// This thread can terminate if the req_ipc_sender is dropped.
|
||||
warn!("TimerScheduler thread terminated.");
|
||||
})
|
||||
.expect("Thread creation failed.")
|
||||
.thread()
|
||||
.clone();
|
||||
|
||||
// A proxy that just routes incoming IPC requests over the MPSC channel to the timeout thread,
|
||||
// and unparks the timeout thread each time. Note that if unpark is called while the timeout
|
||||
// thread isn't parked, this causes the next call to thread::park by the timeout thread
|
||||
// not to block. This means that the timeout thread won't park when there is a request
|
||||
// waiting in the MPSC channel buffer.
|
||||
thread::Builder::new()
|
||||
.name(String::from("TimerProxy"))
|
||||
.spawn(move || {
|
||||
while let Ok(req) = req_ipc_receiver.recv() {
|
||||
let mut shutting_down = false;
|
||||
match req {
|
||||
TimerSchedulerMsg::Exit => shutting_down = true,
|
||||
_ => {},
|
||||
}
|
||||
let _ = req_sender.send(req);
|
||||
timeout_thread.unpark();
|
||||
if shutting_down {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// This thread can terminate if the req_ipc_sender is dropped.
|
||||
warn!("TimerProxy thread terminated.");
|
||||
})
|
||||
.expect("Thread creation failed.");
|
||||
|
||||
// Return the IPC sender
|
||||
req_ipc_sender
|
||||
/// Handle an incoming timer request.
|
||||
pub fn handle_timer_request(&mut self, request: TimerSchedulerMsg) {
|
||||
let TimerEventRequest(_, _, _, delay) = request.0;
|
||||
let schedule = Instant::now() + Duration::from_millis(delay.get());
|
||||
let event = ScheduledEvent {
|
||||
request: request.0,
|
||||
for_time: schedule,
|
||||
};
|
||||
self.0.push(event);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue