Compare commits
12 commits
8b4eba5a19
...
f8aa8d9615
Author | SHA1 | Date | |
---|---|---|---|
|
f8aa8d9615 | ||
|
edf25e407f | ||
|
6e4f6a1285 | ||
|
69d3794ff1 | ||
|
f384f61f81 | ||
|
19ef21e2ef | ||
|
38ce1de3ac | ||
|
09166a8eb7 | ||
|
228aa4d544 | ||
|
6fe5351e59 | ||
|
9cc125e558 | ||
|
2a0372a8a0 |
|
@ -48,4 +48,4 @@ cfg-if = "1.0.0"
|
||||||
async-std = "1.13.0"
|
async-std = "1.13.0"
|
||||||
tracing-test = "0.2.5"
|
tracing-test = "0.2.5"
|
||||||
tracing-tracy = "0.11.4"
|
tracing-tracy = "0.11.4"
|
||||||
distaff = {path = "distaff"}
|
distaff = {path = "distaff", features = ["tracing"]}
|
||||||
|
|
|
@ -56,7 +56,7 @@ mod tree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TREE_SIZE: usize = 16;
|
const TREE_SIZE: usize = 8;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn join_melange(b: &mut Bencher) {
|
fn join_melange(b: &mut Bencher) {
|
||||||
|
@ -191,11 +191,7 @@ fn join_distaff(b: &mut Bencher) {
|
||||||
let pool = ThreadPool::new();
|
let pool = ThreadPool::new();
|
||||||
let tree = tree::Tree::new(TREE_SIZE, 1u32);
|
let tree = tree::Tree::new(TREE_SIZE, 1u32);
|
||||||
|
|
||||||
fn sum<'scope, 'env>(
|
fn sum<'scope, 'env>(tree: &tree::Tree<u32>, node: usize, scope: Scope<'scope, 'env>) -> u32 {
|
||||||
tree: &tree::Tree<u32>,
|
|
||||||
node: usize,
|
|
||||||
scope: &'scope Scope<'scope, 'env>,
|
|
||||||
) -> u32 {
|
|
||||||
let node = tree.get(node);
|
let node = tree.get(node);
|
||||||
let (l, r) = scope.join(
|
let (l, r) = scope.join(
|
||||||
|s| node.left.map(|node| sum(tree, node, s)).unwrap_or_default(),
|
|s| node.left.map(|node| sum(tree, node, s)).unwrap_or_default(),
|
||||||
|
@ -210,10 +206,12 @@ fn join_distaff(b: &mut Bencher) {
|
||||||
}
|
}
|
||||||
|
|
||||||
b.iter(move || {
|
b.iter(move || {
|
||||||
pool.scope(|s| {
|
let sum = pool.scope(|s| {
|
||||||
let sum = sum(&tree, tree.root().unwrap(), s);
|
let sum = sum(&tree, tree.root().unwrap(), s);
|
||||||
// eprintln!("{sum}");
|
|
||||||
assert_ne!(sum, 0);
|
assert_ne!(sum, 0);
|
||||||
|
sum
|
||||||
});
|
});
|
||||||
|
std::hint::black_box(sum);
|
||||||
});
|
});
|
||||||
|
eprintln!("Done with distaff join");
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,22 @@ name = "distaff"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[profile.bench]
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
tracing = ["dep:tracing"]
|
||||||
std = []
|
std = []
|
||||||
|
metrics = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
parking_lot = {version = "0.12.3"}
|
parking_lot = {version = "0.12.3"}
|
||||||
tracing = "0.1.40"
|
atomic-wait = "1.1.0"
|
||||||
|
tracing = {version = "0.1", optional = true}
|
||||||
parking_lot_core = "0.9.10"
|
parking_lot_core = "0.9.10"
|
||||||
crossbeam-utils = "0.8.21"
|
crossbeam-utils = "0.8.21"
|
||||||
either = "1.15.0"
|
either = "1.15.0"
|
||||||
|
@ -17,6 +26,14 @@ either = "1.15.0"
|
||||||
async-task = "4.7.1"
|
async-task = "4.7.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tracing-test = "0.2.5"
|
tracing-test = {version = "0.2"}
|
||||||
tracing-tracy = "0.11.4"
|
tracing-tracy = {version = "0.11"}
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
|
||||||
|
divan = "0.1.14"
|
||||||
|
rayon = "1.10.0"
|
||||||
|
chili = {path = "../../chili"}
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "overhead"
|
||||||
|
harness = false
|
119
distaff/benches/overhead.rs
Normal file
119
distaff/benches/overhead.rs
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
use distaff::{Scope, ThreadPool};
|
||||||
|
use divan::Bencher;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
val: u64,
|
||||||
|
left: Option<Box<Node>>,
|
||||||
|
right: Option<Box<Node>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node {
|
||||||
|
pub fn tree(layers: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
val: 1,
|
||||||
|
left: (layers != 1).then(|| Box::new(Self::tree(layers - 1))),
|
||||||
|
right: (layers != 1).then(|| Box::new(Self::tree(layers - 1))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const LAYERS: &[usize] = &[10, 24];
|
||||||
|
fn nodes() -> impl Iterator<Item = (usize, usize)> {
|
||||||
|
LAYERS.iter().map(|&l| (l, (1 << l) - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = nodes())]
|
||||||
|
fn no_overhead(bencher: Bencher, nodes: (usize, usize)) {
|
||||||
|
fn join_no_overhead<A, B, RA, RB>(scope: Scope<'_, '_>, a: A, b: B) -> (RA, RB)
|
||||||
|
where
|
||||||
|
A: FnOnce(Scope<'_, '_>) -> RA + Send,
|
||||||
|
B: FnOnce(Scope<'_, '_>) -> RB + Send,
|
||||||
|
RA: Send,
|
||||||
|
RB: Send,
|
||||||
|
{
|
||||||
|
(a(scope), b(scope))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn sum(node: &Node, scope: Scope<'_, '_>) -> u64 {
|
||||||
|
let (left, right) = join_no_overhead(
|
||||||
|
scope,
|
||||||
|
|s| node.left.as_deref().map(|n| sum(n, s)).unwrap_or_default(),
|
||||||
|
|s| node.right.as_deref().map(|n| sum(n, s)).unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
node.val + left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
let tree = Node::tree(nodes.0);
|
||||||
|
let pool = ThreadPool::global();
|
||||||
|
|
||||||
|
bencher.bench_local(move || {
|
||||||
|
pool.scope(|scope| {
|
||||||
|
assert_eq!(sum(&tree, scope), nodes.1 as u64);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = nodes())]
|
||||||
|
fn distaff_overhead(bencher: Bencher, nodes: (usize, usize)) {
|
||||||
|
fn sum<'scope, 'env>(node: &Node, scope: Scope<'scope, 'env>) -> u64 {
|
||||||
|
let (left, right) = scope.join(
|
||||||
|
|s| node.left.as_deref().map(|n| sum(n, s)).unwrap_or_default(),
|
||||||
|
|s| node.right.as_deref().map(|n| sum(n, s)).unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
node.val + left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
let tree = Node::tree(nodes.0);
|
||||||
|
let pool = ThreadPool::global();
|
||||||
|
|
||||||
|
bencher.bench_local(move || {
|
||||||
|
pool.scope(|scope| {
|
||||||
|
assert_eq!(sum(&tree, scope), nodes.1 as u64);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = nodes())]
|
||||||
|
fn rayon_overhead(bencher: Bencher, nodes: (usize, usize)) {
|
||||||
|
fn sum(node: &Node) -> u64 {
|
||||||
|
let (left, right) = rayon::join(
|
||||||
|
|| node.left.as_deref().map(sum).unwrap_or_default(),
|
||||||
|
|| node.right.as_deref().map(sum).unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
node.val + left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
let tree = Node::tree(nodes.0);
|
||||||
|
|
||||||
|
bencher.bench_local(move || {
|
||||||
|
assert_eq!(sum(&tree), nodes.1 as u64);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[divan::bench(args = nodes())]
|
||||||
|
fn chili_overhead(bencher: Bencher, nodes: (usize, usize)) {
|
||||||
|
use chili::Scope;
|
||||||
|
fn sum(node: &Node, scope: &mut Scope<'_>) -> u64 {
|
||||||
|
let (left, right) = scope.join(
|
||||||
|
|s| node.left.as_deref().map(|n| sum(n, s)).unwrap_or_default(),
|
||||||
|
|s| node.right.as_deref().map(|n| sum(n, s)).unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
node.val + left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
let tree = Node::tree(nodes.0);
|
||||||
|
let mut scope = Scope::global();
|
||||||
|
|
||||||
|
bencher.bench_local(move || {
|
||||||
|
assert_eq!(sum(&tree, &mut scope), nodes.1 as u64);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
divan::main();
|
||||||
|
}
|
230
distaff/src/channel.rs
Normal file
230
distaff/src/channel.rs
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
// This file is taken from [`chili`]
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
cell::UnsafeCell,
|
||||||
|
ptr::NonNull,
|
||||||
|
sync::{
|
||||||
|
Arc,
|
||||||
|
atomic::{AtomicU8, AtomicU32, Ordering},
|
||||||
|
},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
Pending,
|
||||||
|
Waiting,
|
||||||
|
Ready,
|
||||||
|
Taken,
|
||||||
|
}
|
||||||
|
|
||||||
|
// taken from `std`
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Parker {
|
||||||
|
mutex: AtomicU32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parker {
|
||||||
|
const PARKED: u32 = u32::MAX;
|
||||||
|
const EMPTY: u32 = 0;
|
||||||
|
const NOTIFIED: u32 = 1;
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
mutex: AtomicU32::new(Self::EMPTY),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_parked(&self) -> bool {
|
||||||
|
self.mutex.load(Ordering::Acquire) == Self::PARKED
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all, fields(this = self as *const Self as usize)))]
|
||||||
|
pub fn park(&self) {
|
||||||
|
if self.mutex.fetch_sub(1, Ordering::Acquire) == Self::NOTIFIED {
|
||||||
|
// The thread was notified, so we can return immediately.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
atomic_wait::wait(&self.mutex, Self::PARKED);
|
||||||
|
|
||||||
|
// We check whether we were notified or woke up spuriously with
|
||||||
|
// acquire ordering in order to make-visible any writes made by the
|
||||||
|
// thread that notified us.
|
||||||
|
if self.mutex.swap(Self::EMPTY, Ordering::Acquire) == Self::NOTIFIED {
|
||||||
|
// The thread was notified, so we can return immediately.
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// spurious wakeup, so we need to re-park.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all, fields(this = self as *const Self as usize)))]
|
||||||
|
pub fn unpark(&self) {
|
||||||
|
// write with Release ordering to ensure that any writes made by this
|
||||||
|
// thread are made-available to the unparked thread.
|
||||||
|
if self.mutex.swap(Self::NOTIFIED, Ordering::Release) == Self::PARKED {
|
||||||
|
// The thread was parked, so we need to notify it.
|
||||||
|
atomic_wait::wake_one(&self.mutex);
|
||||||
|
} else {
|
||||||
|
// The thread was not parked, so we don't need to do anything.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[repr(C)]
|
||||||
|
struct Channel<T = ()> {
|
||||||
|
state: AtomicU8,
|
||||||
|
/// Can only be written only by the `Receiver` and read by the `Sender` if
|
||||||
|
/// `state` is `State::Waiting`.
|
||||||
|
waiting_thread: NonNull<Parker>,
|
||||||
|
/// Can only be written only by the `Sender` and read by the `Receiver` if
|
||||||
|
/// `state` is `State::Ready`.
|
||||||
|
val: UnsafeCell<Option<Box<thread::Result<T>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Channel<T> {
|
||||||
|
fn new(waiting_thread: NonNull<Parker>) -> Self {
|
||||||
|
Self {
|
||||||
|
state: AtomicU8::new(State::Pending as u8),
|
||||||
|
waiting_thread,
|
||||||
|
val: UnsafeCell::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Receiver<T = ()>(Arc<Channel<T>>);
|
||||||
|
|
||||||
|
impl<T: Send> Receiver<T> {
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.0.state.load(Ordering::Acquire) != State::Ready as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
|
pub fn wait(&self) {
|
||||||
|
loop {
|
||||||
|
match self.0.state.compare_exchange(
|
||||||
|
State::Pending as u8,
|
||||||
|
State::Waiting as u8,
|
||||||
|
Ordering::AcqRel,
|
||||||
|
Ordering::Acquire,
|
||||||
|
) {
|
||||||
|
Ok(_) => {
|
||||||
|
// SAFETY:
|
||||||
|
// The `waiting_thread` is set to the current thread's parker
|
||||||
|
// before we park it.
|
||||||
|
unsafe {
|
||||||
|
let thread = self.0.waiting_thread.as_ref();
|
||||||
|
thread.park();
|
||||||
|
}
|
||||||
|
|
||||||
|
// we might have been woken up because of a shared job.
|
||||||
|
// In that case, we need to check the state again.
|
||||||
|
if self
|
||||||
|
.0
|
||||||
|
.state
|
||||||
|
.compare_exchange(
|
||||||
|
State::Waiting as u8,
|
||||||
|
State::Pending as u8,
|
||||||
|
Ordering::AcqRel,
|
||||||
|
Ordering::Acquire,
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// The state was changed to `State::Ready` by the `Sender`, so we can return.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(state) if state == State::Ready as u8 => {
|
||||||
|
// The channel is ready, so we can return immediately.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("Receiver is already waiting or consumed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
|
pub fn poll(&self) -> Option<thread::Result<T>> {
|
||||||
|
if self
|
||||||
|
.0
|
||||||
|
.state
|
||||||
|
.compare_exchange(
|
||||||
|
State::Ready as u8,
|
||||||
|
State::Taken as u8,
|
||||||
|
Ordering::AcqRel,
|
||||||
|
Ordering::Acquire,
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
unsafe { Some(self.take()) }
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
|
pub fn recv(self) -> thread::Result<T> {
|
||||||
|
self.wait();
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// To arrive here, either `state` is `State::Ready` or the above
|
||||||
|
// `compare_exchange` succeeded, the thread was parked and then
|
||||||
|
// unparked by the `Sender` *after* the `state` was set to
|
||||||
|
// `State::Ready`.
|
||||||
|
//
|
||||||
|
// In either case, this thread now has unique access to `val`.
|
||||||
|
unsafe { self.take() }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn take(&self) -> thread::Result<T> {
|
||||||
|
assert_eq!(
|
||||||
|
self.0.state.swap(State::Taken as u8, Ordering::Acquire),
|
||||||
|
State::Ready as u8
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = unsafe { (*self.0.val.get()).take().map(|b| *b).unwrap() };
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Sender<T = ()>(Arc<Channel<T>>);
|
||||||
|
|
||||||
|
impl<T: Send> Sender<T> {
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
|
pub fn send(self, val: thread::Result<T>) {
|
||||||
|
// SAFETY:
|
||||||
|
// Only this thread can write to `val` and none can read it
|
||||||
|
// yet.
|
||||||
|
unsafe {
|
||||||
|
*self.0.val.get() = Some(Box::new(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.0.state.swap(State::Ready as u8, Ordering::AcqRel) == State::Waiting as u8 {
|
||||||
|
// SAFETY:
|
||||||
|
// A `Receiver` already wrote its thread to `waiting_thread`
|
||||||
|
// *before* setting the `state` to `State::Waiting`.
|
||||||
|
unsafe {
|
||||||
|
let thread = self.0.waiting_thread.as_ref();
|
||||||
|
thread.unpark();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn channel<T: Send>(thread: NonNull<Parker>) -> (Sender<T>, Receiver<T>) {
|
||||||
|
let channel = Arc::new(Channel::new(thread));
|
||||||
|
|
||||||
|
(Sender(channel.clone()), Receiver(channel))
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
ptr::{self, NonNull},
|
ptr::NonNull,
|
||||||
sync::{
|
sync::{
|
||||||
Arc, OnceLock,
|
Arc, OnceLock,
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
|
@ -9,38 +9,16 @@ use std::{
|
||||||
use alloc::collections::BTreeMap;
|
use alloc::collections::BTreeMap;
|
||||||
|
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
use crossbeam_utils::CachePadded;
|
|
||||||
use parking_lot::{Condvar, Mutex};
|
use parking_lot::{Condvar, Mutex};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
channel::{Parker, Sender},
|
||||||
heartbeat::HeartbeatList,
|
heartbeat::HeartbeatList,
|
||||||
job::{HeapJob, JobSender, QueuedJob as Job, StackJob},
|
job::{HeapJob, Job2 as Job, SharedJob, StackJob},
|
||||||
latch::{AsCoreLatch, MutexLatch, NopLatch, WorkerLatch},
|
util::DropGuard,
|
||||||
workerthread::{HeartbeatThread, WorkerThread},
|
workerthread::{HeartbeatThread, WorkerThread},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Heartbeat {
|
|
||||||
pub latch: MutexLatch,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Heartbeat {
|
|
||||||
pub fn new() -> NonNull<CachePadded<Self>> {
|
|
||||||
let ptr = Box::new(CachePadded::new(Self {
|
|
||||||
latch: MutexLatch::new(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
Box::into_non_null(ptr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_pending(&self) -> bool {
|
|
||||||
self.latch.as_core_latch().poll_heartbeat()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_sleeping(&self) -> bool {
|
|
||||||
self.latch.as_core_latch().is_sleeping()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
shared: Mutex<Shared>,
|
shared: Mutex<Shared>,
|
||||||
pub shared_job: Condvar,
|
pub shared_job: Condvar,
|
||||||
|
@ -49,14 +27,14 @@ pub struct Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Shared {
|
pub(crate) struct Shared {
|
||||||
pub jobs: BTreeMap<usize, NonNull<Job>>,
|
pub jobs: BTreeMap<usize, SharedJob>,
|
||||||
injected_jobs: Vec<NonNull<Job>>,
|
injected_jobs: Vec<SharedJob>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for Shared {}
|
unsafe impl Send for Shared {}
|
||||||
|
|
||||||
impl Shared {
|
impl Shared {
|
||||||
pub fn pop_job(&mut self) -> Option<NonNull<Job>> {
|
pub fn pop_job(&mut self) -> Option<SharedJob> {
|
||||||
// this is unlikely, so make the function cold?
|
// this is unlikely, so make the function cold?
|
||||||
// TODO: profile this
|
// TODO: profile this
|
||||||
if !self.injected_jobs.is_empty() {
|
if !self.injected_jobs.is_empty() {
|
||||||
|
@ -68,18 +46,20 @@ impl Shared {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cold]
|
#[cold]
|
||||||
unsafe fn pop_injected_job(&mut self) -> NonNull<Job> {
|
unsafe fn pop_injected_job(&mut self) -> SharedJob {
|
||||||
self.injected_jobs.pop().unwrap()
|
self.injected_jobs.pop().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
#[inline]
|
|
||||||
pub(crate) fn shared(&self) -> parking_lot::MutexGuard<'_, Shared> {
|
pub(crate) fn shared(&self) -> parking_lot::MutexGuard<'_, Shared> {
|
||||||
self.shared.lock()
|
self.shared.lock()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_threads(num_threads: usize) -> Arc<Self> {
|
pub fn new_with_threads(num_threads: usize) -> Arc<Self> {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::trace!("Creating context with {} threads", num_threads);
|
||||||
|
|
||||||
let this = Arc::new(Self {
|
let this = Arc::new(Self {
|
||||||
shared: Mutex::new(Shared {
|
shared: Mutex::new(Shared {
|
||||||
jobs: BTreeMap::new(),
|
jobs: BTreeMap::new(),
|
||||||
|
@ -90,8 +70,6 @@ impl Context {
|
||||||
heartbeats: HeartbeatList::new(),
|
heartbeats: HeartbeatList::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
tracing::trace!("Creating thread pool with {} threads", num_threads);
|
|
||||||
|
|
||||||
// Create a barrier to synchronize the worker threads and the heartbeat thread
|
// Create a barrier to synchronize the worker threads and the heartbeat thread
|
||||||
let barrier = Arc::new(std::sync::Barrier::new(num_threads + 2));
|
let barrier = Arc::new(std::sync::Barrier::new(num_threads + 2));
|
||||||
|
|
||||||
|
@ -104,8 +82,7 @@ impl Context {
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
let worker = Box::new(WorkerThread::new_in(ctx));
|
let worker = Box::new(WorkerThread::new_in(ctx));
|
||||||
|
|
||||||
barrier.wait();
|
worker.run(barrier);
|
||||||
worker.run();
|
|
||||||
})
|
})
|
||||||
.expect("Failed to spawn worker thread");
|
.expect("Failed to spawn worker thread");
|
||||||
}
|
}
|
||||||
|
@ -117,8 +94,7 @@ impl Context {
|
||||||
std::thread::Builder::new()
|
std::thread::Builder::new()
|
||||||
.name("heartbeat-thread".to_string())
|
.name("heartbeat-thread".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
barrier.wait();
|
HeartbeatThread::new(ctx).run(barrier);
|
||||||
HeartbeatThread::new(ctx).run();
|
|
||||||
})
|
})
|
||||||
.expect("Failed to spawn heartbeat thread");
|
.expect("Failed to spawn heartbeat thread");
|
||||||
}
|
}
|
||||||
|
@ -147,7 +123,7 @@ impl Context {
|
||||||
GLOBAL_CONTEXT.get_or_init(|| Self::new())
|
GLOBAL_CONTEXT.get_or_init(|| Self::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inject_job(&self, job: NonNull<Job>) {
|
pub fn inject_job(&self, job: SharedJob) {
|
||||||
let mut shared = self.shared.lock();
|
let mut shared = self.shared.lock();
|
||||||
shared.injected_jobs.push(job);
|
shared.injected_jobs.push(job);
|
||||||
|
|
||||||
|
@ -157,17 +133,20 @@ impl Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// caller should hold the shared lock while calling this
|
/// caller should hold the shared lock while calling this
|
||||||
pub unsafe fn notify_job_shared(&self) {
|
pub unsafe fn notify_job_shared(&self) {
|
||||||
if let Some((i, sender)) = self
|
let heartbeats = self.heartbeats.inner();
|
||||||
.heartbeats
|
if let Some((i, sender)) = heartbeats
|
||||||
.inner()
|
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(_, heartbeat)| heartbeat.is_waiting())
|
.find(|(_, heartbeat)| heartbeat.is_waiting())
|
||||||
|
.or_else(|| heartbeats.iter().next())
|
||||||
{
|
{
|
||||||
|
_ = i;
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::trace!("Notifying worker thread {} about job sharing", i);
|
tracing::trace!("Notifying worker thread {} about job sharing", i);
|
||||||
sender.wake();
|
sender.wake();
|
||||||
} else {
|
} else {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::warn!("No worker found to notify about job sharing");
|
tracing::warn!("No worker found to notify about job sharing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,21 +160,13 @@ impl Context {
|
||||||
// current thread is not in the same context, create a job and inject it into the other thread's context, then wait while working on our jobs.
|
// current thread is not in the same context, create a job and inject it into the other thread's context, then wait while working on our jobs.
|
||||||
|
|
||||||
// SAFETY: we are waiting on this latch in this thread.
|
// SAFETY: we are waiting on this latch in this thread.
|
||||||
let job = StackJob::new(
|
let job = StackJob::new(move |worker: &WorkerThread| f(worker));
|
||||||
move || {
|
|
||||||
let worker = WorkerThread::current_ref()
|
|
||||||
.expect("WorkerThread::run_in_worker called outside of worker thread");
|
|
||||||
|
|
||||||
f(worker)
|
let job = Job::from_stackjob(&job);
|
||||||
},
|
|
||||||
NopLatch,
|
|
||||||
);
|
|
||||||
|
|
||||||
let job = Job::from_stackjob(&job, worker.heartbeat.raw_latch());
|
self.inject_job(job.share(Some(worker.heartbeat.parker())));
|
||||||
|
|
||||||
self.inject_job(Into::into(&job));
|
let t = worker.wait_until_shared_job(&job).unwrap();
|
||||||
|
|
||||||
let t = worker.wait_until_queued_job(&job).unwrap();
|
|
||||||
|
|
||||||
crate::util::unwrap_or_panic(t)
|
crate::util::unwrap_or_panic(t)
|
||||||
}
|
}
|
||||||
|
@ -207,47 +178,47 @@ impl Context {
|
||||||
T: Send,
|
T: Send,
|
||||||
{
|
{
|
||||||
// current thread isn't a worker thread, create job and inject into context
|
// current thread isn't a worker thread, create job and inject into context
|
||||||
let latch = WorkerLatch::new();
|
let parker = Parker::new();
|
||||||
|
|
||||||
let job = StackJob::new(
|
let job = StackJob::new(move |worker: &WorkerThread| f(worker));
|
||||||
move || {
|
|
||||||
let worker = WorkerThread::current_ref()
|
|
||||||
.expect("WorkerThread::run_in_worker called outside of worker thread");
|
|
||||||
|
|
||||||
f(worker)
|
let job = Job::from_stackjob(&job);
|
||||||
},
|
|
||||||
NopLatch,
|
|
||||||
);
|
|
||||||
|
|
||||||
let job = Job::from_stackjob(&job, &raw const latch);
|
self.inject_job(job.share(Some(&parker)));
|
||||||
|
|
||||||
self.inject_job(Into::into(&job));
|
let recv = job.take_receiver().unwrap();
|
||||||
let recv = unsafe { job.as_receiver::<T>() };
|
|
||||||
|
|
||||||
crate::util::unwrap_or_panic(latch.wait_until(|| recv.poll()))
|
crate::util::unwrap_or_panic(recv.recv())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run closure in this context.
|
/// Run closure in this context.
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
pub fn run_in_worker<T, F>(self: &Arc<Self>, f: F) -> T
|
pub fn run_in_worker<T, F>(self: &Arc<Self>, f: F) -> T
|
||||||
where
|
where
|
||||||
T: Send,
|
T: Send,
|
||||||
F: FnOnce(&WorkerThread) -> T + Send,
|
F: FnOnce(&WorkerThread) -> T + Send,
|
||||||
{
|
{
|
||||||
|
let _guard = DropGuard::new(|| {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::trace!("run_in_worker: finished");
|
||||||
|
});
|
||||||
match WorkerThread::current_ref() {
|
match WorkerThread::current_ref() {
|
||||||
Some(worker) => {
|
Some(worker) => {
|
||||||
// check if worker is in the same context
|
// check if worker is in the same context
|
||||||
if Arc::ptr_eq(&worker.context, self) {
|
if Arc::ptr_eq(&worker.context, self) {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::trace!("run_in_worker: current thread");
|
tracing::trace!("run_in_worker: current thread");
|
||||||
f(worker)
|
f(worker)
|
||||||
} else {
|
} else {
|
||||||
// current thread is a worker for a different context
|
// current thread is a worker for a different context
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::trace!("run_in_worker: cross-context");
|
tracing::trace!("run_in_worker: cross-context");
|
||||||
self.run_in_worker_cross(worker, f)
|
self.run_in_worker_cross(worker, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// current thread is not a worker for any context
|
// current thread is not a worker for any context
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::trace!("run_in_worker: inject into context");
|
tracing::trace!("run_in_worker: inject into context");
|
||||||
self.run_in_worker_cold(f)
|
self.run_in_worker_cold(f)
|
||||||
}
|
}
|
||||||
|
@ -260,9 +231,10 @@ impl Context {
|
||||||
where
|
where
|
||||||
F: FnOnce() + Send + 'static,
|
F: FnOnce() + Send + 'static,
|
||||||
{
|
{
|
||||||
let job = Job::from_heapjob(Box::new(HeapJob::new(f)), ptr::null());
|
let job = Job::from_heapjob(HeapJob::new(|_: &WorkerThread| f()));
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::trace!("Context::spawn: spawning job: {:?}", job);
|
tracing::trace!("Context::spawn: spawning job: {:?}", job);
|
||||||
self.inject_job(job);
|
self.inject_job(job.share(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_future<T, F>(self: &Arc<Self>, future: F) -> async_task::Task<T>
|
pub fn spawn_future<T, F>(self: &Arc<Self>, future: F) -> async_task::Task<T>
|
||||||
|
@ -272,24 +244,16 @@ impl Context {
|
||||||
{
|
{
|
||||||
let schedule = move |runnable: Runnable| {
|
let schedule = move |runnable: Runnable| {
|
||||||
#[align(8)]
|
#[align(8)]
|
||||||
unsafe fn harness<T>(this: *const (), job: *const JobSender, _: *const WorkerLatch) {
|
unsafe fn harness<T>(_: &WorkerThread, this: NonNull<()>, _: Option<Sender>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let runnable =
|
let runnable = Runnable::<()>::from_raw(this);
|
||||||
Runnable::<()>::from_raw(NonNull::new_unchecked(this.cast_mut()));
|
|
||||||
runnable.run();
|
runnable.run();
|
||||||
|
|
||||||
// SAFETY: job was turned into raw
|
|
||||||
drop(Box::from_raw(job.cast::<JobSender<T>>().cast_mut()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let job = Box::into_non_null(Box::new(Job::from_harness(
|
let job = Job::<T>::from_harness(harness::<T>, runnable.into_raw());
|
||||||
harness::<T>,
|
|
||||||
runnable.into_raw(),
|
|
||||||
ptr::null(),
|
|
||||||
)));
|
|
||||||
|
|
||||||
self.inject_job(job);
|
self.inject_job(job.share(None));
|
||||||
};
|
};
|
||||||
|
|
||||||
let (runnable, task) = unsafe { async_task::spawn_unchecked(future, schedule) };
|
let (runnable, task) = unsafe { async_task::spawn_unchecked(future, schedule) };
|
||||||
|
@ -324,12 +288,10 @@ where
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::atomic::AtomicU8;
|
use std::sync::atomic::AtomicU8;
|
||||||
|
|
||||||
use tracing_test::traced_test;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(not(miri), traced_test)]
|
#[cfg_attr(all(not(miri), feature = "tracing"), tracing_test::traced_test)]
|
||||||
fn run_in_worker() {
|
fn run_in_worker() {
|
||||||
let ctx = Context::global_context().clone();
|
let ctx = Context::global_context().clone();
|
||||||
let result = ctx.run_in_worker(|_| 42);
|
let result = ctx.run_in_worker(|_| 42);
|
||||||
|
@ -337,7 +299,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(not(miri), traced_test)]
|
#[cfg_attr(all(not(miri), feature = "tracing"), tracing_test::traced_test)]
|
||||||
fn context_spawn_future() {
|
fn context_spawn_future() {
|
||||||
let ctx = Context::global_context().clone();
|
let ctx = Context::global_context().clone();
|
||||||
let task = ctx.spawn_future(async { 42 });
|
let task = ctx.spawn_future(async { 42 });
|
||||||
|
@ -348,7 +310,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(not(miri), traced_test)]
|
#[cfg_attr(all(not(miri), feature = "tracing"), tracing_test::traced_test)]
|
||||||
fn context_spawn_async() {
|
fn context_spawn_async() {
|
||||||
let ctx = Context::global_context().clone();
|
let ctx = Context::global_context().clone();
|
||||||
let task = ctx.spawn_async(|| async { 42 });
|
let task = ctx.spawn_async(|| async { 42 });
|
||||||
|
@ -359,7 +321,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(not(miri), traced_test)]
|
#[cfg_attr(all(not(miri), feature = "tracing"), tracing_test::traced_test)]
|
||||||
fn context_spawn() {
|
fn context_spawn() {
|
||||||
let ctx = Context::global_context().clone();
|
let ctx = Context::global_context().clone();
|
||||||
let counter = Arc::new(AtomicU8::new(0));
|
let counter = Arc::new(AtomicU8::new(0));
|
||||||
|
@ -379,27 +341,25 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(not(miri), traced_test)]
|
#[cfg_attr(all(not(miri), feature = "tracing"), tracing_test::traced_test)]
|
||||||
fn inject_job_and_wake_worker() {
|
fn inject_job_and_wake_worker() {
|
||||||
let ctx = Context::new_with_threads(1);
|
let ctx = Context::new_with_threads(1);
|
||||||
let counter = Arc::new(AtomicU8::new(0));
|
let counter = Arc::new(AtomicU8::new(0));
|
||||||
|
|
||||||
let waker = WorkerLatch::new();
|
let parker = Parker::new();
|
||||||
|
|
||||||
let job = StackJob::new(
|
let job = StackJob::new({
|
||||||
{
|
let counter = counter.clone();
|
||||||
let counter = counter.clone();
|
move |_: &WorkerThread| {
|
||||||
move || {
|
#[cfg(feature = "tracing")]
|
||||||
tracing::info!("Job running");
|
tracing::info!("Job running");
|
||||||
counter.fetch_add(1, Ordering::SeqCst);
|
counter.fetch_add(1, Ordering::SeqCst);
|
||||||
|
|
||||||
42
|
42
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
NopLatch,
|
|
||||||
);
|
|
||||||
|
|
||||||
let job = Job::from_stackjob(&job, &raw const waker);
|
let job = Job::from_stackjob(&job);
|
||||||
|
|
||||||
// wait for the worker to sleep
|
// wait for the worker to sleep
|
||||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
@ -412,11 +372,11 @@ mod tests {
|
||||||
assert!(heartbeat.is_waiting());
|
assert!(heartbeat.is_waiting());
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.inject_job(Into::into(&job));
|
ctx.inject_job(job.share(Some(&parker)));
|
||||||
|
|
||||||
// Wait for the job to be executed
|
// Wait for the job to be executed
|
||||||
let recv = unsafe { job.as_receiver::<i32>() };
|
let recv = job.take_receiver().unwrap();
|
||||||
let result = waker.wait_until(|| recv.poll());
|
let result = recv.recv();
|
||||||
let result = crate::util::unwrap_or_panic(result);
|
let result = crate::util::unwrap_or_panic(result);
|
||||||
assert_eq!(result, 42);
|
assert_eq!(result, 42);
|
||||||
assert_eq!(counter.load(Ordering::SeqCst), 1);
|
assert_eq!(counter.load(Ordering::SeqCst), 1);
|
||||||
|
|
|
@ -12,7 +12,7 @@ use std::{
|
||||||
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
use crate::latch::WorkerLatch;
|
use crate::channel::Parker;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct HeartbeatList {
|
pub struct HeartbeatList {
|
||||||
|
@ -27,6 +27,8 @@ impl HeartbeatList {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn notify_nth(&self, n: usize) {
|
pub fn notify_nth(&self, n: usize) {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::trace!("notifying worker-{}", n);
|
||||||
self.inner.lock().notify_nth(n);
|
self.inner.lock().notify_nth(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,13 +127,13 @@ impl Drop for OwnedHeartbeatReceiver {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Heartbeat {
|
pub struct Heartbeat {
|
||||||
ptr: NonNull<(AtomicBool, WorkerLatch)>,
|
ptr: NonNull<(AtomicBool, Parker)>,
|
||||||
i: u64,
|
i: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HeartbeatReceiver {
|
pub struct HeartbeatReceiver {
|
||||||
ptr: NonNull<(AtomicBool, WorkerLatch)>,
|
ptr: NonNull<(AtomicBool, Parker)>,
|
||||||
i: u64,
|
i: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +151,7 @@ impl Drop for Heartbeat {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HeartbeatSender {
|
pub struct HeartbeatSender {
|
||||||
ptr: NonNull<(AtomicBool, WorkerLatch)>,
|
ptr: NonNull<(AtomicBool, Parker)>,
|
||||||
pub last_heartbeat: Instant,
|
pub last_heartbeat: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +163,7 @@ impl Heartbeat {
|
||||||
// `AtomicBool` is `Sync` and `Send`, so it can be safely shared between threads.
|
// `AtomicBool` is `Sync` and `Send`, so it can be safely shared between threads.
|
||||||
let ptr = NonNull::new(Box::into_raw(Box::new((
|
let ptr = NonNull::new(Box::into_raw(Box::new((
|
||||||
AtomicBool::new(true),
|
AtomicBool::new(true),
|
||||||
WorkerLatch::new(),
|
Parker::new(),
|
||||||
))))
|
))))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Self { ptr, i }
|
Self { ptr, i }
|
||||||
|
@ -200,15 +202,7 @@ impl HeartbeatReceiver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait(&self) {
|
pub fn parker(&self) -> &Parker {
|
||||||
unsafe { self.ptr.as_ref().1.wait() };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn raw_latch(&self) -> *const WorkerLatch {
|
|
||||||
unsafe { &raw const self.ptr.as_ref().1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn latch(&self) -> &WorkerLatch {
|
|
||||||
unsafe { &self.ptr.as_ref().1 }
|
unsafe { &self.ptr.as_ref().1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,13 +220,13 @@ impl HeartbeatSender {
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// `AtomicBool` is `Sync` and `Send`, so it can be safely shared between threads.
|
// `AtomicBool` is `Sync` and `Send`, so it can be safely shared between threads.
|
||||||
unsafe { self.ptr.as_ref().0.store(true, Ordering::Relaxed) };
|
unsafe { self.ptr.as_ref().0.store(true, Ordering::Relaxed) };
|
||||||
self.last_heartbeat = Instant::now();
|
// self.last_heartbeat = Instant::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_waiting(&self) -> bool {
|
pub fn is_waiting(&self) -> bool {
|
||||||
unsafe { self.ptr.as_ref().1.is_waiting() }
|
unsafe { self.ptr.as_ref().1.is_parked() }
|
||||||
}
|
}
|
||||||
pub fn wake(&self) {
|
pub fn wake(&self) {
|
||||||
unsafe { self.ptr.as_ref().1.wake() };
|
unsafe { self.ptr.as_ref().1.unpark() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1427
distaff/src/job.rs
1427
distaff/src/job.rs
File diff suppressed because it is too large
Load diff
|
@ -1,41 +1,48 @@
|
||||||
|
#[cfg(feature = "metrics")]
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use std::{hint::cold_path, sync::Arc};
|
use std::{hint::cold_path, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::Context,
|
context::Context,
|
||||||
job::{QueuedJob as Job, StackJob},
|
job::{
|
||||||
latch::NopLatch,
|
Job2 as Job, StackJob,
|
||||||
|
traits::{InlineJob, IntoJob},
|
||||||
|
},
|
||||||
workerthread::WorkerThread,
|
workerthread::WorkerThread,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl WorkerThread {
|
impl WorkerThread {
|
||||||
#[inline]
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
|
||||||
fn join_seq<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
|
fn join_seq<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
|
||||||
where
|
where
|
||||||
A: FnOnce() -> RA,
|
A: FnOnce(&WorkerThread) -> RA,
|
||||||
B: FnOnce() -> RB,
|
B: FnOnce(&WorkerThread) -> RB,
|
||||||
{
|
{
|
||||||
let span = tracing::trace_span!("join_seq");
|
let rb = b(self);
|
||||||
let _guard = span.enter();
|
let ra = a(self);
|
||||||
|
|
||||||
let rb = b();
|
|
||||||
let ra = a();
|
|
||||||
|
|
||||||
(ra, rb)
|
(ra, rb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn join_heartbeat_every<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
|
||||||
|
where
|
||||||
|
A: FnOnce(&WorkerThread) -> RA + Send,
|
||||||
|
B: FnOnce(&WorkerThread) -> RB,
|
||||||
|
RA: Send,
|
||||||
|
{
|
||||||
|
// self.join_heartbeat_every_inner::<A, B, RA, RB, 2>(a, b)
|
||||||
|
self.join_heartbeat(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
/// This function must be called from a worker thread.
|
/// This function must be called from a worker thread.
|
||||||
#[inline]
|
#[allow(dead_code)]
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[inline(always)]
|
||||||
pub(crate) fn join_heartbeat_every<A, B, RA, RB, const TIMES: usize>(
|
fn join_heartbeat_every_inner<A, B, RA, RB, const TIMES: usize>(&self, a: A, b: B) -> (RA, RB)
|
||||||
&self,
|
|
||||||
a: A,
|
|
||||||
b: B,
|
|
||||||
) -> (RA, RB)
|
|
||||||
where
|
where
|
||||||
RA: Send,
|
RA: Send,
|
||||||
A: FnOnce() -> RA + Send,
|
A: FnOnce(&WorkerThread) -> RA + Send,
|
||||||
B: FnOnce() -> RB,
|
B: FnOnce(&WorkerThread) -> RB,
|
||||||
{
|
{
|
||||||
// SAFETY: each worker is only ever used by one thread, so this is safe.
|
// SAFETY: each worker is only ever used by one thread, so this is safe.
|
||||||
let count = self.join_count.get();
|
let count = self.join_count.get();
|
||||||
|
@ -48,10 +55,6 @@ impl WorkerThread {
|
||||||
// SAFETY: this function runs in a worker thread, so we can access the queue safely.
|
// SAFETY: this function runs in a worker thread, so we can access the queue safely.
|
||||||
if count == 0 || queue_len < 3 {
|
if count == 0 || queue_len < 3 {
|
||||||
cold_path();
|
cold_path();
|
||||||
tracing::trace!(
|
|
||||||
queue_len = queue_len,
|
|
||||||
"join_heartbeat_every: using heartbeat join",
|
|
||||||
);
|
|
||||||
self.join_heartbeat(a, b)
|
self.join_heartbeat(a, b)
|
||||||
} else {
|
} else {
|
||||||
self.join_seq(a, b)
|
self.join_seq(a, b)
|
||||||
|
@ -59,53 +62,162 @@ impl WorkerThread {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function must be called from a worker thread.
|
/// This function must be called from a worker thread.
|
||||||
#[inline]
|
#[allow(dead_code)]
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
fn join_heartbeat<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
|
pub(crate) fn join_heartbeat2_every<A, B, RA, RB, const TIMES: usize>(
|
||||||
|
&self,
|
||||||
|
a: A,
|
||||||
|
b: B,
|
||||||
|
) -> (RA, RB)
|
||||||
where
|
where
|
||||||
RA: Send,
|
RA: Send,
|
||||||
A: FnOnce() -> RA + Send,
|
A: InlineJob<RA> + Copy,
|
||||||
B: FnOnce() -> RB,
|
B: FnOnce(&WorkerThread) -> RB,
|
||||||
|
{
|
||||||
|
// SAFETY: each worker is only ever used by one thread, so this is safe.
|
||||||
|
let count = self.join_count.get();
|
||||||
|
let queue_len = unsafe { self.queue.as_ref_unchecked().len() };
|
||||||
|
self.join_count.set(count.wrapping_add(1) % TIMES as u8);
|
||||||
|
|
||||||
|
// TODO: add counter to job queue, check for low job count to decide whether to use heartbeat or seq.
|
||||||
|
// see: chili
|
||||||
|
|
||||||
|
// SAFETY: this function runs in a worker thread, so we can access the queue safely.
|
||||||
|
if count == 0 || queue_len < 3 {
|
||||||
|
cold_path();
|
||||||
|
self.join_heartbeat2(a, b)
|
||||||
|
} else {
|
||||||
|
(a.run_inline(self), b(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
|
pub(crate) fn join_heartbeat2<RA, A, B, RB>(&self, a: A, b: B) -> (RA, RB)
|
||||||
|
where
|
||||||
|
B: FnOnce(&WorkerThread) -> RB,
|
||||||
|
A: InlineJob<RA> + Copy,
|
||||||
|
RA: Send,
|
||||||
{
|
{
|
||||||
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
|
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
|
||||||
|
|
||||||
let a = StackJob::new(a, NopLatch);
|
#[cfg(feature = "metrics")]
|
||||||
let job = Job::from_stackjob(&a, self.heartbeat.raw_latch());
|
self.metrics.num_joins.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
|
let job = a.into_job();
|
||||||
|
|
||||||
self.push_back(&job);
|
self.push_back(&job);
|
||||||
|
|
||||||
self.tick();
|
self.tick();
|
||||||
|
|
||||||
let rb = match catch_unwind(AssertUnwindSafe(|| b())) {
|
let rb = match catch_unwind(AssertUnwindSafe(|| b(self))) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(payload) => {
|
Err(payload) => {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::debug!("join_heartbeat: b panicked, waiting for a to finish");
|
tracing::debug!("join_heartbeat: b panicked, waiting for a to finish");
|
||||||
cold_path();
|
cold_path();
|
||||||
|
|
||||||
// if b panicked, we need to wait for a to finish
|
// if b panicked, we need to wait for a to finish
|
||||||
self.wait_until_latch(&job);
|
let mut receiver = job.take_receiver();
|
||||||
|
self.wait_until_pred(|| match &receiver {
|
||||||
|
Some(recv) => recv.poll().is_some(),
|
||||||
|
None => {
|
||||||
|
receiver = job.take_receiver();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
resume_unwind(payload);
|
resume_unwind(payload);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let ra = if !job.is_shared() {
|
let ra = if let Some(recv) = job.take_receiver() {
|
||||||
tracing::trace!("join_heartbeat: job is not shared, running a() inline");
|
match self.wait_until_recv(recv) {
|
||||||
// we pushed the job to the back of the queue, any `join`s called by `b` on this worker thread will have already popped their job, or seen it be executed.
|
|
||||||
self.pop_back();
|
|
||||||
|
|
||||||
// a is allowed to panic here, because we already finished b.
|
|
||||||
unsafe { a.unwrap()() }
|
|
||||||
} else {
|
|
||||||
match self.wait_until_queued_job(&job) {
|
|
||||||
Some(t) => crate::util::unwrap_or_panic(t),
|
Some(t) => crate::util::unwrap_or_panic(t),
|
||||||
None => {
|
None => {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
"join_heartbeat: job was shared, but reclaimed, running a() inline"
|
"join_heartbeat: job was shared, but reclaimed, running a() inline"
|
||||||
);
|
);
|
||||||
// the job was shared, but not yet stolen, so we get to run the
|
// the job was shared, but not yet stolen, so we get to run the
|
||||||
// job inline
|
// job inline
|
||||||
unsafe { a.unwrap()() }
|
a.run_inline(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
self.pop_back();
|
||||||
|
|
||||||
|
// SAFETY: we just popped the job from the queue, so it is safe to unwrap.
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::trace!("join_heartbeat: job was not shared, running a() inline");
|
||||||
|
a.run_inline(self)
|
||||||
|
};
|
||||||
|
|
||||||
|
(ra, rb)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function must be called from a worker thread.
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
|
fn join_heartbeat<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
|
||||||
|
where
|
||||||
|
RA: Send,
|
||||||
|
A: FnOnce(&WorkerThread) -> RA + Send,
|
||||||
|
B: FnOnce(&WorkerThread) -> RB,
|
||||||
|
{
|
||||||
|
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
|
||||||
|
|
||||||
|
#[cfg(feature = "metrics")]
|
||||||
|
self.metrics.num_joins.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
|
let a = StackJob::new(a);
|
||||||
|
let job = Job::from_stackjob(&a);
|
||||||
|
|
||||||
|
self.push_back(&job);
|
||||||
|
|
||||||
|
self.tick();
|
||||||
|
|
||||||
|
let rb = match catch_unwind(AssertUnwindSafe(|| b(self))) {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(payload) => {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::debug!("join_heartbeat: b panicked, waiting for a to finish");
|
||||||
|
cold_path();
|
||||||
|
|
||||||
|
// if b panicked, we need to wait for a to finish
|
||||||
|
let mut receiver = job.take_receiver();
|
||||||
|
self.wait_until_pred(|| match &receiver {
|
||||||
|
Some(recv) => recv.poll().is_some(),
|
||||||
|
None => {
|
||||||
|
receiver = job.take_receiver();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
resume_unwind(payload);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let ra = if let Some(recv) = job.take_receiver() {
|
||||||
|
match self.wait_until_recv(recv) {
|
||||||
|
Some(t) => crate::util::unwrap_or_panic(t),
|
||||||
|
None => {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::trace!(
|
||||||
|
"join_heartbeat: job was shared, but reclaimed, running a() inline"
|
||||||
|
);
|
||||||
|
// the job was shared, but not yet stolen, so we get to run the
|
||||||
|
// job inline
|
||||||
|
unsafe { a.unwrap()(self) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.pop_back();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// SAFETY: we just popped the job from the queue, so it is safe to unwrap.
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::trace!("join_heartbeat: job was not shared, running a() inline");
|
||||||
|
a.unwrap()(self)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
(ra, rb)
|
(ra, rb)
|
||||||
|
@ -113,7 +225,6 @@ impl WorkerThread {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
#[inline]
|
|
||||||
pub fn join<A, B, RA, RB>(self: &Arc<Self>, a: A, b: B) -> (RA, RB)
|
pub fn join<A, B, RA, RB>(self: &Arc<Self>, a: A, b: B) -> (RA, RB)
|
||||||
where
|
where
|
||||||
A: FnOnce() -> RA + Send,
|
A: FnOnce() -> RA + Send,
|
||||||
|
@ -122,12 +233,13 @@ impl Context {
|
||||||
RB: Send,
|
RB: Send,
|
||||||
{
|
{
|
||||||
// SAFETY: join_heartbeat_every is safe to call from a worker thread.
|
// SAFETY: join_heartbeat_every is safe to call from a worker thread.
|
||||||
self.run_in_worker(move |worker| worker.join_heartbeat_every::<_, _, _, _, 64>(a, b))
|
self.run_in_worker(move |worker| {
|
||||||
|
worker.join_heartbeat_every::<_, _, _, _>(|_| a(), |_| b())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// run two closures potentially in parallel, in the global threadpool.
|
/// run two closures potentially in parallel, in the global threadpool.
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn join<A, B, RA, RB>(a: A, b: B) -> (RA, RB)
|
pub fn join<A, B, RA, RB>(a: A, b: B) -> (RA, RB)
|
||||||
where
|
where
|
||||||
A: FnOnce() -> RA + Send,
|
A: FnOnce() -> RA + Send,
|
||||||
|
|
|
@ -2,19 +2,11 @@ use core::{
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
sync::atomic::{AtomicUsize, Ordering},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::sync::atomic::{AtomicPtr, AtomicU8};
|
||||||
cell::UnsafeCell,
|
|
||||||
mem,
|
|
||||||
ops::DerefMut,
|
|
||||||
sync::{
|
|
||||||
Arc,
|
|
||||||
atomic::{AtomicPtr, AtomicU8},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use parking_lot::{Condvar, Mutex};
|
use parking_lot::{Condvar, Mutex};
|
||||||
|
|
||||||
use crate::{WorkerThread, context::Context};
|
use crate::channel::Parker;
|
||||||
|
|
||||||
pub trait Latch {
|
pub trait Latch {
|
||||||
unsafe fn set_raw(this: *const Self);
|
unsafe fn set_raw(this: *const Self);
|
||||||
|
@ -199,21 +191,20 @@ impl Probe for NopLatch {
|
||||||
|
|
||||||
pub struct CountLatch {
|
pub struct CountLatch {
|
||||||
count: AtomicUsize,
|
count: AtomicUsize,
|
||||||
inner: AtomicPtr<WorkerLatch>,
|
inner: AtomicPtr<Parker>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CountLatch {
|
impl CountLatch {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn new(inner: *const WorkerLatch) -> Self {
|
pub const fn new(inner: *const Parker) -> Self {
|
||||||
Self {
|
Self {
|
||||||
count: AtomicUsize::new(0),
|
count: AtomicUsize::new(0),
|
||||||
inner: AtomicPtr::new(inner as *mut WorkerLatch),
|
inner: AtomicPtr::new(inner as *mut Parker),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_inner(&self, inner: *const WorkerLatch) {
|
pub fn set_inner(&self, inner: *const Parker) {
|
||||||
self.inner
|
self.inner.store(inner as *mut Parker, Ordering::Relaxed);
|
||||||
.store(inner as *mut WorkerLatch, Ordering::Relaxed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count(&self) -> usize {
|
pub fn count(&self) -> usize {
|
||||||
|
@ -238,11 +229,12 @@ impl Latch for CountLatch {
|
||||||
unsafe fn set_raw(this: *const Self) {
|
unsafe fn set_raw(this: *const Self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
if (&*this).count.fetch_sub(1, Ordering::Relaxed) == 1 {
|
if (&*this).count.fetch_sub(1, Ordering::Relaxed) == 1 {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::trace!("CountLatch set_raw: count was 1, setting inner latch");
|
tracing::trace!("CountLatch set_raw: count was 1, setting inner latch");
|
||||||
// If the count was 1, we need to set the inner latch.
|
// If the count was 1, we need to set the inner latch.
|
||||||
let inner = (*this).inner.load(Ordering::Relaxed);
|
let inner = (*this).inner.load(Ordering::Relaxed);
|
||||||
if !inner.is_null() {
|
if !inner.is_null() {
|
||||||
(&*inner).wake();
|
(&*inner).unpark();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -341,20 +333,36 @@ impl AsCoreLatch for MutexLatch {
|
||||||
pub struct WorkerLatch {
|
pub struct WorkerLatch {
|
||||||
// this boolean is set when the worker is waiting.
|
// this boolean is set when the worker is waiting.
|
||||||
mutex: Mutex<bool>,
|
mutex: Mutex<bool>,
|
||||||
condvar: AtomicUsize,
|
condvar: Condvar,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkerLatch {
|
impl WorkerLatch {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
mutex: Mutex::new(false),
|
mutex: Mutex::new(false),
|
||||||
condvar: AtomicUsize::new(0),
|
condvar: Condvar::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn lock(&self) {
|
|
||||||
mem::forget(self.mutex.lock());
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(
|
||||||
|
level = "trace",
|
||||||
|
skip_all,
|
||||||
|
fields(this = self as *const Self as usize)
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub fn lock(&self) -> parking_lot::MutexGuard<'_, bool> {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::trace!("aquiring mutex..");
|
||||||
|
let guard = self.mutex.lock();
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::trace!("mutex acquired.");
|
||||||
|
|
||||||
|
guard
|
||||||
}
|
}
|
||||||
pub fn unlock(&self) {
|
|
||||||
|
pub unsafe fn force_unlock(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.mutex.force_unlock();
|
self.mutex.force_unlock();
|
||||||
}
|
}
|
||||||
|
@ -362,133 +370,43 @@ impl WorkerLatch {
|
||||||
|
|
||||||
pub fn wait(&self) {
|
pub fn wait(&self) {
|
||||||
let condvar = &self.condvar;
|
let condvar = &self.condvar;
|
||||||
let mut guard = self.mutex.lock();
|
let mut guard = self.lock();
|
||||||
|
|
||||||
Self::wait_internal(condvar, &mut guard);
|
Self::wait_internal(condvar, &mut guard);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_internal(condvar: &AtomicUsize, guard: &mut parking_lot::MutexGuard<'_, bool>) {
|
fn wait_internal(condvar: &Condvar, guard: &mut parking_lot::MutexGuard<'_, bool>) {
|
||||||
let mutex = parking_lot::MutexGuard::mutex(guard);
|
|
||||||
let key = condvar as *const _ as usize;
|
|
||||||
let lock_addr = mutex as *const _ as usize;
|
|
||||||
let mut requeued = false;
|
|
||||||
|
|
||||||
let state = unsafe { AtomicUsize::from_ptr(condvar as *const _ as *mut usize) };
|
|
||||||
|
|
||||||
**guard = true; // set the mutex to true to indicate that the worker is waiting
|
**guard = true; // set the mutex to true to indicate that the worker is waiting
|
||||||
|
//condvar.wait_for(guard, std::time::Duration::from_micros(100));
|
||||||
unsafe {
|
condvar.wait(guard);
|
||||||
parking_lot_core::park(
|
**guard = false;
|
||||||
key,
|
|
||||||
|| {
|
|
||||||
let old = state.load(Ordering::Relaxed);
|
|
||||||
if old == 0 {
|
|
||||||
state.store(lock_addr, Ordering::Relaxed);
|
|
||||||
} else if old != lock_addr {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
},
|
|
||||||
|| {
|
|
||||||
mutex.force_unlock();
|
|
||||||
},
|
|
||||||
|k, was_last_thread| {
|
|
||||||
requeued = k != key;
|
|
||||||
if !requeued && was_last_thread {
|
|
||||||
state.store(0, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
parking_lot_core::DEFAULT_PARK_TOKEN,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// relock
|
|
||||||
|
|
||||||
let mut new = mutex.lock();
|
|
||||||
mem::swap(&mut new, guard);
|
|
||||||
mem::forget(new); // forget the new guard to avoid dropping it
|
|
||||||
|
|
||||||
**guard = false; // reset the mutex to false after waking up
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_with_lock_internal<T>(&self, other: &mut parking_lot::MutexGuard<'_, T>) {
|
#[cfg_attr(
|
||||||
let key = &self.condvar as *const _ as usize;
|
feature = "tracing",
|
||||||
let lock_addr = &self.mutex as *const _ as usize;
|
tracing::instrument(level = "trace", skip_all, fields(
|
||||||
let mut requeued = false;
|
this = self as *const Self as usize,
|
||||||
|
)))]
|
||||||
let mut guard = self.mutex.lock();
|
pub fn wait_unless<F>(&self, mut f: F)
|
||||||
|
|
||||||
let state = unsafe { AtomicUsize::from_ptr(&self.condvar as *const _ as *mut usize) };
|
|
||||||
|
|
||||||
*guard = true; // set the mutex to true to indicate that the worker is waiting
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let token = parking_lot_core::park(
|
|
||||||
key,
|
|
||||||
|| {
|
|
||||||
let old = state.load(Ordering::Relaxed);
|
|
||||||
if old == 0 {
|
|
||||||
state.store(lock_addr, Ordering::Relaxed);
|
|
||||||
} else if old != lock_addr {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
},
|
|
||||||
|| {
|
|
||||||
drop(guard); // drop the guard to release the lock
|
|
||||||
parking_lot::MutexGuard::mutex(&other).force_unlock();
|
|
||||||
},
|
|
||||||
|k, was_last_thread| {
|
|
||||||
requeued = k != key;
|
|
||||||
if !requeued && was_last_thread {
|
|
||||||
state.store(0, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
parking_lot_core::DEFAULT_PARK_TOKEN,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
tracing::trace!(
|
|
||||||
"WorkerLatch wait_with_lock_internal: unparked with token {:?}",
|
|
||||||
token
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// relock
|
|
||||||
let mut other2 = parking_lot::MutexGuard::mutex(&other).lock();
|
|
||||||
tracing::trace!("WorkerLatch wait_with_lock_internal: relocked other");
|
|
||||||
|
|
||||||
// because `other` is logically unlocked, we swap it with `other2` and then forget `other2`
|
|
||||||
core::mem::swap(&mut *other2, &mut *other);
|
|
||||||
core::mem::forget(other2);
|
|
||||||
|
|
||||||
let mut guard = self.mutex.lock();
|
|
||||||
tracing::trace!("WorkerLatch wait_with_lock_internal: relocked self");
|
|
||||||
|
|
||||||
*guard = false; // reset the mutex to false after waking up
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(other))]
|
|
||||||
pub fn wait_with_lock<T>(&self, other: &mut parking_lot::MutexGuard<'_, T>) {
|
|
||||||
self.wait_with_lock_internal(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wait_with_lock_while<T, F>(&self, other: &mut parking_lot::MutexGuard<'_, T>, mut f: F)
|
|
||||||
where
|
where
|
||||||
F: FnMut(&mut T) -> bool,
|
F: FnMut() -> bool,
|
||||||
{
|
{
|
||||||
while f(other.deref_mut()) {
|
let mut guard = self.lock();
|
||||||
self.wait_with_lock_internal(other);
|
if !f() {
|
||||||
|
Self::wait_internal(&self.condvar, &mut guard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(f))]
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(level = "trace", skip_all, fields(
|
||||||
|
this = self as *const Self as usize,
|
||||||
|
)))]
|
||||||
pub fn wait_until<F, T>(&self, mut f: F) -> T
|
pub fn wait_until<F, T>(&self, mut f: F) -> T
|
||||||
where
|
where
|
||||||
F: FnMut() -> Option<T>,
|
F: FnMut() -> Option<T>,
|
||||||
{
|
{
|
||||||
let mut guard = self.mutex.lock();
|
let mut guard = self.lock();
|
||||||
loop {
|
loop {
|
||||||
if let Some(result) = f() {
|
if let Some(result) = f() {
|
||||||
return result;
|
return result;
|
||||||
|
@ -501,14 +419,15 @@ impl WorkerLatch {
|
||||||
*self.mutex.lock()
|
*self.mutex.lock()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace")]
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(level = "trace", skip_all, fields(
|
||||||
|
this = self as *const Self as usize,
|
||||||
|
)))]
|
||||||
fn notify(&self) {
|
fn notify(&self) {
|
||||||
let key = &self.condvar as *const _ as usize;
|
let n = self.condvar.notify_all();
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
unsafe {
|
tracing::trace!("WorkerLatch notify: notified {} threads", n);
|
||||||
let n = parking_lot_core::unpark_all(key, parking_lot_core::DEFAULT_UNPARK_TOKEN);
|
|
||||||
tracing::trace!("WorkerLatch notify_one: unparked {} threads", n);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wake(&self) {
|
pub fn wake(&self) {
|
||||||
|
@ -518,70 +437,10 @@ impl WorkerLatch {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{ptr, sync::Barrier};
|
use std::{ptr, sync::Arc};
|
||||||
|
|
||||||
use tracing_test::traced_test;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg_attr(not(miri), traced_test)]
|
|
||||||
fn worker_latch() {
|
|
||||||
let latch = Arc::new(WorkerLatch::new());
|
|
||||||
let barrier = Arc::new(Barrier::new(2));
|
|
||||||
let mutex = Arc::new(parking_lot::Mutex::new(false));
|
|
||||||
|
|
||||||
let count = Arc::new(AtomicUsize::new(0));
|
|
||||||
|
|
||||||
let thread = std::thread::spawn({
|
|
||||||
let latch = latch.clone();
|
|
||||||
let mutex = mutex.clone();
|
|
||||||
let barrier = barrier.clone();
|
|
||||||
let count = count.clone();
|
|
||||||
|
|
||||||
move || {
|
|
||||||
tracing::info!("Thread waiting on barrier");
|
|
||||||
let mut guard = mutex.lock();
|
|
||||||
barrier.wait();
|
|
||||||
|
|
||||||
tracing::info!("Thread waiting on latch");
|
|
||||||
latch.wait_with_lock(&mut guard);
|
|
||||||
count.fetch_add(1, Ordering::Relaxed);
|
|
||||||
tracing::info!("Thread woke up from latch");
|
|
||||||
barrier.wait();
|
|
||||||
tracing::info!("Thread finished waiting on barrier");
|
|
||||||
count.fetch_add(1, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
assert!(!latch.is_waiting(), "Latch should not be waiting yet");
|
|
||||||
barrier.wait();
|
|
||||||
tracing::info!("Main thread finished waiting on barrier");
|
|
||||||
// lock mutex and notify the thread that isn't yet waiting.
|
|
||||||
{
|
|
||||||
let guard = mutex.lock();
|
|
||||||
tracing::info!("Main thread acquired mutex, waking up thread");
|
|
||||||
assert!(latch.is_waiting(), "Latch should be waiting now");
|
|
||||||
|
|
||||||
latch.wake();
|
|
||||||
tracing::info!("Main thread woke up thread");
|
|
||||||
}
|
|
||||||
assert_eq!(count.load(Ordering::Relaxed), 0, "Count should still be 0");
|
|
||||||
barrier.wait();
|
|
||||||
assert_eq!(
|
|
||||||
count.load(Ordering::Relaxed),
|
|
||||||
1,
|
|
||||||
"Count should be 1 after waking up"
|
|
||||||
);
|
|
||||||
|
|
||||||
thread.join().expect("Thread should join successfully");
|
|
||||||
assert_eq!(
|
|
||||||
count.load(Ordering::Relaxed),
|
|
||||||
2,
|
|
||||||
"Count should be 2 after thread has finished"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_atomic_latch() {
|
fn test_atomic_latch() {
|
||||||
let latch = AtomicLatch::new();
|
let latch = AtomicLatch::new();
|
||||||
|
@ -645,7 +504,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[traced_test]
|
#[cfg_attr(all(not(miri), feature = "tracing"), tracing_test::traced_test)]
|
||||||
fn mutex_latch() {
|
fn mutex_latch() {
|
||||||
let latch = Arc::new(MutexLatch::new());
|
let latch = Arc::new(MutexLatch::new());
|
||||||
assert!(!latch.probe());
|
assert!(!latch.probe());
|
||||||
|
@ -657,8 +516,10 @@ mod tests {
|
||||||
// Test wait functionality
|
// Test wait functionality
|
||||||
let latch_clone = latch.clone();
|
let latch_clone = latch.clone();
|
||||||
let handle = std::thread::spawn(move || {
|
let handle = std::thread::spawn(move || {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::info!("Thread waiting on latch");
|
tracing::info!("Thread waiting on latch");
|
||||||
latch_clone.wait_and_reset();
|
latch_clone.wait_and_reset();
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::info!("Thread woke up from latch");
|
tracing::info!("Thread woke up from latch");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -666,8 +527,10 @@ mod tests {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
assert!(!latch.probe());
|
assert!(!latch.probe());
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::info!("Setting latch from main thread");
|
tracing::info!("Setting latch from main thread");
|
||||||
latch.set();
|
latch.set();
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::info!("Latch set, joining waiting thread");
|
tracing::info!("Latch set, joining waiting thread");
|
||||||
handle.join().expect("Thread should join successfully");
|
handle.join().expect("Thread should join successfully");
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,16 +8,21 @@
|
||||||
box_as_ptr,
|
box_as_ptr,
|
||||||
box_vec_non_null,
|
box_vec_non_null,
|
||||||
strict_provenance_atomic_ptr,
|
strict_provenance_atomic_ptr,
|
||||||
|
btree_extract_if,
|
||||||
|
likely_unlikely,
|
||||||
let_chains
|
let_chains
|
||||||
)]
|
)]
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
|
mod channel;
|
||||||
mod context;
|
mod context;
|
||||||
mod heartbeat;
|
mod heartbeat;
|
||||||
mod job;
|
mod job;
|
||||||
mod join;
|
mod join;
|
||||||
mod latch;
|
mod latch;
|
||||||
|
#[cfg(feature = "metrics")]
|
||||||
|
mod metrics;
|
||||||
mod scope;
|
mod scope;
|
||||||
mod threadpool;
|
mod threadpool;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
12
distaff/src/metrics.rs
Normal file
12
distaff/src/metrics.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
use std::sync::atomic::AtomicU32;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct WorkerMetrics {
|
||||||
|
pub(crate) num_jobs_shared: AtomicU32,
|
||||||
|
pub(crate) num_heartbeats: AtomicU32,
|
||||||
|
pub(crate) num_joins: AtomicU32,
|
||||||
|
pub(crate) num_jobs_reclaimed: AtomicU32,
|
||||||
|
pub(crate) num_jobs_executed: AtomicU32,
|
||||||
|
pub(crate) num_jobs_stolen: AtomicU32,
|
||||||
|
pub(crate) num_sent_to_self: AtomicU32,
|
||||||
|
}
|
|
@ -1,19 +1,25 @@
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
|
panic::{AssertUnwindSafe, catch_unwind},
|
||||||
|
pin::{self, Pin},
|
||||||
ptr::{self, NonNull},
|
ptr::{self, NonNull},
|
||||||
sync::{
|
sync::{
|
||||||
Arc,
|
Arc,
|
||||||
atomic::{AtomicPtr, Ordering},
|
atomic::{AtomicPtr, AtomicUsize, Ordering},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
channel::Sender,
|
||||||
context::Context,
|
context::Context,
|
||||||
job::{HeapJob, JobSender, QueuedJob as Job},
|
job::{
|
||||||
latch::{CountLatch, WorkerLatch},
|
HeapJob, Job2 as Job,
|
||||||
|
traits::{InlineJob, IntoJob},
|
||||||
|
},
|
||||||
|
latch::{CountLatch, Probe},
|
||||||
util::{DropGuard, SendPtr},
|
util::{DropGuard, SendPtr},
|
||||||
workerthread::WorkerThread,
|
workerthread::WorkerThread,
|
||||||
};
|
};
|
||||||
|
@ -29,6 +35,83 @@ use crate::{
|
||||||
// - when a join() job finishes, it's latch is set
|
// - when a join() job finishes, it's latch is set
|
||||||
// - when we wait for a join() job, we loop over the latch until it is set
|
// - when we wait for a join() job, we loop over the latch until it is set
|
||||||
|
|
||||||
|
// a Scope must keep track of:
|
||||||
|
// - The number of async jobs spawned, which is used to determine when the scope
|
||||||
|
// is complete.
|
||||||
|
// - A panic box, which is set when a job panics and is used to resume the panic
|
||||||
|
// when the scope is completed.
|
||||||
|
// - The Parker of the worker on which the scope was created, which is signaled
|
||||||
|
// when the last outstanding async job finishes.
|
||||||
|
// - The current worker thread in order to avoid having to query the
|
||||||
|
// thread-local storage.
|
||||||
|
|
||||||
|
struct ScopeInner {
|
||||||
|
outstanding_jobs: AtomicUsize,
|
||||||
|
parker: NonNull<crate::channel::Parker>,
|
||||||
|
panic: AtomicPtr<Box<dyn Any + Send + 'static>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for ScopeInner {}
|
||||||
|
unsafe impl Sync for ScopeInner {}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Scope<'scope, 'env: 'scope> {
|
||||||
|
inner: SendPtr<ScopeInner>,
|
||||||
|
worker: SendPtr<WorkerThread>,
|
||||||
|
_scope: PhantomData<&'scope mut &'scope ()>,
|
||||||
|
_env: PhantomData<&'env mut &'env ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScopeInner {
|
||||||
|
fn from_worker(worker: &WorkerThread) -> Self {
|
||||||
|
Self {
|
||||||
|
outstanding_jobs: AtomicUsize::new(0),
|
||||||
|
parker: worker.heartbeat.parker().into(),
|
||||||
|
panic: AtomicPtr::new(ptr::null_mut()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn increment(&self) {
|
||||||
|
self.outstanding_jobs.fetch_add(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrement(&self) {
|
||||||
|
if self.outstanding_jobs.fetch_sub(1, Ordering::Relaxed) == 1 {
|
||||||
|
unsafe {
|
||||||
|
self.parker.as_ref().unpark();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn panicked(&self, err: Box<dyn Any + Send + 'static>) {
|
||||||
|
unsafe {
|
||||||
|
let err = Box::into_raw(Box::new(err));
|
||||||
|
if !self
|
||||||
|
.panic
|
||||||
|
.compare_exchange(ptr::null_mut(), err, Ordering::AcqRel, Ordering::Acquire)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
// someone else already set the panic, so we drop the error
|
||||||
|
_ = Box::from_raw(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_propagate_panic(&self) {
|
||||||
|
let err = self.panic.swap(ptr::null_mut(), Ordering::AcqRel);
|
||||||
|
|
||||||
|
if err.is_null() {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// SAFETY: we have exclusive access to the panic error, so we can safely resume it.
|
||||||
|
unsafe {
|
||||||
|
let err = *Box::from_raw(err);
|
||||||
|
std::panic::resume_unwind(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// find below a sketch of an unbalanced tree:
|
// find below a sketch of an unbalanced tree:
|
||||||
// []
|
// []
|
||||||
// / \
|
// / \
|
||||||
|
@ -51,7 +134,7 @@ use crate::{
|
||||||
// - another thread sharing a job
|
// - another thread sharing a job
|
||||||
// - the heartbeat waking up the worker // does this make sense? if the thread was sleeping, it didn't have any work to share.
|
// - the heartbeat waking up the worker // does this make sense? if the thread was sleeping, it didn't have any work to share.
|
||||||
|
|
||||||
pub struct Scope<'scope, 'env: 'scope> {
|
pub struct Scope2<'scope, 'env: 'scope> {
|
||||||
// latch to wait on before the scope finishes
|
// latch to wait on before the scope finishes
|
||||||
job_counter: CountLatch,
|
job_counter: CountLatch,
|
||||||
// local threadpool
|
// local threadpool
|
||||||
|
@ -65,7 +148,7 @@ pub struct Scope<'scope, 'env: 'scope> {
|
||||||
|
|
||||||
pub fn scope<'env, F, R>(f: F) -> R
|
pub fn scope<'env, F, R>(f: F) -> R
|
||||||
where
|
where
|
||||||
F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> R + Send,
|
F: for<'scope> FnOnce(Scope<'scope, 'env>) -> R + Send,
|
||||||
R: Send,
|
R: Send,
|
||||||
{
|
{
|
||||||
scope_with_context(Context::global_context(), f)
|
scope_with_context(Context::global_context(), f)
|
||||||
|
@ -73,41 +156,25 @@ where
|
||||||
|
|
||||||
pub fn scope_with_context<'env, F, R>(context: &Arc<Context>, f: F) -> R
|
pub fn scope_with_context<'env, F, R>(context: &Arc<Context>, f: F) -> R
|
||||||
where
|
where
|
||||||
F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> R + Send,
|
F: for<'scope> FnOnce(Scope<'scope, 'env>) -> R + Send,
|
||||||
R: Send,
|
R: Send,
|
||||||
{
|
{
|
||||||
context.run_in_worker(|worker| {
|
context.run_in_worker(|worker| {
|
||||||
// SAFETY: we call complete() after creating this scope, which
|
// SAFETY: we call complete() after creating this scope, which
|
||||||
// ensures that any jobs spawned from the scope exit before the
|
// ensures that any jobs spawned from the scope exit before the
|
||||||
// scope closes.
|
// scope closes.
|
||||||
let this = unsafe { Scope::from_context(context.clone()) };
|
let inner = pin::pin!(ScopeInner::from_worker(worker));
|
||||||
this.complete(worker, || f(&this))
|
let this = Scope::<'_, 'env>::new(worker, inner.as_ref());
|
||||||
|
this.complete(|| f(this))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'scope, 'env> Scope<'scope, 'env> {
|
impl<'scope, 'env> Scope<'scope, 'env> {
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
|
||||||
fn wait_for_jobs(&self, worker: &WorkerThread) {
|
|
||||||
self.job_counter.set_inner(worker.heartbeat.raw_latch());
|
|
||||||
if self.job_counter.count() > 0 {
|
|
||||||
tracing::trace!("waiting for {} jobs to finish.", self.job_counter.count());
|
|
||||||
tracing::trace!(
|
|
||||||
"thread id: {:?}, jobs: {:?}",
|
|
||||||
worker.heartbeat.index(),
|
|
||||||
unsafe { worker.queue.as_ref_unchecked() }
|
|
||||||
);
|
|
||||||
|
|
||||||
// set worker index in the job counter
|
|
||||||
worker.wait_until_latch(&self.job_counter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// should be called from within a worker thread.
|
/// should be called from within a worker thread.
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
fn complete<F, R>(&self, worker: &WorkerThread, f: F) -> R
|
fn complete<F, R>(&self, f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce() -> R + Send,
|
F: FnOnce() -> R,
|
||||||
R: Send,
|
|
||||||
{
|
{
|
||||||
use std::panic::{AssertUnwindSafe, catch_unwind};
|
use std::panic::{AssertUnwindSafe, catch_unwind};
|
||||||
|
|
||||||
|
@ -119,81 +186,97 @@ impl<'scope, 'env> Scope<'scope, 'env> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.wait_for_jobs(worker);
|
self.wait_for_jobs();
|
||||||
self.maybe_propagate_panic();
|
let inner = self.inner();
|
||||||
|
inner.maybe_propagate_panic();
|
||||||
|
|
||||||
// SAFETY: if result panicked, we would have propagated the panic above.
|
// SAFETY: if result panicked, we would have propagated the panic above.
|
||||||
result.unwrap()
|
result.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// resumes the panic if one happened in this scope.
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
fn wait_for_jobs(&self) {
|
||||||
fn maybe_propagate_panic(&self) {
|
#[cfg(feature = "tracing")]
|
||||||
let err_ptr = self.panic.load(Ordering::Relaxed);
|
tracing::trace!(
|
||||||
if !err_ptr.is_null() {
|
"waiting for {} jobs to finish.",
|
||||||
unsafe {
|
self.inner().outstanding_jobs.load(Ordering::Relaxed)
|
||||||
let err = Box::from_raw(err_ptr);
|
);
|
||||||
std::panic::resume_unwind(*err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// stores the first panic that happened in this scope.
|
self.worker().wait_until_pred(|| {
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
// SAFETY: we are in a worker thread, so the inner is valid.
|
||||||
fn panicked(&self, err: Box<dyn Any + Send + 'static>) {
|
let count = self.inner().outstanding_jobs.load(Ordering::Relaxed);
|
||||||
tracing::debug!("panicked in scope, storing error: {:?}", err);
|
#[cfg(feature = "tracing")]
|
||||||
self.panic.load(Ordering::Relaxed).is_null().then(|| {
|
tracing::trace!("waiting for {} jobs to finish.", count);
|
||||||
use core::mem::ManuallyDrop;
|
count == 0
|
||||||
let mut boxed = ManuallyDrop::new(Box::new(err));
|
|
||||||
|
|
||||||
let err_ptr: *mut Box<dyn Any + Send + 'static> = &mut **boxed;
|
|
||||||
if self
|
|
||||||
.panic
|
|
||||||
.compare_exchange(
|
|
||||||
ptr::null_mut(),
|
|
||||||
err_ptr,
|
|
||||||
Ordering::SeqCst,
|
|
||||||
Ordering::Relaxed,
|
|
||||||
)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
// we successfully set the panic, no need to drop
|
|
||||||
} else {
|
|
||||||
// drop the error, someone else already set it
|
|
||||||
_ = ManuallyDrop::into_inner(boxed);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn<F>(&'scope self, f: F)
|
fn inner(&self) -> &ScopeInner {
|
||||||
where
|
unsafe { self.inner.as_ref() }
|
||||||
F: FnOnce(&'scope Self) + Send,
|
|
||||||
{
|
|
||||||
self.job_counter.increment();
|
|
||||||
|
|
||||||
let this = SendPtr::new_const(self).unwrap();
|
|
||||||
|
|
||||||
let job = Job::from_heapjob(
|
|
||||||
Box::new(HeapJob::new(move || unsafe {
|
|
||||||
use std::panic::{AssertUnwindSafe, catch_unwind};
|
|
||||||
if let Err(payload) = catch_unwind(AssertUnwindSafe(|| f(this.as_ref()))) {
|
|
||||||
this.as_unchecked_ref().panicked(payload);
|
|
||||||
}
|
|
||||||
this.as_unchecked_ref().job_counter.decrement();
|
|
||||||
})),
|
|
||||||
ptr::null(),
|
|
||||||
);
|
|
||||||
|
|
||||||
tracing::trace!("allocated heapjob");
|
|
||||||
|
|
||||||
WorkerThread::current_ref()
|
|
||||||
.expect("spawn is run in workerthread.")
|
|
||||||
.push_front(job.as_ptr());
|
|
||||||
|
|
||||||
tracing::trace!("leaked heapjob");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_future<T, F>(&'scope self, future: F) -> async_task::Task<T>
|
/// stores the first panic that happened in this scope.
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
|
fn panicked(&self, err: Box<dyn Any + Send + 'static>) {
|
||||||
|
self.inner().panicked(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn<F>(&self, f: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(Self) + Send,
|
||||||
|
{
|
||||||
|
struct SpawnedJob<F> {
|
||||||
|
f: F,
|
||||||
|
inner: SendPtr<ScopeInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> SpawnedJob<F> {
|
||||||
|
fn new<'scope, 'env, T>(f: F, inner: SendPtr<ScopeInner>) -> Job
|
||||||
|
where
|
||||||
|
F: FnOnce(Scope<'scope, 'env>) -> T + Send,
|
||||||
|
'env: 'scope,
|
||||||
|
T: Send,
|
||||||
|
{
|
||||||
|
Job::from_harness(
|
||||||
|
Self::harness,
|
||||||
|
Box::into_non_null(Box::new(Self { f, inner })).cast(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn harness<'scope, 'env, T>(
|
||||||
|
worker: &WorkerThread,
|
||||||
|
this: NonNull<()>,
|
||||||
|
_: Option<Sender>,
|
||||||
|
) where
|
||||||
|
F: FnOnce(Scope<'scope, 'env>) -> T + Send,
|
||||||
|
'env: 'scope,
|
||||||
|
T: Send,
|
||||||
|
{
|
||||||
|
let Self { f, inner } =
|
||||||
|
unsafe { *Box::<SpawnedJob<F>>::from_non_null(this.cast()) };
|
||||||
|
let scope = unsafe { Scope::<'scope, 'env>::new_unchecked(worker, inner) };
|
||||||
|
|
||||||
|
// SAFETY: we are in a worker thread, so the inner is valid.
|
||||||
|
(f)(scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner().increment();
|
||||||
|
let job = SpawnedJob::new(
|
||||||
|
move |scope| {
|
||||||
|
if let Err(payload) = catch_unwind(AssertUnwindSafe(|| f(scope))) {
|
||||||
|
scope.inner().panicked(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.inner().decrement();
|
||||||
|
},
|
||||||
|
self.inner,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.context().inject_job(job.share(None));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_future<T, F>(&self, future: F) -> async_task::Task<T>
|
||||||
where
|
where
|
||||||
F: Future<Output = T> + Send + 'scope,
|
F: Future<Output = T> + Send + 'scope,
|
||||||
T: Send + 'scope,
|
T: Send + 'scope,
|
||||||
|
@ -202,9 +285,9 @@ impl<'scope, 'env> Scope<'scope, 'env> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn spawn_async<T, Fut, Fn>(&'scope self, f: Fn) -> async_task::Task<T>
|
pub fn spawn_async<T, Fut, Fn>(&self, f: Fn) -> async_task::Task<T>
|
||||||
where
|
where
|
||||||
Fn: FnOnce(&'scope Self) -> Fut + Send + 'scope,
|
Fn: FnOnce(Self) -> Fut + Send + 'scope,
|
||||||
Fut: Future<Output = T> + Send + 'scope,
|
Fut: Future<Output = T> + Send + 'scope,
|
||||||
T: Send + 'scope,
|
T: Send + 'scope,
|
||||||
{
|
{
|
||||||
|
@ -212,51 +295,43 @@ impl<'scope, 'env> Scope<'scope, 'env> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn spawn_async_internal<T, Fut, Fn>(&'scope self, f: Fn) -> async_task::Task<T>
|
fn spawn_async_internal<T, Fut, Fn>(&self, f: Fn) -> async_task::Task<T>
|
||||||
where
|
where
|
||||||
Fn: FnOnce(&'scope Self) -> Fut + Send + 'scope,
|
Fn: FnOnce(Self) -> Fut + Send + 'scope,
|
||||||
Fut: Future<Output = T> + Send + 'scope,
|
Fut: Future<Output = T> + Send + 'scope,
|
||||||
T: Send + 'scope,
|
T: Send + 'scope,
|
||||||
{
|
{
|
||||||
self.job_counter.increment();
|
self.inner().increment();
|
||||||
|
|
||||||
let this = SendPtr::new_const(self).unwrap();
|
// TODO: make sure this worker lasts long enough for the
|
||||||
// let this = SendPtr::new_const(&self.job_counter).unwrap();
|
// reference to remain valid for the duration of the future.
|
||||||
|
let scope = unsafe { Self::new_unchecked(self.worker.as_ref(), self.inner) };
|
||||||
|
|
||||||
let future = async move {
|
let future = async move {
|
||||||
// SAFETY: this is valid until we decrement the job counter.
|
let _guard = DropGuard::new(move || {
|
||||||
unsafe {
|
scope.inner().decrement();
|
||||||
let _guard = DropGuard::new(move || {
|
});
|
||||||
this.as_unchecked_ref().job_counter.decrement();
|
|
||||||
});
|
// TODO: handle panics here
|
||||||
// TODO: handle panics here
|
f(scope).await
|
||||||
f(this.as_ref()).await
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let schedule = move |runnable: Runnable| {
|
let schedule = move |runnable: Runnable| {
|
||||||
#[align(8)]
|
#[align(8)]
|
||||||
unsafe fn harness(this: *const (), job: *const JobSender, _: *const WorkerLatch) {
|
unsafe fn harness(_: &WorkerThread, this: NonNull<()>, _: Option<Sender>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let runnable =
|
let runnable = Runnable::<()>::from_raw(this.cast());
|
||||||
Runnable::<()>::from_raw(NonNull::new_unchecked(this.cast_mut()));
|
|
||||||
runnable.run();
|
runnable.run();
|
||||||
|
|
||||||
// SAFETY: job was turned into raw
|
|
||||||
drop(Box::from_raw(job.cast_mut()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let job = Box::into_raw(Box::new(Job::from_harness(
|
let job = Job::<()>::from_harness(harness, runnable.into_raw());
|
||||||
harness,
|
|
||||||
runnable.into_raw(),
|
|
||||||
ptr::null(),
|
|
||||||
)));
|
|
||||||
|
|
||||||
// casting into Job<()> here
|
// casting into Job<()> here
|
||||||
WorkerThread::current_ref()
|
self.context().inject_job(job.share(None));
|
||||||
.expect("spawn_async_internal is run in workerthread.")
|
// WorkerThread::current_ref()
|
||||||
.push_front(job);
|
// .expect("spawn_async_internal is run in workerthread.")
|
||||||
|
// .push_front(job);
|
||||||
};
|
};
|
||||||
|
|
||||||
let (runnable, task) = unsafe { async_task::spawn_unchecked(future, schedule) };
|
let (runnable, task) = unsafe { async_task::spawn_unchecked(future, schedule) };
|
||||||
|
@ -266,51 +341,176 @@ impl<'scope, 'env> Scope<'scope, 'env> {
|
||||||
task
|
task
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn join<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
|
||||||
pub fn join<A, B, RA, RB>(&'scope self, a: A, b: B) -> (RA, RB)
|
|
||||||
where
|
where
|
||||||
RA: Send,
|
RA: Send,
|
||||||
RB: Send,
|
A: FnOnce(Self) -> RA + Send,
|
||||||
A: FnOnce(&'scope Self) -> RA + Send,
|
B: FnOnce(Self) -> RB,
|
||||||
B: FnOnce(&'scope Self) -> RB + Send,
|
|
||||||
{
|
{
|
||||||
let worker = WorkerThread::current_ref().expect("join is run in workerthread.");
|
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
|
||||||
let this = SendPtr::new_const(self).unwrap();
|
use std::{
|
||||||
|
cell::UnsafeCell,
|
||||||
|
mem::{self, ManuallyDrop},
|
||||||
|
};
|
||||||
|
|
||||||
worker.join_heartbeat_every::<_, _, _, _, 64>(
|
let worker = self.worker();
|
||||||
|
|
||||||
|
struct ScopeJob<F> {
|
||||||
|
f: UnsafeCell<ManuallyDrop<F>>,
|
||||||
|
inner: SendPtr<ScopeInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> ScopeJob<F> {
|
||||||
|
fn new(f: F, inner: SendPtr<ScopeInner>) -> Self {
|
||||||
|
Self {
|
||||||
|
f: UnsafeCell::new(ManuallyDrop::new(f)),
|
||||||
|
inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_job<'scope, 'env, T>(&self) -> Job<T>
|
||||||
|
where
|
||||||
|
F: FnOnce(Scope<'scope, 'env>) -> T + Send,
|
||||||
|
'env: 'scope,
|
||||||
|
T: Send,
|
||||||
{
|
{
|
||||||
let this = this;
|
Job::from_harness(Self::harness, NonNull::from(self).cast())
|
||||||
move || a(unsafe { this.as_ref() })
|
}
|
||||||
},
|
|
||||||
|
unsafe fn unwrap(&self) -> F {
|
||||||
|
unsafe { ManuallyDrop::take(&mut *self.f.get()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn harness<'scope, 'env, T>(
|
||||||
|
worker: &WorkerThread,
|
||||||
|
this: NonNull<()>,
|
||||||
|
sender: Option<Sender>,
|
||||||
|
) where
|
||||||
|
F: FnOnce(Scope<'scope, 'env>) -> T + Send,
|
||||||
|
'env: 'scope,
|
||||||
|
T: Send,
|
||||||
{
|
{
|
||||||
let this = this;
|
let this: &ScopeJob<F> = unsafe { this.cast().as_ref() };
|
||||||
move || b(unsafe { this.as_ref() })
|
let f = unsafe { this.unwrap() };
|
||||||
},
|
let scope = unsafe { Scope::<'scope, 'env>::new_unchecked(worker, this.inner) };
|
||||||
)
|
let sender: Sender<T> = unsafe { mem::transmute(sender) };
|
||||||
|
|
||||||
|
// SAFETY: we are in a worker thread, so the inner is valid.
|
||||||
|
sender.send(catch_unwind(AssertUnwindSafe(|| f(scope))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'scope, 'env, F, T> IntoJob<T> for &ScopeJob<F>
|
||||||
|
where
|
||||||
|
F: FnOnce(Scope<'scope, 'env>) -> T + Send,
|
||||||
|
'env: 'scope,
|
||||||
|
T: Send,
|
||||||
|
{
|
||||||
|
fn into_job(self) -> Job<T> {
|
||||||
|
self.into_job()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'scope, 'env, F, T> InlineJob<T> for &ScopeJob<F>
|
||||||
|
where
|
||||||
|
F: FnOnce(Scope<'scope, 'env>) -> T + Send,
|
||||||
|
'env: 'scope,
|
||||||
|
T: Send,
|
||||||
|
{
|
||||||
|
fn run_inline(self, worker: &WorkerThread) -> T {
|
||||||
|
unsafe { self.unwrap()(Scope::<'scope, 'env>::new_unchecked(worker, self.inner)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return worker
|
||||||
|
.join_heartbeat2_every::<_, _, _, _, 64>(&ScopeJob::new(a, self.inner), |_| b(*self));
|
||||||
|
|
||||||
|
// let stack = ScopeJob::new(a, self.inner);
|
||||||
|
// let job = ScopeJob::into_job(&stack);
|
||||||
|
|
||||||
|
// worker.push_back(&job);
|
||||||
|
|
||||||
|
// worker.tick();
|
||||||
|
|
||||||
|
// let rb = match catch_unwind(AssertUnwindSafe(|| b(*self))) {
|
||||||
|
// Ok(val) => val,
|
||||||
|
// Err(payload) => {
|
||||||
|
// #[cfg(feature = "tracing")]
|
||||||
|
// tracing::debug!("join_heartbeat: b panicked, waiting for a to finish");
|
||||||
|
// std::hint::cold_path();
|
||||||
|
|
||||||
|
// // if b panicked, we need to wait for a to finish
|
||||||
|
// let mut receiver = job.take_receiver();
|
||||||
|
// worker.wait_until_pred(|| match &receiver {
|
||||||
|
// Some(recv) => recv.poll().is_some(),
|
||||||
|
// None => {
|
||||||
|
// receiver = job.take_receiver();
|
||||||
|
// false
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// resume_unwind(payload);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let ra = if let Some(recv) = job.take_receiver() {
|
||||||
|
// match worker.wait_until_recv(recv) {
|
||||||
|
// Some(t) => crate::util::unwrap_or_panic(t),
|
||||||
|
// None => {
|
||||||
|
// #[cfg(feature = "tracing")]
|
||||||
|
// tracing::trace!(
|
||||||
|
// "join_heartbeat: job was shared, but reclaimed, running a() inline"
|
||||||
|
// );
|
||||||
|
// // the job was shared, but not yet stolen, so we get to run the
|
||||||
|
// // job inline
|
||||||
|
// unsafe { stack.unwrap()(*self) }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// worker.pop_back();
|
||||||
|
|
||||||
|
// unsafe {
|
||||||
|
// // SAFETY: we just popped the job from the queue, so it is safe to unwrap.
|
||||||
|
// #[cfg(feature = "tracing")]
|
||||||
|
// tracing::trace!("join_heartbeat: job was not shared, running a() inline");
|
||||||
|
// stack.unwrap()(*self)
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// (ra, rb)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn from_context(context: Arc<Context>) -> Self {
|
fn new(worker: &WorkerThread, inner: Pin<&'scope ScopeInner>) -> Self {
|
||||||
|
// SAFETY: we are creating a new scope, so the inner is valid.
|
||||||
|
unsafe { Self::new_unchecked(worker, SendPtr::new_const(&*inner).unwrap()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn new_unchecked(worker: &WorkerThread, inner: SendPtr<ScopeInner>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
context,
|
inner,
|
||||||
job_counter: CountLatch::new(ptr::null()),
|
worker: SendPtr::new_const(worker).unwrap(),
|
||||||
panic: AtomicPtr::new(ptr::null_mut()),
|
|
||||||
_scope: PhantomData,
|
_scope: PhantomData,
|
||||||
_env: PhantomData,
|
_env: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn context(&self) -> &Arc<Context> {
|
||||||
|
unsafe { &self.worker.as_ref().context }
|
||||||
|
}
|
||||||
|
pub fn worker(&self) -> &WorkerThread {
|
||||||
|
unsafe { self.worker.as_ref() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::atomic::AtomicU8;
|
use std::sync::atomic::AtomicU8;
|
||||||
|
|
||||||
use tracing_test::traced_test;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ThreadPool;
|
use crate::ThreadPool;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(not(miri), traced_test)]
|
#[cfg_attr(all(not(miri), feature = "tracing"), tracing_test::traced_test)]
|
||||||
fn scope_spawn_sync() {
|
fn scope_spawn_sync() {
|
||||||
let pool = ThreadPool::new_with_threads(1);
|
let pool = ThreadPool::new_with_threads(1);
|
||||||
let count = Arc::new(AtomicU8::new(0));
|
let count = Arc::new(AtomicU8::new(0));
|
||||||
|
@ -325,7 +525,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(not(miri), traced_test)]
|
#[cfg_attr(all(not(miri), feature = "tracing"), tracing_test::traced_test)]
|
||||||
fn scope_join_one() {
|
fn scope_join_one() {
|
||||||
let pool = ThreadPool::new_with_threads(1);
|
let pool = ThreadPool::new_with_threads(1);
|
||||||
|
|
||||||
|
@ -338,11 +538,11 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(not(miri), traced_test)]
|
#[cfg_attr(all(not(miri), feature = "tracing"), tracing_test::traced_test)]
|
||||||
fn scope_join_many() {
|
fn scope_join_many() {
|
||||||
let pool = ThreadPool::new_with_threads(1);
|
let pool = ThreadPool::new_with_threads(1);
|
||||||
|
|
||||||
fn sum<'scope, 'env>(scope: &'scope Scope<'scope, 'env>, n: usize) -> usize {
|
fn sum<'scope, 'env>(scope: Scope<'scope, 'env>, n: usize) -> usize {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -360,7 +560,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(not(miri), traced_test)]
|
#[cfg_attr(all(not(miri), feature = "tracing"), tracing_test::traced_test)]
|
||||||
fn scope_spawn_future() {
|
fn scope_spawn_future() {
|
||||||
let pool = ThreadPool::new_with_threads(1);
|
let pool = ThreadPool::new_with_threads(1);
|
||||||
let mut x = 0;
|
let mut x = 0;
|
||||||
|
@ -376,7 +576,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(not(miri), traced_test)]
|
#[cfg_attr(all(not(miri), feature = "tracing"), tracing_test::traced_test)]
|
||||||
fn scope_spawn_many() {
|
fn scope_spawn_many() {
|
||||||
let pool = ThreadPool::new_with_threads(1);
|
let pool = ThreadPool::new_with_threads(1);
|
||||||
let count = Arc::new(AtomicU8::new(0));
|
let count = Arc::new(AtomicU8::new(0));
|
||||||
|
|
|
@ -8,8 +8,8 @@ pub struct ThreadPool {
|
||||||
|
|
||||||
impl Drop for ThreadPool {
|
impl Drop for ThreadPool {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// Ensure that the context is properly cleaned up when the thread pool is dropped.
|
// TODO: Ensure that the context is properly cleaned up when the thread pool is dropped.
|
||||||
self.context.set_should_exit();
|
// self.context.set_should_exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,9 +25,14 @@ impl ThreadPool {
|
||||||
Self { context }
|
Self { context }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn global() -> Self {
|
||||||
|
let context = Context::global_context().clone();
|
||||||
|
Self { context }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn scope<'env, F, R>(&self, f: F) -> R
|
pub fn scope<'env, F, R>(&self, f: F) -> R
|
||||||
where
|
where
|
||||||
F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> R + Send,
|
F: for<'scope> FnOnce(Scope<'scope, 'env>) -> R + Send,
|
||||||
R: Send,
|
R: Send,
|
||||||
{
|
{
|
||||||
scope_with_context(&self.context, f)
|
scope_with_context(&self.context, f)
|
||||||
|
@ -53,17 +58,16 @@ impl ThreadPool {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use tracing_test::traced_test;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(not(miri), traced_test)]
|
#[cfg_attr(all(not(miri), feature = "tracing"), tracing_test::traced_test)]
|
||||||
fn pool_spawn_borrow() {
|
fn pool_spawn_borrow() {
|
||||||
let pool = ThreadPool::new_with_threads(1);
|
let pool = ThreadPool::new_with_threads(1);
|
||||||
let mut x = 0;
|
let mut x = 0;
|
||||||
pool.scope(|scope| {
|
pool.scope(|scope| {
|
||||||
scope.spawn(|_| {
|
scope.spawn(|_| {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::info!("Incrementing x");
|
tracing::info!("Incrementing x");
|
||||||
x += 1;
|
x += 1;
|
||||||
});
|
});
|
||||||
|
@ -72,7 +76,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(not(miri), traced_test)]
|
#[cfg_attr(all(not(miri), feature = "tracing"), tracing_test::traced_test)]
|
||||||
fn pool_spawn_future() {
|
fn pool_spawn_future() {
|
||||||
let pool = ThreadPool::new_with_threads(1);
|
let pool = ThreadPool::new_with_threads(1);
|
||||||
let mut x = 0;
|
let mut x = 0;
|
||||||
|
@ -89,7 +93,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(not(miri), traced_test)]
|
#[cfg_attr(all(not(miri), feature = "tracing"), tracing_test::traced_test)]
|
||||||
fn pool_join() {
|
fn pool_join() {
|
||||||
let pool = ThreadPool::new_with_threads(1);
|
let pool = ThreadPool::new_with_threads(1);
|
||||||
let (a, b) = pool.join(|| 3 + 4, || 5 * 6);
|
let (a, b) = pool.join(|| 3 + 4, || 5 * 6);
|
||||||
|
|
|
@ -94,8 +94,7 @@ impl<T> SendPtr<T> {
|
||||||
unsafe { Self::new_unchecked(ptr.cast_mut()) }
|
unsafe { Self::new_unchecked(ptr.cast_mut()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn as_unchecked_ref(&self) -> &T {
|
pub(crate) unsafe fn as_ref(&self) -> &T {
|
||||||
// SAFETY: `self.0` is a valid non-null pointer.
|
|
||||||
unsafe { self.0.as_ref() }
|
unsafe { self.0.as_ref() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,7 +181,6 @@ impl<T, const BITS: u8> TaggedAtomicPtr<T, BITS> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns tag
|
/// returns tag
|
||||||
#[inline]
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn compare_exchange_tag(
|
pub fn compare_exchange_tag(
|
||||||
&self,
|
&self,
|
||||||
|
@ -201,7 +199,6 @@ impl<T, const BITS: u8> TaggedAtomicPtr<T, BITS> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns tag
|
/// returns tag
|
||||||
#[inline]
|
|
||||||
pub fn compare_exchange_weak_tag(
|
pub fn compare_exchange_weak_tag(
|
||||||
&self,
|
&self,
|
||||||
old: usize,
|
old: usize,
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
|
#[cfg(feature = "metrics")]
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Cell, UnsafeCell},
|
cell::{Cell, UnsafeCell},
|
||||||
hint::cold_path,
|
|
||||||
ptr::NonNull,
|
ptr::NonNull,
|
||||||
sync::Arc,
|
sync::{Arc, Barrier},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crossbeam_utils::CachePadded;
|
use crossbeam_utils::CachePadded;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::{Context, Heartbeat},
|
channel::Receiver,
|
||||||
|
context::Context,
|
||||||
heartbeat::OwnedHeartbeatReceiver,
|
heartbeat::OwnedHeartbeatReceiver,
|
||||||
job::{JobQueue as JobList, JobResult, QueuedJob as Job, QueuedJob, StackJob},
|
job::{Job2 as Job, JobQueue as JobList, SharedJob},
|
||||||
latch::{AsCoreLatch, CoreLatch, Probe, WorkerLatch},
|
|
||||||
util::DropGuard,
|
util::DropGuard,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,6 +23,9 @@ pub struct WorkerThread {
|
||||||
pub(crate) queue: UnsafeCell<JobList>,
|
pub(crate) queue: UnsafeCell<JobList>,
|
||||||
pub(crate) heartbeat: OwnedHeartbeatReceiver,
|
pub(crate) heartbeat: OwnedHeartbeatReceiver,
|
||||||
pub(crate) join_count: Cell<u8>,
|
pub(crate) join_count: Cell<u8>,
|
||||||
|
|
||||||
|
#[cfg(feature = "metrics")]
|
||||||
|
pub(crate) metrics: CachePadded<crate::metrics::WorkerMetrics>,
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
|
@ -36,13 +41,17 @@ impl WorkerThread {
|
||||||
queue: UnsafeCell::new(JobList::new()),
|
queue: UnsafeCell::new(JobList::new()),
|
||||||
heartbeat,
|
heartbeat,
|
||||||
join_count: Cell::new(0),
|
join_count: Cell::new(0),
|
||||||
|
#[cfg(feature = "metrics")]
|
||||||
|
metrics: CachePadded::new(crate::metrics::WorkerMetrics::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkerThread {
|
impl WorkerThread {
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all, fields(
|
||||||
pub fn run(self: Box<Self>) {
|
worker = self.heartbeat.index(),
|
||||||
|
)))]
|
||||||
|
pub fn run(self: Box<Self>, barrier: Arc<Barrier>) {
|
||||||
let this = Box::into_raw(self);
|
let this = Box::into_raw(self);
|
||||||
unsafe {
|
unsafe {
|
||||||
Self::set_current(this);
|
Self::set_current(this);
|
||||||
|
@ -54,16 +63,24 @@ impl WorkerThread {
|
||||||
Self::drop_in_place(this);
|
Self::drop_in_place(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::trace!("WorkerThread::run: starting worker thread");
|
tracing::trace!("WorkerThread::run: starting worker thread");
|
||||||
|
|
||||||
|
barrier.wait();
|
||||||
unsafe {
|
unsafe {
|
||||||
(&*this).run_inner();
|
(&*this).run_inner();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "metrics")]
|
||||||
|
unsafe {
|
||||||
|
eprintln!("{:?}", (&*this).metrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::trace!("WorkerThread::run: worker thread finished");
|
tracing::trace!("WorkerThread::run: worker thread finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
fn run_inner(&self) {
|
fn run_inner(&self) {
|
||||||
let mut job = None;
|
let mut job = None;
|
||||||
'outer: loop {
|
'outer: loop {
|
||||||
|
@ -86,55 +103,69 @@ impl WorkerThread {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkerThread {
|
impl WorkerThread {
|
||||||
pub(crate) fn find_work(&self) -> Option<NonNull<Job>> {
|
|
||||||
self.find_work_inner().left()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Looks for work in the local queue, then in the shared context, and if no
|
/// Looks for work in the local queue, then in the shared context, and if no
|
||||||
/// work is found, waits for the thread to be notified of a new job, after
|
/// work is found, waits for the thread to be notified of a new job, after
|
||||||
/// which it returns `None`.
|
/// which it returns `None`.
|
||||||
/// The caller should then check for `should_exit` to determine if the
|
/// The caller should then check for `should_exit` to determine if the
|
||||||
/// thread should exit, or look for work again.
|
/// thread should exit, or look for work again.
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
pub(crate) fn find_work_or_wait(&self) -> Option<NonNull<Job>> {
|
pub(crate) fn find_work_or_wait(&self) -> Option<SharedJob> {
|
||||||
match self.find_work_inner() {
|
if let Some(job) = self.find_work() {
|
||||||
either::Either::Left(job) => {
|
return Some(job);
|
||||||
return Some(job);
|
|
||||||
}
|
|
||||||
either::Either::Right(mut guard) => {
|
|
||||||
// no jobs found, wait for a heartbeat or a new job
|
|
||||||
tracing::trace!("WorkerThread::find_work_or_wait: waiting for new job");
|
|
||||||
self.heartbeat.latch().wait_with_lock(&mut guard);
|
|
||||||
tracing::trace!("WorkerThread::find_work_or_wait: woken up from wait");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::trace!("waiting for new job");
|
||||||
|
self.heartbeat.parker().park();
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::trace!("woken up from wait");
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
fn find_work_inner(
|
pub(crate) fn find_work_or_wait_unless<F>(&self, mut pred: F) -> Option<SharedJob>
|
||||||
&self,
|
where
|
||||||
) -> either::Either<NonNull<Job>, parking_lot::MutexGuard<'_, crate::context::Shared>> {
|
F: FnMut() -> bool,
|
||||||
// first check the local queue for jobs
|
{
|
||||||
if let Some(job) = self.pop_front() {
|
if let Some(job) = self.find_work() {
|
||||||
tracing::trace!("WorkerThread::find_work_inner: found local job: {:?}", job);
|
return Some(job);
|
||||||
return either::Either::Left(job);
|
|
||||||
}
|
}
|
||||||
|
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
// Check the predicate while holding the lock. This is very important,
|
||||||
|
// because the lock must be held when notifying us of the result of a
|
||||||
|
// job we scheduled.
|
||||||
|
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
// no jobs found, wait for a heartbeat or a new job
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::trace!(worker = self.heartbeat.index(), "waiting for new job");
|
||||||
|
if !pred() {
|
||||||
|
self.heartbeat.parker().park();
|
||||||
|
}
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::trace!(worker = self.heartbeat.index(), "woken up from wait");
|
||||||
|
|
||||||
// then check the shared context for jobs
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_work(&self) -> Option<SharedJob> {
|
||||||
let mut guard = self.context.shared();
|
let mut guard = self.context.shared();
|
||||||
|
|
||||||
if let Some(job) = guard.pop_job() {
|
if let Some(job) = guard.pop_job() {
|
||||||
|
#[cfg(feature = "metrics")]
|
||||||
|
self.metrics.num_jobs_stolen.fetch_add(1, Ordering::Relaxed);
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::trace!("WorkerThread::find_work_inner: found shared job: {:?}", job);
|
tracing::trace!("WorkerThread::find_work_inner: found shared job: {:?}", job);
|
||||||
return either::Either::Left(job);
|
return Some(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
either::Either::Right(guard)
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn tick(&self) {
|
pub(crate) fn tick(&self) {
|
||||||
if self.heartbeat.take() {
|
if self.heartbeat.take() {
|
||||||
|
#[cfg(feature = "metrics")]
|
||||||
|
self.metrics.num_heartbeats.fetch_add(1, Ordering::Relaxed);
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
"received heartbeat, thread id: {:?}",
|
"received heartbeat, thread id: {:?}",
|
||||||
self.heartbeat.index()
|
self.heartbeat.index()
|
||||||
|
@ -143,11 +174,10 @@ impl WorkerThread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
#[tracing::instrument(level = "trace", skip(self))]
|
fn execute(&self, job: SharedJob) {
|
||||||
fn execute(&self, job: NonNull<Job>) {
|
unsafe { SharedJob::execute(job, self) };
|
||||||
self.tick();
|
self.tick();
|
||||||
unsafe { Job::execute(job.as_ptr()) };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cold]
|
#[cold]
|
||||||
|
@ -156,10 +186,17 @@ impl WorkerThread {
|
||||||
|
|
||||||
if !guard.jobs.contains_key(&self.heartbeat.id()) {
|
if !guard.jobs.contains_key(&self.heartbeat.id()) {
|
||||||
if let Some(job) = self.pop_back() {
|
if let Some(job) = self.pop_back() {
|
||||||
Job::set_shared(unsafe { job.as_ref() });
|
#[cfg(feature = "tracing")]
|
||||||
tracing::trace!("heartbeat: sharing job: {:?}", job);
|
tracing::trace!("heartbeat: sharing job: {:?}", job);
|
||||||
guard.jobs.insert(self.heartbeat.id(), job);
|
|
||||||
|
#[cfg(feature = "metrics")]
|
||||||
|
self.metrics.num_jobs_shared.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
guard.jobs.insert(
|
||||||
|
self.heartbeat.id(),
|
||||||
|
job.as_ref().share(Some(self.heartbeat.parker())),
|
||||||
|
);
|
||||||
// SAFETY: we are holding the lock on the shared context.
|
// SAFETY: we are holding the lock on the shared context.
|
||||||
self.context.notify_job_shared();
|
self.context.notify_job_shared();
|
||||||
}
|
}
|
||||||
|
@ -169,29 +206,23 @@ impl WorkerThread {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkerThread {
|
impl WorkerThread {
|
||||||
#[inline]
|
|
||||||
pub fn pop_back(&self) -> Option<NonNull<Job>> {
|
pub fn pop_back(&self) -> Option<NonNull<Job>> {
|
||||||
unsafe { self.queue.as_mut_unchecked().pop_back() }
|
unsafe { self.queue.as_mut_unchecked().pop_back() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn push_back<T>(&self, job: *const Job<T>) {
|
||||||
pub fn push_back(&self, job: *const Job) {
|
unsafe { self.queue.as_mut_unchecked().push_back(job.cast()) }
|
||||||
unsafe { self.queue.as_mut_unchecked().push_back(job) }
|
}
|
||||||
|
pub fn push_front<T>(&self, job: *const Job<T>) {
|
||||||
|
unsafe { self.queue.as_mut_unchecked().push_front(job.cast()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn pop_front(&self) -> Option<NonNull<Job>> {
|
pub fn pop_front(&self) -> Option<NonNull<Job>> {
|
||||||
unsafe { self.queue.as_mut_unchecked().pop_front() }
|
unsafe { self.queue.as_mut_unchecked().pop_front() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn push_front(&self, job: *const Job) {
|
|
||||||
unsafe { self.queue.as_mut_unchecked().push_front(job) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkerThread {
|
impl WorkerThread {
|
||||||
#[inline]
|
|
||||||
pub fn current_ref<'a>() -> Option<&'a Self> {
|
pub fn current_ref<'a>() -> Option<&'a Self> {
|
||||||
unsafe { (*WORKER.with(UnsafeCell::get)).map(|ptr| ptr.as_ref()) }
|
unsafe { (*WORKER.with(UnsafeCell::get)).map(|ptr| ptr.as_ref()) }
|
||||||
}
|
}
|
||||||
|
@ -242,9 +273,11 @@ impl HeartbeatThread {
|
||||||
Self { ctx }
|
Self { ctx }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
|
||||||
pub fn run(self) {
|
pub fn run(self, barrier: Arc<Barrier>) {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::trace!("new heartbeat thread {:?}", std::thread::current());
|
tracing::trace!("new heartbeat thread {:?}", std::thread::current());
|
||||||
|
barrier.wait();
|
||||||
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
loop {
|
loop {
|
||||||
|
@ -273,113 +306,88 @@ impl HeartbeatThread {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkerThread {
|
impl WorkerThread {
|
||||||
#[tracing::instrument(level = "trace", skip(self))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
|
||||||
pub fn wait_until_queued_job<T>(
|
pub fn wait_until_shared_job<T: Send>(&self, job: &Job<T>) -> Option<std::thread::Result<T>> {
|
||||||
&self,
|
let recv = (*job).take_receiver().unwrap();
|
||||||
job: *const QueuedJob,
|
|
||||||
) -> Option<std::thread::Result<T>> {
|
|
||||||
let recv = unsafe { (*job).as_receiver::<T>() };
|
|
||||||
// we've already checked that the job was popped from the queue
|
|
||||||
// check if shared job is our job
|
|
||||||
|
|
||||||
// if let Some(shared_job) = self.context.shared().jobs.remove(&self.heartbeat.id()) {
|
let mut out = recv.poll();
|
||||||
// if core::ptr::eq(shared_job.as_ptr(), job as *const Job as _) {
|
|
||||||
// // this is the job we are looking for, so we want to
|
|
||||||
// // short-circuit and call it inline
|
|
||||||
// tracing::trace!(
|
|
||||||
// thread = self.heartbeat.index(),
|
|
||||||
// "reclaiming shared job: {:?}",
|
|
||||||
// shared_job
|
|
||||||
// );
|
|
||||||
|
|
||||||
// return None;
|
while std::hint::unlikely(out.is_none()) {
|
||||||
// } else {
|
if let Some(job) = self.find_work() {
|
||||||
// // this isn't the job we are looking for, but we still need to
|
unsafe {
|
||||||
// // execute it
|
SharedJob::execute(job, self);
|
||||||
// tracing::trace!(
|
|
||||||
// thread = self.heartbeat.index(),
|
|
||||||
// "executing reclaimed shared job: {:?}",
|
|
||||||
// shared_job
|
|
||||||
// );
|
|
||||||
|
|
||||||
// unsafe { Job::execute(shared_job.as_ptr()) };
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match recv.poll() {
|
|
||||||
Some(t) => {
|
|
||||||
return Some(t);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
cold_path();
|
|
||||||
|
|
||||||
// check local jobs before locking shared context
|
|
||||||
if let Some(job) = self.find_work_or_wait() {
|
|
||||||
tracing::trace!(
|
|
||||||
"thread {:?} executing local job: {:?}",
|
|
||||||
self.heartbeat.index(),
|
|
||||||
job
|
|
||||||
);
|
|
||||||
unsafe {
|
|
||||||
Job::execute(job.as_ptr());
|
|
||||||
}
|
|
||||||
tracing::trace!(
|
|
||||||
"thread {:?} finished local job: {:?}",
|
|
||||||
self.heartbeat.index(),
|
|
||||||
job
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out = recv.poll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
pub fn wait_until_latch<L>(&self, latch: &L)
|
pub fn wait_until_recv<T: Send>(&self, recv: Receiver<T>) -> Option<std::thread::Result<T>> {
|
||||||
|
if self
|
||||||
|
.context
|
||||||
|
.shared()
|
||||||
|
.jobs
|
||||||
|
.remove(&self.heartbeat.id())
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::trace!("reclaiming shared job");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
while recv.is_empty() {
|
||||||
|
if let Some(job) = self.find_work() {
|
||||||
|
unsafe {
|
||||||
|
SharedJob::execute(job, self);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
recv.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(recv.recv())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
|
pub fn wait_until_pred<F>(&self, mut pred: F)
|
||||||
where
|
where
|
||||||
L: Probe,
|
F: FnMut() -> bool,
|
||||||
{
|
{
|
||||||
if !latch.probe() {
|
if !pred() {
|
||||||
tracing::trace!("thread {:?} waiting on latch", self.heartbeat.index());
|
#[cfg(feature = "tracing")]
|
||||||
self.wait_until_latch_cold(latch);
|
tracing::trace!("thread {:?} waiting on predicate", self.heartbeat.index());
|
||||||
|
self.wait_until_latch_cold(pred);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cold]
|
#[cold]
|
||||||
fn wait_until_latch_cold<L>(&self, latch: &L)
|
fn wait_until_latch_cold<F>(&self, mut pred: F)
|
||||||
where
|
where
|
||||||
L: Probe,
|
F: FnMut() -> bool,
|
||||||
{
|
{
|
||||||
if let Some(shared_job) = self.context.shared().jobs.remove(&self.heartbeat.id()) {
|
if let Some(shared_job) = self.context.shared().jobs.remove(&self.heartbeat.id()) {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
"thread {:?} reclaiming shared job: {:?}",
|
"thread {:?} reclaiming shared job: {:?}",
|
||||||
self.heartbeat.index(),
|
self.heartbeat.index(),
|
||||||
shared_job
|
shared_job
|
||||||
);
|
);
|
||||||
unsafe { Job::execute(shared_job.as_ptr()) };
|
unsafe { SharedJob::execute(shared_job, self) };
|
||||||
}
|
}
|
||||||
|
|
||||||
// do the usual thing and wait for the job's latch
|
// do the usual thing and wait for the job's latch
|
||||||
// do the usual thing??? chatgipity really said this..
|
// do the usual thing??? chatgipity really said this..
|
||||||
while !latch.probe() {
|
while !pred() {
|
||||||
// check local jobs before locking shared context
|
// check local jobs before locking shared context
|
||||||
if let Some(job) = self.find_work_or_wait() {
|
if let Some(job) = self.find_work() {
|
||||||
tracing::trace!(
|
|
||||||
"thread {:?} executing local job: {:?}",
|
|
||||||
self.heartbeat.index(),
|
|
||||||
job
|
|
||||||
);
|
|
||||||
unsafe {
|
unsafe {
|
||||||
Job::execute(job.as_ptr());
|
SharedJob::execute(job, self);
|
||||||
}
|
}
|
||||||
tracing::trace!(
|
|
||||||
"thread {:?} finished local job: {:?}",
|
|
||||||
self.heartbeat.index(),
|
|
||||||
job
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,10 +62,11 @@ fn join_pool(tree_size: usize) {
|
||||||
|
|
||||||
fn join_distaff(tree_size: usize) {
|
fn join_distaff(tree_size: usize) {
|
||||||
use distaff::*;
|
use distaff::*;
|
||||||
let pool = ThreadPool::new();
|
let pool = ThreadPool::new_with_threads(6);
|
||||||
|
|
||||||
let tree = Tree::new(tree_size, 1);
|
let tree = Tree::new(tree_size, 1);
|
||||||
|
|
||||||
fn sum<'scope, 'env>(tree: &Tree<u32>, node: usize, scope: &'scope Scope<'scope, 'env>) -> u32 {
|
fn sum<'scope, 'env>(tree: &Tree<u32>, node: usize, scope: Scope<'scope, 'env>) -> u32 {
|
||||||
let node = tree.get(node);
|
let node = tree.get(node);
|
||||||
let (l, r) = scope.join(
|
let (l, r) = scope.join(
|
||||||
|s| node.left.map(|node| sum(tree, node, s)).unwrap_or_default(),
|
|s| node.left.map(|node| sum(tree, node, s)).unwrap_or_default(),
|
||||||
|
@ -81,11 +82,13 @@ fn join_distaff(tree_size: usize) {
|
||||||
node.leaf + l + r
|
node.leaf + l + r
|
||||||
}
|
}
|
||||||
|
|
||||||
let sum = pool.scope(|s| {
|
for _ in 0..1000 {
|
||||||
let sum = sum(&tree, tree.root().unwrap(), s);
|
let sum = pool.scope(|s| {
|
||||||
sum
|
let sum = sum(&tree, tree.root().unwrap(), s);
|
||||||
});
|
sum
|
||||||
std::hint::black_box(sum);
|
});
|
||||||
|
std::hint::black_box(sum);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join_chili(tree_size: usize) {
|
fn join_chili(tree_size: usize) {
|
||||||
|
@ -131,11 +134,15 @@ fn join_rayon(tree_size: usize) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// use tracing_subscriber::layer::SubscriberExt;
|
//tracing_subscriber::fmt::init();
|
||||||
// tracing::subscriber::set_global_default(
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
// tracing_subscriber::registry().with(tracing_tracy::TracyLayer::default()),
|
tracing::subscriber::set_global_default(
|
||||||
// )
|
tracing_subscriber::registry().with(tracing_tracy::TracyLayer::default()),
|
||||||
// .expect("Failed to set global default subscriber");
|
)
|
||||||
|
.expect("Failed to set global default subscriber");
|
||||||
|
|
||||||
|
eprintln!("Press Enter to start profiling...");
|
||||||
|
std::io::stdin().read_line(&mut String::new()).unwrap();
|
||||||
|
|
||||||
let size = std::env::args()
|
let size = std::env::args()
|
||||||
.nth(2)
|
.nth(2)
|
||||||
|
@ -159,6 +166,6 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!("Done!");
|
eprintln!("Done!");
|
||||||
// wait for user input before exiting
|
// // wait for user input before exiting
|
||||||
// std::io::stdin().read_line(&mut String::new()).unwrap();
|
// std::io::stdin().read_line(&mut String::new()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue