Make block_with_timeout more robust (#11670)
The previous implementation relied on a background thread to wake up the main thread, which was prone to priority inversion under heavy load. In a synthetic test, where we spawn 200 git processes while doing a 5ms timeout, the old version blocked for 5-80ms, the new version blocks for 5.1-5.4ms. Release Notes: - Improved responsiveness of the main thread under high system load
This commit is contained in:
@@ -110,12 +110,13 @@ impl PlatformDispatcher for LinuxDispatcher {
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn tick(&self, background_only: bool) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn park(&self) {
|
||||
self.parker.lock().park();
|
||||
fn park(&self, timeout: Option<Duration>) -> bool {
|
||||
if let Some(timeout) = timeout {
|
||||
self.parker.lock().park_timeout(timeout)
|
||||
} else {
|
||||
self.parker.lock().park();
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn unparker(&self) -> Unparker {
|
||||
|
||||
@@ -87,12 +87,13 @@ impl PlatformDispatcher for MacDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(&self, _background_only: bool) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn park(&self) {
|
||||
self.parker.lock().park()
|
||||
fn park(&self, timeout: Option<Duration>) -> bool {
|
||||
if let Some(timeout) = timeout {
|
||||
self.parker.lock().park_timeout(timeout)
|
||||
} else {
|
||||
self.parker.lock().park();
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn unparker(&self) -> Unparker {
|
||||
|
||||
@@ -111,6 +111,68 @@ impl TestDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(&self, background_only: bool) -> bool {
|
||||
let mut state = self.state.lock();
|
||||
|
||||
while let Some((deadline, _)) = state.delayed.first() {
|
||||
if *deadline > state.time {
|
||||
break;
|
||||
}
|
||||
let (_, runnable) = state.delayed.remove(0);
|
||||
state.background.push(runnable);
|
||||
}
|
||||
|
||||
let foreground_len: usize = if background_only {
|
||||
0
|
||||
} else {
|
||||
state
|
||||
.foreground
|
||||
.values()
|
||||
.map(|runnables| runnables.len())
|
||||
.sum()
|
||||
};
|
||||
let background_len = state.background.len();
|
||||
|
||||
let runnable;
|
||||
let main_thread;
|
||||
if foreground_len == 0 && background_len == 0 {
|
||||
let deprioritized_background_len = state.deprioritized_background.len();
|
||||
if deprioritized_background_len == 0 {
|
||||
return false;
|
||||
}
|
||||
let ix = state.random.gen_range(0..deprioritized_background_len);
|
||||
main_thread = false;
|
||||
runnable = state.deprioritized_background.swap_remove(ix);
|
||||
} else {
|
||||
main_thread = state.random.gen_ratio(
|
||||
foreground_len as u32,
|
||||
(foreground_len + background_len) as u32,
|
||||
);
|
||||
if main_thread {
|
||||
let state = &mut *state;
|
||||
runnable = state
|
||||
.foreground
|
||||
.values_mut()
|
||||
.filter(|runnables| !runnables.is_empty())
|
||||
.choose(&mut state.random)
|
||||
.unwrap()
|
||||
.pop_front()
|
||||
.unwrap();
|
||||
} else {
|
||||
let ix = state.random.gen_range(0..background_len);
|
||||
runnable = state.background.swap_remove(ix);
|
||||
};
|
||||
};
|
||||
|
||||
let was_main_thread = state.is_main_thread;
|
||||
state.is_main_thread = main_thread;
|
||||
drop(state);
|
||||
runnable.run();
|
||||
self.state.lock().is_main_thread = was_main_thread;
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn deprioritize(&self, task_label: TaskLabel) {
|
||||
self.state
|
||||
.lock()
|
||||
@@ -221,71 +283,9 @@ impl PlatformDispatcher for TestDispatcher {
|
||||
};
|
||||
state.delayed.insert(ix, (next_time, runnable));
|
||||
}
|
||||
|
||||
fn tick(&self, background_only: bool) -> bool {
|
||||
let mut state = self.state.lock();
|
||||
|
||||
while let Some((deadline, _)) = state.delayed.first() {
|
||||
if *deadline > state.time {
|
||||
break;
|
||||
}
|
||||
let (_, runnable) = state.delayed.remove(0);
|
||||
state.background.push(runnable);
|
||||
}
|
||||
|
||||
let foreground_len: usize = if background_only {
|
||||
0
|
||||
} else {
|
||||
state
|
||||
.foreground
|
||||
.values()
|
||||
.map(|runnables| runnables.len())
|
||||
.sum()
|
||||
};
|
||||
let background_len = state.background.len();
|
||||
|
||||
let runnable;
|
||||
let main_thread;
|
||||
if foreground_len == 0 && background_len == 0 {
|
||||
let deprioritized_background_len = state.deprioritized_background.len();
|
||||
if deprioritized_background_len == 0 {
|
||||
return false;
|
||||
}
|
||||
let ix = state.random.gen_range(0..deprioritized_background_len);
|
||||
main_thread = false;
|
||||
runnable = state.deprioritized_background.swap_remove(ix);
|
||||
} else {
|
||||
main_thread = state.random.gen_ratio(
|
||||
foreground_len as u32,
|
||||
(foreground_len + background_len) as u32,
|
||||
);
|
||||
if main_thread {
|
||||
let state = &mut *state;
|
||||
runnable = state
|
||||
.foreground
|
||||
.values_mut()
|
||||
.filter(|runnables| !runnables.is_empty())
|
||||
.choose(&mut state.random)
|
||||
.unwrap()
|
||||
.pop_front()
|
||||
.unwrap();
|
||||
} else {
|
||||
let ix = state.random.gen_range(0..background_len);
|
||||
runnable = state.background.swap_remove(ix);
|
||||
};
|
||||
};
|
||||
|
||||
let was_main_thread = state.is_main_thread;
|
||||
state.is_main_thread = main_thread;
|
||||
drop(state);
|
||||
runnable.run();
|
||||
self.state.lock().is_main_thread = was_main_thread;
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn park(&self) {
|
||||
fn park(&self, _: Option<std::time::Duration>) -> bool {
|
||||
self.parker.lock().park();
|
||||
true
|
||||
}
|
||||
|
||||
fn unparker(&self) -> Unparker {
|
||||
|
||||
@@ -122,12 +122,13 @@ impl PlatformDispatcher for WindowsDispatcher {
|
||||
self.dispatch_on_threadpool_after(runnable, duration);
|
||||
}
|
||||
|
||||
fn tick(&self, _background_only: bool) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn park(&self) {
|
||||
self.parker.lock().park();
|
||||
fn park(&self, timeout: Option<Duration>) -> bool {
|
||||
if let Some(timeout) = timeout {
|
||||
self.parker.lock().park_timeout(timeout)
|
||||
} else {
|
||||
self.parker.lock().park();
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn unparker(&self) -> parking::Unparker {
|
||||
|
||||
Reference in New Issue
Block a user