blob: f20ef7d86b9c71d3bd7e7bc4e932651798ded661 [file] [log] [blame]
#![cfg_attr(test, allow(dead_code))] // why is this necessary?
use crate::io;
use crate::sys::pal::abi::{thread, usercalls};
use crate::time::Duration;
pub struct Thread(task_queue::JoinHandle);
pub const DEFAULT_MIN_STACK_SIZE: usize = 4096;
pub use self::task_queue::JoinNotifier;
mod task_queue {
use super::wait_notify;
use crate::sync::{Mutex, MutexGuard};
pub type JoinHandle = wait_notify::Waiter;
pub struct JoinNotifier(Option<wait_notify::Notifier>);
impl Drop for JoinNotifier {
fn drop(&mut self) {
self.0.take().unwrap().notify();
}
}
pub(super) struct Task {
p: Box<dyn FnOnce() + Send>,
done: JoinNotifier,
}
impl Task {
pub(super) fn new(p: Box<dyn FnOnce() + Send>) -> (Task, JoinHandle) {
let (done, recv) = wait_notify::new();
let done = JoinNotifier(Some(done));
(Task { p, done }, recv)
}
pub(super) fn run(self) -> JoinNotifier {
(self.p)();
self.done
}
}
// Specifying linkage/symbol name is solely to ensure a single instance between this crate and its unit tests
#[cfg_attr(test, linkage = "available_externally")]
#[unsafe(export_name = "_ZN16__rust_internals3std3sys3pal3sgx6thread10TASK_QUEUEE")]
static TASK_QUEUE: Mutex<Vec<Task>> = Mutex::new(Vec::new());
pub(super) fn lock() -> MutexGuard<'static, Vec<Task>> {
TASK_QUEUE.lock().unwrap()
}
}
/// This module provides a synchronization primitive that does not use thread
/// local variables. This is needed for signaling that a thread has finished
/// execution. The signal is sent once all TLS destructors have finished at
/// which point no new thread locals should be created.
pub mod wait_notify {
use crate::pin::Pin;
use crate::sync::Arc;
use crate::sys::sync::Parker;
pub struct Notifier(Arc<Parker>);
impl Notifier {
/// Notify the waiter. The waiter is either notified right away (if
/// currently blocked in `Waiter::wait()`) or later when it calls the
/// `Waiter::wait()` method.
pub fn notify(self) {
Pin::new(&*self.0).unpark()
}
}
pub struct Waiter(Arc<Parker>);
impl Waiter {
/// Wait for a notification. If `Notifier::notify()` has already been
/// called, this will return immediately, otherwise the current thread
/// is blocked until notified.
pub fn wait(self) {
// SAFETY:
// This is only ever called on one thread.
unsafe { Pin::new(&*self.0).park() }
}
}
pub fn new() -> (Notifier, Waiter) {
let inner = Arc::new(Parker::new());
(Notifier(inner.clone()), Waiter(inner))
}
}
impl Thread {
// unsafe: see thread::Builder::spawn_unchecked for safety requirements
pub unsafe fn new(
_stack: usize,
_name: Option<&str>,
p: Box<dyn FnOnce() + Send>,
) -> io::Result<Thread> {
let mut queue_lock = task_queue::lock();
unsafe { usercalls::launch_thread()? };
let (task, handle) = task_queue::Task::new(p);
queue_lock.push(task);
Ok(Thread(handle))
}
pub(crate) fn entry() -> JoinNotifier {
let mut pending_tasks = task_queue::lock();
let task = rtunwrap!(Some, pending_tasks.pop());
drop(pending_tasks); // make sure to not hold the task queue lock longer than necessary
task.run()
}
pub fn join(self) {
self.0.wait();
}
}
pub fn current_os_id() -> Option<u64> {
Some(thread::current().addr().get() as u64)
}
pub fn sleep(dur: Duration) {
usercalls::wait_timeout(0, dur, || true);
}
pub fn yield_now() {
let wait_error = rtunwrap!(Err, usercalls::wait(0, usercalls::raw::WAIT_NO));
rtassert!(wait_error.kind() == io::ErrorKind::WouldBlock);
}