mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-08 19:43:11 +00:00
Use Windows native API to impl dispatcher (#9280)
Using `Threadpool` and `TimerQueue` which are provided by the native Windows APIs, to implement the corresponding interfaces, we do not need to sort tasks ourselves as Windows will handle it in a relatively more efficient manner, I guess. I am unsure if Zed would welcome this PR, and suggestions are welcome. Release Notes: - N/A
This commit is contained in:
parent
4939d23bd3
commit
c2d27c44f9
1 changed files with 118 additions and 94 deletions
|
@ -1,94 +1,78 @@
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
sync::{
|
||||||
thread::{current, JoinHandle, ThreadId},
|
atomic::{AtomicIsize, Ordering},
|
||||||
time::{Duration, Instant},
|
Arc,
|
||||||
|
},
|
||||||
|
thread::{current, ThreadId},
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
use collections::BinaryHeap;
|
use flume::Sender;
|
||||||
use flume::{RecvTimeoutError, Sender};
|
|
||||||
use parking::Parker;
|
use parking::Parker;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use windows::Win32::{Foundation::HANDLE, System::Threading::SetEvent};
|
use windows::Win32::{
|
||||||
|
Foundation::{BOOLEAN, HANDLE},
|
||||||
|
System::Threading::{
|
||||||
|
CreateThreadpool, CreateThreadpoolWork, CreateTimerQueueTimer, DeleteTimerQueueTimer,
|
||||||
|
SetEvent, SetThreadpoolThreadMinimum, SubmitThreadpoolWork, PTP_CALLBACK_INSTANCE,
|
||||||
|
PTP_POOL, PTP_WORK, TP_CALLBACK_ENVIRON_V3, TP_CALLBACK_PRIORITY_NORMAL,
|
||||||
|
WT_EXECUTEONLYONCE,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{PlatformDispatcher, TaskLabel};
|
use crate::{PlatformDispatcher, TaskLabel};
|
||||||
|
|
||||||
pub(crate) struct WindowsDispatcher {
|
pub(crate) struct WindowsDispatcher {
|
||||||
background_sender: Sender<(Runnable, Option<TaskLabel>)>,
|
threadpool: PTP_POOL,
|
||||||
main_sender: Sender<Runnable>,
|
main_sender: Sender<Runnable>,
|
||||||
timer_sender: Sender<(Runnable, Duration)>,
|
|
||||||
background_threads: Vec<JoinHandle<()>>,
|
|
||||||
timer_thread: JoinHandle<()>,
|
|
||||||
parker: Mutex<Parker>,
|
parker: Mutex<Parker>,
|
||||||
main_thread_id: ThreadId,
|
main_thread_id: ThreadId,
|
||||||
event: HANDLE,
|
dispatch_event: HANDLE,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowsDispatcher {
|
impl WindowsDispatcher {
|
||||||
pub(crate) fn new(main_sender: Sender<Runnable>, event: HANDLE) -> Self {
|
pub(crate) fn new(main_sender: Sender<Runnable>, dispatch_event: HANDLE) -> Self {
|
||||||
let parker = Mutex::new(Parker::new());
|
let parker = Mutex::new(Parker::new());
|
||||||
let (background_sender, background_receiver) =
|
let threadpool = unsafe {
|
||||||
flume::unbounded::<(Runnable, Option<TaskLabel>)>();
|
let ret = CreateThreadpool(None);
|
||||||
let background_threads = (0..std::thread::available_parallelism()
|
if ret.0 == 0 {
|
||||||
.map(|i| i.get())
|
panic!(
|
||||||
.unwrap_or(1))
|
"unable to initialize a thread pool: {}",
|
||||||
.map(|_| {
|
std::io::Error::last_os_error()
|
||||||
let receiver = background_receiver.clone();
|
);
|
||||||
std::thread::spawn(move || {
|
|
||||||
for (runnable, label) in receiver {
|
|
||||||
if let Some(label) = label {
|
|
||||||
log::debug!("TaskLabel: {label:?}");
|
|
||||||
}
|
|
||||||
runnable.run();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let (timer_sender, timer_receiver) = flume::unbounded::<(Runnable, Duration)>();
|
|
||||||
let timer_thread = std::thread::spawn(move || {
|
|
||||||
let mut runnables = BinaryHeap::<RunnableAfter>::new();
|
|
||||||
let mut timeout_dur = None;
|
|
||||||
loop {
|
|
||||||
let recv = if let Some(dur) = timeout_dur {
|
|
||||||
match timer_receiver.recv_timeout(dur) {
|
|
||||||
Ok(recv) => Some(recv),
|
|
||||||
Err(RecvTimeoutError::Timeout) => None,
|
|
||||||
Err(RecvTimeoutError::Disconnected) => break,
|
|
||||||
}
|
|
||||||
} else if let Ok(recv) = timer_receiver.recv() {
|
|
||||||
Some(recv)
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
let now = Instant::now();
|
|
||||||
if let Some((runnable, dur)) = recv {
|
|
||||||
runnables.push(RunnableAfter {
|
|
||||||
runnable,
|
|
||||||
instant: now + dur,
|
|
||||||
});
|
|
||||||
while let Ok((runnable, dur)) = timer_receiver.try_recv() {
|
|
||||||
runnables.push(RunnableAfter {
|
|
||||||
runnable,
|
|
||||||
instant: now + dur,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while runnables.peek().is_some_and(|entry| entry.instant <= now) {
|
|
||||||
runnables.pop().unwrap().runnable.run();
|
|
||||||
}
|
|
||||||
timeout_dur = runnables.peek().map(|entry| entry.instant - now);
|
|
||||||
}
|
}
|
||||||
});
|
// set minimum 1 thread in threadpool
|
||||||
|
let _ = SetThreadpoolThreadMinimum(ret, 1)
|
||||||
|
.inspect_err(|_| log::error!("unable to configure thread pool"));
|
||||||
|
|
||||||
|
ret
|
||||||
|
};
|
||||||
let main_thread_id = current().id();
|
let main_thread_id = current().id();
|
||||||
Self {
|
WindowsDispatcher {
|
||||||
background_sender,
|
threadpool,
|
||||||
main_sender,
|
main_sender,
|
||||||
timer_sender,
|
|
||||||
background_threads,
|
|
||||||
timer_thread,
|
|
||||||
parker,
|
parker,
|
||||||
main_thread_id,
|
main_thread_id,
|
||||||
event,
|
dispatch_event,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch_on_threadpool(&self, runnable: Runnable) {
|
||||||
|
unsafe {
|
||||||
|
let ptr = Box::into_raw(Box::new(runnable));
|
||||||
|
let environment = get_threadpool_environment(self.threadpool);
|
||||||
|
let Ok(work) =
|
||||||
|
CreateThreadpoolWork(Some(threadpool_runner), Some(ptr as _), Some(&environment))
|
||||||
|
.inspect_err(|_| {
|
||||||
|
log::error!(
|
||||||
|
"unable to dispatch work on thread pool: {}",
|
||||||
|
std::io::Error::last_os_error()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
SubmitThreadpoolWork(work);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,10 +83,10 @@ impl PlatformDispatcher for WindowsDispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>) {
|
fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>) {
|
||||||
self.background_sender
|
self.dispatch_on_threadpool(runnable);
|
||||||
.send((runnable, label))
|
if let Some(label) = label {
|
||||||
.inspect_err(|e| log::error!("Dispatch failed: {e}"))
|
log::debug!("TaskLabel: {label:?}");
|
||||||
.ok();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_on_main_thread(&self, runnable: Runnable) {
|
fn dispatch_on_main_thread(&self, runnable: Runnable) {
|
||||||
|
@ -110,14 +94,34 @@ impl PlatformDispatcher for WindowsDispatcher {
|
||||||
.send(runnable)
|
.send(runnable)
|
||||||
.inspect_err(|e| log::error!("Dispatch failed: {e}"))
|
.inspect_err(|e| log::error!("Dispatch failed: {e}"))
|
||||||
.ok();
|
.ok();
|
||||||
unsafe { SetEvent(self.event) }.ok();
|
unsafe { SetEvent(self.dispatch_event) }.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_after(&self, duration: std::time::Duration, runnable: Runnable) {
|
fn dispatch_after(&self, duration: std::time::Duration, runnable: Runnable) {
|
||||||
self.timer_sender
|
if duration.as_millis() == 0 {
|
||||||
.send((runnable, duration))
|
self.dispatch_on_threadpool(runnable);
|
||||||
.inspect_err(|e| log::error!("Dispatch failed: {e}"))
|
return;
|
||||||
.ok();
|
}
|
||||||
|
unsafe {
|
||||||
|
let mut handle = std::mem::zeroed();
|
||||||
|
let task = Arc::new(DelayedTask::new(runnable));
|
||||||
|
let _ = CreateTimerQueueTimer(
|
||||||
|
&mut handle,
|
||||||
|
None,
|
||||||
|
Some(timer_queue_runner),
|
||||||
|
Some(Arc::into_raw(task.clone()) as _),
|
||||||
|
duration.as_millis() as u32,
|
||||||
|
0,
|
||||||
|
WT_EXECUTEONLYONCE,
|
||||||
|
)
|
||||||
|
.inspect_err(|_| {
|
||||||
|
log::error!(
|
||||||
|
"unable to dispatch delayed task: {}",
|
||||||
|
std::io::Error::last_os_error()
|
||||||
|
)
|
||||||
|
});
|
||||||
|
task.raw_timer_handle.store(handle.0, Ordering::SeqCst);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&self, _background_only: bool) -> bool {
|
fn tick(&self, _background_only: bool) -> bool {
|
||||||
|
@ -133,27 +137,47 @@ impl PlatformDispatcher for WindowsDispatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RunnableAfter {
|
extern "system" fn threadpool_runner(
|
||||||
runnable: Runnable,
|
_: PTP_CALLBACK_INSTANCE,
|
||||||
instant: Instant,
|
ptr: *mut std::ffi::c_void,
|
||||||
}
|
_: PTP_WORK,
|
||||||
|
) {
|
||||||
impl PartialEq for RunnableAfter {
|
unsafe {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
let runnable = Box::from_raw(ptr as *mut Runnable);
|
||||||
self.instant == other.instant
|
runnable.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for RunnableAfter {}
|
unsafe extern "system" fn timer_queue_runner(ptr: *mut std::ffi::c_void, _: BOOLEAN) {
|
||||||
|
let task = Arc::from_raw(ptr as *mut DelayedTask);
|
||||||
impl Ord for RunnableAfter {
|
task.runnable.lock().take().unwrap().run();
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
unsafe {
|
||||||
self.instant.cmp(&other.instant).reverse()
|
let timer = task.raw_timer_handle.load(Ordering::SeqCst);
|
||||||
|
let _ = DeleteTimerQueueTimer(None, HANDLE(timer), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for RunnableAfter {
|
struct DelayedTask {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
runnable: Mutex<Option<Runnable>>,
|
||||||
Some(self.cmp(other))
|
raw_timer_handle: AtomicIsize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DelayedTask {
|
||||||
|
pub fn new(runnable: Runnable) -> Self {
|
||||||
|
DelayedTask {
|
||||||
|
runnable: Mutex::new(Some(runnable)),
|
||||||
|
raw_timer_handle: AtomicIsize::new(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_threadpool_environment(pool: PTP_POOL) -> TP_CALLBACK_ENVIRON_V3 {
|
||||||
|
TP_CALLBACK_ENVIRON_V3 {
|
||||||
|
Version: 3, // Win7+, otherwise this value should be 1
|
||||||
|
Pool: pool,
|
||||||
|
CallbackPriority: TP_CALLBACK_PRIORITY_NORMAL,
|
||||||
|
Size: std::mem::size_of::<TP_CALLBACK_ENVIRON_V3>() as _,
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue