use std::collections::HashMap;
use super::error::{
EvalResult, Flow, make_signal_binding_value, signal, signal_from_binding_value,
signal_with_data,
};
use super::value::{Value, ValueKind, eq_value};
use crate::gc_trace::GcTrace;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ThreadStatus {
Created,
Running,
Finished,
Signaled,
}
#[derive(Clone, Debug)]
pub struct ThreadState {
pub id: u64,
pub name: Option<String>,
pub function: Value,
pub status: ThreadStatus,
pub result: Value,
pub last_error: Option<Value>,
pub joined: bool,
pub buffer_disposition: Value,
pub current_buffer: Option<crate::buffer::BufferId>,
pub event_object: Value,
pub error_symbol: Value,
pub error_data: Value,
}
#[derive(Clone, Debug)]
pub struct MutexState {
pub id: u64,
pub name: Option<String>,
pub owner: Option<u64>,
pub lock_count: u32,
}
#[derive(Clone, Debug)]
pub struct ConditionVarState {
pub id: u64,
pub name: Option<String>,
pub mutex_id: u64,
}
pub struct ThreadManager {
threads: HashMap<u64, ThreadState>,
next_id: u64,
current_thread: u64, thread_handles: HashMap<u64, Value>,
mutexes: HashMap<u64, MutexState>,
next_mutex_id: u64,
mutex_handles: HashMap<u64, Value>,
condition_vars: HashMap<u64, ConditionVarState>,
next_cv_id: u64,
condition_var_handles: HashMap<u64, Value>,
last_error: Option<Value>,
}
impl ThreadManager {
pub fn new() -> Self {
let mut threads = HashMap::new();
threads.insert(
0,
ThreadState {
id: 0,
name: None,
function: Value::NIL,
status: ThreadStatus::Running,
result: Value::NIL,
last_error: None,
joined: false,
buffer_disposition: Value::NIL,
current_buffer: None,
event_object: Value::NIL,
error_symbol: Value::NIL,
error_data: Value::NIL,
},
);
let mut thread_handles = HashMap::new();
thread_handles.insert(0, tagged_object_value("thread", 0));
Self {
threads,
next_id: 1,
current_thread: 0,
thread_handles,
mutexes: HashMap::new(),
next_mutex_id: 1,
mutex_handles: HashMap::new(),
condition_vars: HashMap::new(),
next_cv_id: 1,
condition_var_handles: HashMap::new(),
last_error: None,
}
}
pub fn create_thread(&mut self, function: Value, name: Option<String>) -> u64 {
let id = self.next_id;
self.next_id += 1;
self.threads.insert(
id,
ThreadState {
id,
name,
function,
status: ThreadStatus::Created,
result: Value::NIL,
last_error: None,
joined: false,
buffer_disposition: Value::NIL,
current_buffer: None,
event_object: Value::NIL,
error_symbol: Value::NIL,
error_data: Value::NIL,
},
);
self.thread_handles
.insert(id, tagged_object_value("thread", id));
id
}
pub fn start_thread(&mut self, id: u64) {
if let Some(t) = self.threads.get_mut(&id) {
t.status = ThreadStatus::Running;
}
}
pub fn enter_thread(&mut self, id: u64) -> u64 {
let saved = self.current_thread;
self.current_thread = id;
saved
}
pub fn restore_thread(&mut self, id: u64) {
self.current_thread = id;
}
pub fn finish_thread(&mut self, id: u64, result: Value) {
if let Some(t) = self.threads.get_mut(&id) {
t.status = ThreadStatus::Finished;
t.result = result;
}
}
pub fn signal_thread(&mut self, id: u64, error: Value) {
if let Some(t) = self.threads.get_mut(&id) {
t.status = ThreadStatus::Signaled;
t.last_error = Some(error);
if let Some((symbol, data)) = split_signal_binding_value(error) {
t.error_symbol = symbol;
t.error_data = data;
} else {
t.error_symbol = Value::NIL;
t.error_data = Value::NIL;
}
}
}
pub fn record_last_error(&mut self, error: Value) {
self.last_error = Some(error);
}
pub fn current_thread_id(&self) -> u64 {
self.current_thread
}
pub fn get_thread(&self, id: u64) -> Option<&ThreadState> {
self.threads.get(&id)
}
pub fn thread_alive_p(&self, id: u64) -> bool {
self.threads
.get(&id)
.is_some_and(|t| t.status == ThreadStatus::Created || t.status == ThreadStatus::Running)
}
pub fn thread_name(&self, id: u64) -> Option<&str> {
self.threads.get(&id).and_then(|t| t.name.as_deref())
}
pub fn is_thread(&self, id: u64) -> bool {
self.threads.contains_key(&id)
}
pub fn thread_handle(&self, id: u64) -> Option<Value> {
self.thread_handles.get(&id).cloned()
}
pub fn thread_id_from_handle(&self, value: &Value) -> Option<u64> {
canonical_handle_id(&self.thread_handles, value, "thread")
}
pub fn all_thread_ids(&self) -> Vec<u64> {
self.threads
.iter()
.filter_map(|(id, thread)| self.thread_alive_p(*id).then_some(*id))
.collect()
}
pub fn thread_result(&self, id: u64) -> Value {
self.threads
.get(&id)
.map(|t| t.result)
.unwrap_or(Value::NIL)
}
pub fn join_thread(&mut self, id: u64) -> Option<Value> {
let thread = self.threads.get_mut(&id)?;
thread.joined = true;
thread.last_error
}
pub fn last_error(&mut self, cleanup: bool) -> Value {
let val = self.last_error.unwrap_or(Value::NIL);
if cleanup {
self.last_error = None;
}
val
}
pub fn thread_buffer_disposition(&self, id: u64) -> Option<Value> {
self.threads
.get(&id)
.map(|thread| thread.buffer_disposition)
}
pub fn set_thread_buffer_disposition(&mut self, id: u64, value: Value) -> bool {
let Some(thread) = self.threads.get_mut(&id) else {
return false;
};
thread.buffer_disposition = value;
true
}
pub fn thread_current_buffer(&self, id: u64) -> Option<crate::buffer::BufferId> {
self.threads
.get(&id)
.and_then(|thread| thread.current_buffer)
}
pub fn set_thread_current_buffer(
&mut self,
id: u64,
buffer_id: Option<crate::buffer::BufferId>,
) -> bool {
let Some(thread) = self.threads.get_mut(&id) else {
return false;
};
thread.current_buffer = buffer_id;
true
}
pub fn thread_blocker(&self, id: u64) -> Option<Value> {
self.threads.get(&id).map(|thread| thread.event_object)
}
pub fn set_thread_blocker(&mut self, id: u64, blocker: Value) -> bool {
let Some(thread) = self.threads.get_mut(&id) else {
return false;
};
thread.event_object = blocker;
true
}
pub fn clear_thread_blocker(&mut self, id: u64) -> bool {
self.set_thread_blocker(id, Value::NIL)
}
pub fn create_mutex(&mut self, name: Option<String>) -> u64 {
let id = self.next_mutex_id;
self.next_mutex_id += 1;
self.mutexes.insert(
id,
MutexState {
id,
name,
owner: None,
lock_count: 0,
},
);
self.mutex_handles
.insert(id, tagged_object_value("mutex", id));
id
}
pub fn mutex_lock(&mut self, mutex_id: u64) -> bool {
let current = self.current_thread;
if let Some(m) = self.mutexes.get_mut(&mutex_id) {
match m.owner {
None => {
m.owner = Some(current);
m.lock_count = 1;
true
}
Some(owner) if owner == current => {
m.lock_count += 1;
true
}
Some(_) => {
m.owner = Some(current);
m.lock_count = 1;
true
}
}
} else {
false
}
}
pub fn mutex_unlock(&mut self, mutex_id: u64) -> bool {
let current = self.current_thread;
if let Some(m) = self.mutexes.get_mut(&mutex_id) {
if m.owner == Some(current) {
m.lock_count = m.lock_count.saturating_sub(1);
if m.lock_count == 0 {
m.owner = None;
}
true
} else {
true
}
} else {
false
}
}
pub fn is_mutex(&self, id: u64) -> bool {
self.mutexes.contains_key(&id)
}
pub fn mutex_handle(&self, id: u64) -> Option<Value> {
self.mutex_handles.get(&id).cloned()
}
pub fn mutex_id_from_handle(&self, value: &Value) -> Option<u64> {
canonical_handle_id(&self.mutex_handles, value, "mutex")
}
pub fn mutex_name(&self, id: u64) -> Option<&str> {
self.mutexes.get(&id).and_then(|m| m.name.as_deref())
}
pub fn mutex_owned_by_current_thread(&self, mutex_id: u64) -> bool {
self.mutexes
.get(&mutex_id)
.is_some_and(|m| m.owner == Some(self.current_thread))
}
pub fn create_condition_variable(
&mut self,
mutex_id: u64,
name: Option<String>,
) -> Option<u64> {
if !self.mutexes.contains_key(&mutex_id) {
return None;
}
let id = self.next_cv_id;
self.next_cv_id += 1;
self.condition_vars
.insert(id, ConditionVarState { id, name, mutex_id });
self.condition_var_handles
.insert(id, tagged_object_value("condition-variable", id));
Some(id)
}
pub fn is_condition_variable(&self, id: u64) -> bool {
self.condition_vars.contains_key(&id)
}
pub fn condition_variable_handle(&self, id: u64) -> Option<Value> {
self.condition_var_handles.get(&id).cloned()
}
pub fn condition_variable_id_from_handle(&self, value: &Value) -> Option<u64> {
canonical_handle_id(&self.condition_var_handles, value, "condition-variable")
}
pub fn condition_variable_name(&self, id: u64) -> Option<&str> {
self.condition_vars
.get(&id)
.and_then(|cv| cv.name.as_deref())
}
pub fn condition_variable_mutex(&self, cv_id: u64) -> Option<u64> {
self.condition_vars.get(&cv_id).map(|cv| cv.mutex_id)
}
}
impl Default for ThreadManager {
fn default() -> Self {
Self::new()
}
}
impl GcTrace for ThreadManager {
fn trace_roots(&self, roots: &mut Vec<Value>) {
for thread in self.threads.values() {
roots.push(thread.function);
roots.push(thread.result);
roots.push(thread.buffer_disposition);
roots.push(thread.event_object);
roots.push(thread.error_symbol);
roots.push(thread.error_data);
if let Some(buffer_id) = thread.current_buffer {
roots.push(Value::make_buffer(buffer_id));
}
if let Some(ref err) = thread.last_error {
roots.push(*err);
}
}
for value in self.thread_handles.values() {
roots.push(*value);
}
for value in self.mutex_handles.values() {
roots.push(*value);
}
for value in self.condition_var_handles.values() {
roots.push(*value);
}
if let Some(ref err) = self.last_error {
roots.push(*err);
}
}
}
fn expect_args(name: &str, args: &[Value], n: usize) -> Result<(), Flow> {
if args.len() != n {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_min_args(name: &str, args: &[Value], min: usize) -> Result<(), Flow> {
if args.len() < min {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_args_range(name: &str, args: &[Value], min: usize, max: usize) -> Result<(), Flow> {
if args.len() < min || args.len() > max {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn tagged_object_value(tag: &str, id: u64) -> Value {
Value::cons(Value::symbol(tag), Value::fixnum(id as i64))
}
fn tagged_object_id(value: &Value, expected_tag: &str) -> Option<u64> {
if !value.is_cons() {
return None;
};
let pair_car = value.cons_car();
let pair_cdr = value.cons_cdr();
if pair_car.as_symbol_name() != Some(expected_tag) {
return None;
}
match pair_cdr.kind() {
ValueKind::Fixnum(n) if n >= 0 => Some(n as u64),
_ => None,
}
}
fn canonical_handle_id(handles: &HashMap<u64, Value>, value: &Value, tag: &str) -> Option<u64> {
let id = tagged_object_id(value, tag)?;
let canonical = handles.get(&id)?;
if eq_value(canonical, value) {
Some(id)
} else {
None
}
}
fn split_signal_binding_value(value: Value) -> Option<(Value, Value)> {
if !value.is_cons() {
return None;
};
let pair_car = value.cons_car();
let pair_cdr = value.cons_cdr();
pair_car.as_symbol_name()?;
Some((pair_car, pair_cdr))
}
fn expect_thread_id(manager: &ThreadManager, value: &Value) -> Result<u64, Flow> {
match manager.thread_id_from_handle(value) {
Some(id) => Ok(id),
None => Err(signal(
"wrong-type-argument",
vec![Value::symbol("threadp"), *value],
)),
}
}
fn expect_mutex_id(manager: &ThreadManager, value: &Value) -> Result<u64, Flow> {
match manager.mutex_id_from_handle(value) {
Some(id) => Ok(id),
None => Err(signal(
"wrong-type-argument",
vec![Value::symbol("mutexp"), *value],
)),
}
}
fn expect_cv_id(manager: &ThreadManager, value: &Value) -> Result<u64, Flow> {
match manager.condition_variable_id_from_handle(value) {
Some(id) => Ok(id),
None => Err(signal(
"wrong-type-argument",
vec![Value::symbol("condition-variable-p"), *value],
)),
}
}
pub(crate) fn builtin_make_thread(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
let (thread_id, function) = prepare_make_thread(&mut eval.threads, &args)?;
eval.threads
.set_thread_current_buffer(thread_id, eval.buffers.current_buffer_id());
let runtime_state = enter_thread_runtime(eval, thread_id)?;
let result = eval.apply(function, vec![]);
exit_thread_runtime(eval, thread_id, runtime_state);
finish_make_thread_result(&mut eval.threads, thread_id, result)
}
pub(crate) fn prepare_make_thread(
threads: &mut ThreadManager,
args: &[Value],
) -> Result<(u64, Value), Flow> {
expect_args_range("make-thread", args, 1, 3)?;
let function = args[0];
let name = if args.len() > 1 {
match args[1].kind() {
ValueKind::String => Some(args[1].as_str().unwrap().to_string()),
ValueKind::Nil => None,
other => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), args[1]],
));
}
}
} else {
None
};
let buffer_disposition = args.get(2).copied().unwrap_or(Value::NIL);
let thread_id = threads.create_thread(function, name);
threads.set_thread_buffer_disposition(thread_id, buffer_disposition);
threads.start_thread(thread_id);
Ok((thread_id, function))
}
pub(crate) fn finish_make_thread_in_eval(
eval: &mut super::eval::Context,
thread_id: u64,
function: Value,
) -> EvalResult {
eval.threads
.set_thread_current_buffer(thread_id, eval.buffers.current_buffer_id());
let runtime_state = enter_thread_runtime(eval, thread_id)?;
let result = eval.apply(function, vec![]);
exit_thread_runtime(eval, thread_id, runtime_state);
finish_make_thread_result(&mut eval.threads, thread_id, result)
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct ThreadRuntimeState {
previous_thread_id: u64,
previous_buffer_id: Option<crate::buffer::BufferId>,
}
pub(crate) fn enter_thread_runtime(
eval: &mut super::eval::Context,
thread_id: u64,
) -> Result<ThreadRuntimeState, Flow> {
let previous_thread_id = eval.threads.enter_thread(thread_id);
let previous_buffer_id = eval
.threads
.thread_current_buffer(previous_thread_id)
.or_else(|| eval.buffers.current_buffer_id());
if let Some(thread_buffer_id) = eval.threads.thread_current_buffer(thread_id) {
if eval.buffers.current_buffer_id() != Some(thread_buffer_id) {
eval.switch_current_buffer(thread_buffer_id)?;
} else {
eval.sync_current_thread_buffer_state();
}
} else {
eval.sync_current_thread_buffer_state();
}
Ok(ThreadRuntimeState {
previous_thread_id,
previous_buffer_id,
})
}
pub(crate) fn exit_thread_runtime(
eval: &mut super::eval::Context,
thread_id: u64,
runtime_state: ThreadRuntimeState,
) {
eval.threads
.set_thread_current_buffer(thread_id, eval.buffers.current_buffer_id());
eval.threads
.restore_thread(runtime_state.previous_thread_id);
if let Some(previous_buffer_id) = runtime_state.previous_buffer_id {
eval.restore_current_buffer_if_live(previous_buffer_id);
}
eval.sync_current_thread_buffer_state();
}
pub(crate) fn finish_make_thread_result(
threads: &mut ThreadManager,
thread_id: u64,
result: EvalResult,
) -> EvalResult {
match result {
Ok(val) => {
threads.finish_thread(thread_id, val);
}
Err(Flow::Signal(ref sig)) => {
let error_val = make_signal_binding_value(sig);
threads.signal_thread(thread_id, error_val);
threads.record_last_error(error_val);
}
Err(Flow::Throw { ref tag, ref value }) => {
let error_val = Value::list(vec![Value::symbol("no-catch"), *tag, *value]);
threads.signal_thread(thread_id, error_val);
threads.record_last_error(error_val);
}
}
Ok(threads
.thread_handle(thread_id)
.unwrap_or_else(|| tagged_object_value("thread", thread_id)))
}
pub(crate) fn builtin_thread_join(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("thread-join", &args, 1)?;
let id = expect_thread_id(&ctx.threads, &args[0])?;
if !ctx.threads.is_thread(id) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("threadp"), args[0]],
));
}
if id == ctx.threads.current_thread_id() {
return Err(signal(
"error",
vec![Value::string("Cannot join current thread")],
));
}
if let Some(error) = ctx.threads.join_thread(id)
&& let Some(flow) = signal_from_binding_value(error)
{
return Err(flow);
}
Ok(ctx.threads.thread_result(id))
}
pub(crate) fn builtin_thread_yield(
_ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("thread-yield", &args, 0)?;
Ok(Value::NIL)
}
pub(crate) fn builtin_thread_name(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("thread-name", &args, 1)?;
let id = expect_thread_id(&ctx.threads, &args[0])?;
if !ctx.threads.is_thread(id) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("threadp"), args[0]],
));
}
match ctx.threads.thread_name(id) {
Some(name) => Ok(Value::string(name)),
None => Ok(Value::NIL),
}
}
pub(crate) fn builtin_thread_live_p(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("thread-live-p", &args, 1)?;
let id = expect_thread_id(&ctx.threads, &args[0])?;
if !ctx.threads.is_thread(id) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("threadp"), args[0]],
));
}
Ok(Value::bool_val(ctx.threads.thread_alive_p(id)))
}
pub(crate) fn builtin_threadp(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("threadp", &args, 1)?;
Ok(Value::bool_val(
ctx.threads.thread_id_from_handle(&args[0]).is_some(),
))
}
pub(crate) fn builtin_thread_signal(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("thread-signal", &args, 3)?;
let id = expect_thread_id(&ctx.threads, &args[0])?;
if !ctx.threads.is_thread(id) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("threadp"), args[0]],
));
}
let error_symbol = args[1];
let Some(error_name) = error_symbol.as_symbol_name() else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), error_symbol],
));
};
let data = args[2];
if id == ctx.threads.current_thread_id() {
return Err(signal_with_data(error_name, data));
}
ctx.threads
.signal_thread(id, Value::cons(error_symbol, data));
Ok(Value::NIL)
}
pub(crate) fn builtin_current_thread(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("current-thread", &args, 0)?;
let id = ctx.threads.current_thread_id();
Ok(ctx
.threads
.thread_handle(id)
.unwrap_or_else(|| tagged_object_value("thread", id)))
}
pub(crate) fn builtin_all_threads(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("all-threads", &args, 0)?;
let mut ids = ctx.threads.all_thread_ids();
ids.sort_unstable();
let objects: Vec<Value> = ids
.into_iter()
.map(|id| {
ctx.threads
.thread_handle(id)
.unwrap_or_else(|| tagged_object_value("thread", id))
})
.collect();
Ok(Value::list(objects))
}
pub(crate) fn builtin_thread_last_error(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
if args.len() > 1 {
return Err(signal(
"wrong-number-of-arguments",
vec![
Value::symbol("thread-last-error"),
Value::fixnum(args.len() as i64),
],
));
}
let cleanup = args.first().is_some_and(|v| v.is_truthy());
Ok(ctx.threads.last_error(cleanup))
}
pub(crate) fn builtin_thread_blocker(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("thread--blocker", &args, 1)?;
let id = expect_thread_id(&ctx.threads, &args[0])?;
Ok(ctx.threads.thread_blocker(id).unwrap_or(Value::NIL))
}
pub(crate) fn builtin_thread_buffer_disposition(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("thread-buffer-disposition", &args, 1)?;
let id = expect_thread_id(&ctx.threads, &args[0])?;
Ok(ctx
.threads
.thread_buffer_disposition(id)
.unwrap_or(Value::NIL))
}
pub(crate) fn builtin_thread_set_buffer_disposition(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("thread-set-buffer-disposition", &args, 2)?;
let id = expect_thread_id(&ctx.threads, &args[0])?;
let value = args[1];
if id == 0 && !value.is_nil() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("null"), value],
));
}
ctx.threads.set_thread_buffer_disposition(id, value);
Ok(value)
}
pub(crate) fn builtin_make_mutex(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
if args.len() > 1 {
return Err(signal(
"wrong-number-of-arguments",
vec![
Value::symbol("make-mutex"),
Value::fixnum(args.len() as i64),
],
));
}
let name = if let Some(v) = args.first() {
match v.kind() {
ValueKind::String => Some(v.as_str().unwrap().to_string()),
ValueKind::Nil => None,
other => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), *v],
));
}
}
} else {
None
};
let id = ctx.threads.create_mutex(name);
Ok(ctx
.threads
.mutex_handle(id)
.unwrap_or_else(|| tagged_object_value("mutex", id)))
}
pub(crate) fn builtin_mutexp(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("mutexp", &args, 1)?;
Ok(Value::bool_val(
ctx.threads.mutex_id_from_handle(&args[0]).is_some(),
))
}
pub(crate) fn builtin_mutex_name(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("mutex-name", &args, 1)?;
let id = expect_mutex_id(&ctx.threads, &args[0])?;
if !ctx.threads.is_mutex(id) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("mutexp"), args[0]],
));
}
match ctx.threads.mutex_name(id) {
Some(name) => Ok(Value::string(name)),
None => Ok(Value::NIL),
}
}
pub(crate) fn builtin_mutex_lock(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("mutex-lock", &args, 1)?;
let id = expect_mutex_id(&ctx.threads, &args[0])?;
if !ctx.threads.is_mutex(id) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("mutexp"), args[0]],
));
}
ctx.threads.mutex_lock(id);
Ok(Value::NIL)
}
pub(crate) fn builtin_mutex_unlock(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("mutex-unlock", &args, 1)?;
let id = expect_mutex_id(&ctx.threads, &args[0])?;
if !ctx.threads.is_mutex(id) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("mutexp"), args[0]],
));
}
ctx.threads.mutex_unlock(id);
Ok(Value::NIL)
}
pub(crate) fn builtin_make_condition_variable(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("make-condition-variable", &args, 1)?;
if args.len() > 2 {
return Err(signal(
"wrong-number-of-arguments",
vec![
Value::symbol("make-condition-variable"),
Value::fixnum(args.len() as i64),
],
));
}
let mutex_id = expect_mutex_id(&ctx.threads, &args[0])?;
if !ctx.threads.is_mutex(mutex_id) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("mutexp"), args[0]],
));
}
let name = if args.len() > 1 {
match args[1].kind() {
ValueKind::String => Some(args[1].as_str().unwrap().to_string()),
ValueKind::Nil => None,
other => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), args[1]],
));
}
}
} else {
None
};
match ctx.threads.create_condition_variable(mutex_id, name) {
Some(id) => Ok(ctx
.threads
.condition_variable_handle(id)
.unwrap_or_else(|| tagged_object_value("condition-variable", id))),
None => Err(signal(
"wrong-type-argument",
vec![Value::symbol("mutexp"), args[0]],
)),
}
}
pub(crate) fn builtin_condition_variable_p(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("condition-variable-p", &args, 1)?;
Ok(Value::bool_val(
ctx.threads
.condition_variable_id_from_handle(&args[0])
.is_some(),
))
}
pub(crate) fn builtin_condition_name(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("condition-name", &args, 1)?;
let id = expect_cv_id(&ctx.threads, &args[0])?;
if !ctx.threads.is_condition_variable(id) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("condition-variable-p"), args[0]],
));
}
match ctx.threads.condition_variable_name(id) {
Some(name) => Ok(Value::string(name)),
None => Ok(Value::NIL),
}
}
pub(crate) fn builtin_condition_mutex(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("condition-mutex", &args, 1)?;
let id = expect_cv_id(&ctx.threads, &args[0])?;
if !ctx.threads.is_condition_variable(id) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("condition-variable-p"), args[0]],
));
}
let Some(mutex_id) = ctx.threads.condition_variable_mutex(id) else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("condition-variable-p"), args[0]],
));
};
ctx.threads.mutex_handle(mutex_id).ok_or_else(|| {
signal(
"wrong-type-argument",
vec![Value::symbol("condition-variable-p"), args[0]],
)
})
}
pub(crate) fn builtin_condition_wait(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("condition-wait", &args, 1)?;
let id = expect_cv_id(&ctx.threads, &args[0])?;
if !ctx.threads.is_condition_variable(id) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("condition-variable-p"), args[0]],
));
}
let Some(mutex_id) = ctx.threads.condition_variable_mutex(id) else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("condition-variable-p"), args[0]],
));
};
if !ctx.threads.mutex_owned_by_current_thread(mutex_id) {
return Err(signal(
"error",
vec![Value::string(
"Condition variable's mutex is not held by current thread",
)],
));
}
Ok(Value::NIL)
}
pub(crate) fn builtin_condition_notify(
ctx: &mut crate::emacs_core::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("condition-notify", &args, 1)?;
if args.len() > 2 {
return Err(signal(
"wrong-number-of-arguments",
vec![
Value::symbol("condition-notify"),
Value::fixnum(args.len() as i64),
],
));
}
let id = expect_cv_id(&ctx.threads, &args[0])?;
if !ctx.threads.is_condition_variable(id) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("condition-variable-p"), args[0]],
));
}
let Some(mutex_id) = ctx.threads.condition_variable_mutex(id) else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("condition-variable-p"), args[0]],
));
};
if !ctx.threads.mutex_owned_by_current_thread(mutex_id) {
return Err(signal(
"error",
vec![Value::string(
"Condition variable's mutex is not held by current thread",
)],
));
}
Ok(Value::NIL)
}
pub(crate) fn sf_with_mutex(
eval: &mut super::eval::Context,
tail: &[super::expr::Expr],
) -> EvalResult {
if tail.is_empty() {
return Err(signal(
"wrong-number-of-arguments",
vec![
Value::cons(Value::fixnum(1), Value::fixnum(1)),
Value::fixnum(0),
],
));
}
let mutex_form = eval.quote_to_runtime_value(&tail[0]);
let mutex_val = eval.eval_sub(mutex_form)?;
let mutex_id = expect_mutex_id(&eval.threads, &mutex_val)?;
if !eval.threads.is_mutex(mutex_id) {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("mutexp"), mutex_val],
));
}
eval.threads.mutex_lock(mutex_id);
let result = eval.sf_progn(&tail[1..]);
eval.threads.mutex_unlock(mutex_id);
result
}
#[cfg(test)]
#[path = "threads_test.rs"]
mod tests;