use crate::channel::ChanId;
use crate::payload::RtPayload;
use crate::task::{SelectArm, TaskId};
pub type FuncIdx = u16;
pub struct SpawnDesc {
pub func: FuncIdx,
pub args: Vec<RtPayload>,
pub priority: u8,
pub is_main: bool,
}
pub enum ResumeKind {
Nothing,
Spawn { func: FuncIdx, args: Vec<RtPayload>, priority: u8, is_main: bool },
Payload(RtPayload),
Select { arm: usize, payload: RtPayload },
NewChan(ChanId),
SpawnedHandle(TaskId),
}
enum ReportStep {
Yield,
Recv(ChanId),
Send(ChanId, RtPayload),
TrySend(ChanId, RtPayload),
TryRecv(ChanId),
Select(Vec<SelectArm>),
Sleep(u64),
NewChan(Option<usize>),
Spawn { func: FuncIdx, args: Vec<RtPayload>, want_handle: bool },
Await(TaskId),
Abort(TaskId),
Close(ChanId),
Exit(RtPayload),
}
struct WorkerReport {
tid: TaskId,
output: Vec<String>,
step: ReportStep,
}
enum WorkMsg {
Run { tid: TaskId, resume: ResumeKind },
Shutdown,
}
pub struct WsOutcome {
pub outcome: crate::scheduler::RunOutcome,
pub output: Vec<String>,
pub trace: crate::seed::SchedTrace,
}
#[allow(dead_code)]
fn _assert_boundary_send() {
fn s<T: Send>() {}
s::<SpawnDesc>();
s::<ResumeKind>();
s::<WorkerReport>();
s::<WorkMsg>();
s::<RtPayload>();
}
use std::collections::HashMap;
use std::sync::mpsc;
use crate::scheduler::{RunOutcome, Scheduler};
use crate::seed::{Chooser, SchedSeed};
use crate::config::SchedulerConfig;
use crate::task::{TaskCtx, TaskStep};
pub fn run_workstealing_seeded<'env, B>(
config: SchedulerConfig,
seed: SchedSeed,
main: SpawnDesc,
build: B,
) -> WsOutcome
where
B: Fn(SpawnDesc) -> Box<dyn crate::task::Task<'env> + 'env> + Sync,
{
let n = config.workers.max(1);
let mut sched: Scheduler<'static> = Scheduler::new(config, Chooser::record(seed));
let (report_tx, report_rx) = mpsc::channel::<WorkerReport>();
let main_tid = sched.spawn_meta(main.priority, true);
let mut fresh: HashMap<TaskId, SpawnDesc> = HashMap::new();
fresh.insert(main_tid, main);
let build_ref = &build;
let (outcome, output, trace) = std::thread::scope(|scope| {
let mut work_txs = Vec::with_capacity(n);
for _ in 0..n {
let (work_tx, work_rx) = mpsc::channel::<WorkMsg>();
work_txs.push(work_tx);
let rtx = report_tx.clone();
scope.spawn(move || worker_main(work_rx, rtx, build_ref));
}
drop(report_tx);
let mut owner: HashMap<TaskId, usize> = HashMap::new();
let mut next_w = 0usize;
let mut output: Vec<String> = Vec::new();
let outcome = loop {
let mut batch: Vec<crate::scheduler::WsDispatch> = Vec::new();
while let Some(d) = sched.ws_next_dispatch() {
batch.push(d);
}
if batch.is_empty() {
if sched.ws_all_done() {
break RunOutcome::Done(sched.ws_main_result());
}
if sched.ws_has_timers() {
sched.ws_advance_timers();
continue;
}
break RunOutcome::Deadlock;
}
let order: Vec<TaskId> = batch.iter().map(|d| d.tid).collect();
for d in batch {
let w = if fresh.contains_key(&d.tid) {
let w = next_w;
next_w = (next_w + 1) % n;
owner.insert(d.tid, w);
w
} else {
owner[&d.tid]
};
let resume = if let Some(desc) = fresh.remove(&d.tid) {
ResumeKind::Spawn {
func: desc.func,
args: desc.args,
priority: desc.priority,
is_main: desc.is_main,
}
} else if let Some(arm) = d.selected_arm {
ResumeKind::Select { arm, payload: d.resume }
} else if let Some(ch) = d.new_chan {
ResumeKind::NewChan(ch)
} else if let Some(t) = d.spawned {
ResumeKind::SpawnedHandle(t)
} else {
match d.resume {
RtPayload::Nothing => ResumeKind::Nothing,
p => ResumeKind::Payload(p),
}
};
work_txs[w].send(WorkMsg::Run { tid: d.tid, resume }).expect("worker alive");
}
let mut reports: HashMap<TaskId, WorkerReport> = HashMap::new();
for _ in 0..order.len() {
let r = report_rx.recv().expect("worker report");
reports.insert(r.tid, r);
}
for tid in &order {
let WorkerReport { tid: rt, output: slice_out, step } =
reports.remove(tid).expect("report for dispatched task");
output.extend(slice_out);
apply_step(&mut sched, &mut fresh, rt, step);
}
};
for tx in &work_txs {
let _ = tx.send(WorkMsg::Shutdown);
}
drop(work_txs);
(outcome, output, sched.into_trace())
});
WsOutcome { outcome, output, trace }
}
fn apply_step(
sched: &mut Scheduler<'static>,
fresh: &mut HashMap<TaskId, SpawnDesc>,
tid: TaskId,
step: ReportStep,
) {
match step {
ReportStep::Yield => sched.process_step(tid, TaskStep::Yield),
ReportStep::Recv(ch) => sched.process_step(tid, TaskStep::Recv(ch)),
ReportStep::Send(ch, p) => sched.process_step(tid, TaskStep::Send(ch, p)),
ReportStep::TrySend(ch, p) => sched.process_step(tid, TaskStep::TrySend(ch, p)),
ReportStep::TryRecv(ch) => sched.process_step(tid, TaskStep::TryRecv(ch)),
ReportStep::Select(arms) => sched.process_step(tid, TaskStep::Select(arms)),
ReportStep::Sleep(t) => sched.process_step(tid, TaskStep::Sleep(t)),
ReportStep::NewChan(cap) => sched.process_step(tid, TaskStep::NewChan(cap)),
ReportStep::Await(t) => sched.process_step(tid, TaskStep::Await(t)),
ReportStep::Abort(t) => sched.process_step(tid, TaskStep::Abort(t)),
ReportStep::Close(ch) => sched.process_step(tid, TaskStep::Close(ch)),
ReportStep::Exit(v) => sched.process_step(tid, TaskStep::Exit(v)),
ReportStep::Spawn { func, args, want_handle: _ } => {
let child = sched.ws_spawn_child(tid, 0);
fresh.insert(child, SpawnDesc { func, args, priority: 0, is_main: false });
}
}
}
fn worker_main<'env, B>(
work_rx: mpsc::Receiver<WorkMsg>,
report_tx: mpsc::Sender<WorkerReport>,
build: &B,
) where
B: Fn(SpawnDesc) -> Box<dyn crate::task::Task<'env> + 'env>,
{
let mut local: HashMap<TaskId, Box<dyn crate::task::Task<'env> + 'env>> = HashMap::new();
while let Ok(msg) = work_rx.recv() {
let (tid, resume) = match msg {
WorkMsg::Shutdown => break,
WorkMsg::Run { tid, resume } => (tid, resume),
};
let mut ctx = TaskCtx {
resumed_with: RtPayload::Nothing,
selected_arm: None,
new_chan: None,
spawned: None,
};
match resume {
ResumeKind::Nothing => {}
ResumeKind::Spawn { func, args, priority, is_main } => {
local.insert(tid, build(SpawnDesc { func, args, priority, is_main }));
}
ResumeKind::Payload(p) => ctx.resumed_with = p,
ResumeKind::Select { arm, payload } => {
ctx.selected_arm = Some(arm);
ctx.resumed_with = payload;
}
ResumeKind::NewChan(ch) => ctx.new_chan = Some(ch),
ResumeKind::SpawnedHandle(t) => ctx.spawned = Some(t),
}
let task = local.get_mut(&tid).expect("task body present on its owning worker");
let step = task.poll(&mut ctx);
let out = task.take_output();
let (rstep, done) = project_step(step);
if done {
local.remove(&tid);
}
if report_tx.send(WorkerReport { tid, output: out, step: rstep }).is_err() {
break;
}
}
}
fn project_step<'env>(step: TaskStep<'env>) -> (ReportStep, bool) {
match step {
TaskStep::Yield => (ReportStep::Yield, false),
TaskStep::Recv(ch) => (ReportStep::Recv(ch), false),
TaskStep::Send(ch, p) => (ReportStep::Send(ch, p), false),
TaskStep::TrySend(ch, p) => (ReportStep::TrySend(ch, p), false),
TaskStep::TryRecv(ch) => (ReportStep::TryRecv(ch), false),
TaskStep::Select(arms) => (ReportStep::Select(arms), false),
TaskStep::Sleep(t) => (ReportStep::Sleep(t), false),
TaskStep::NewChan(cap) => (ReportStep::NewChan(cap), false),
TaskStep::SpawnDesc { func, args, want_handle } => {
(ReportStep::Spawn { func, args, want_handle }, false)
}
TaskStep::Spawn(_) => {
panic!("work-stealing tasks must spawn via SpawnDesc, not a boxed Spawn")
}
TaskStep::Await(t) => (ReportStep::Await(t), false),
TaskStep::Abort(t) => (ReportStep::Abort(t), false),
TaskStep::Close(ch) => (ReportStep::Close(ch), false),
TaskStep::IoPending => {
panic!("work-stealing tasks do not perform external I/O (IoPending)")
}
TaskStep::Exit(v) => (ReportStep::Exit(v), true),
}
}