#![allow(unused)]
use crate::parking::Event;
use std::collections::HashMap;
use std::mem;
use std::rc::Rc;
use std::sync::{Arc, Condvar, Mutex};
use std::time::{Duration, Instant};
use cljrs_gc::GcPtr;
use cljrs_gc::mutator::TrackedMutex;
use cljrs_reader::Form;
use crate::TypeHint;
use crate::Value;
pub type MethodMap = HashMap<Arc<str>, Value>;
#[cfg(feature = "no-gc")]
type ImplementationOwnership = HashMap<Arc<str>, HashMap<Arc<str>, crate::publish::ValueLease>>;
#[derive(Debug)]
pub struct Protocol {
pub name: Arc<str>,
pub ns: Arc<str>,
pub methods: Vec<ProtocolMethod>,
pub impls: TrackedMutex<HashMap<Arc<str>, MethodMap>>,
#[cfg(feature = "no-gc")]
impl_ownership: std::cell::RefCell<ImplementationOwnership>,
pub extend_via_metadata: bool,
}
impl Protocol {
pub fn new(
name: Arc<str>,
ns: Arc<str>,
methods: Vec<ProtocolMethod>,
extend_via_metadata: bool,
) -> Self {
Self {
name,
ns,
methods,
impls: TrackedMutex::new(HashMap::new()),
#[cfg(feature = "no-gc")]
impl_ownership: Default::default(),
extend_via_metadata,
}
}
}
impl Protocol {
pub fn set_implementation(&self, tag: Arc<str>, method: Arc<str>, value: Value) {
#[cfg(feature = "no-gc")]
let (value, lease) = crate::publish::prepare_for_owner(
value,
&cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize),
);
#[cfg(not(feature = "no-gc"))]
let value = crate::publish::publish_value(value);
self.impls
.lock()
.unwrap()
.entry(tag.clone())
.or_default()
.insert(method.clone(), value);
#[cfg(feature = "no-gc")]
{
let previous = self
.impl_ownership
.borrow_mut()
.entry(tag)
.or_default()
.insert(method, lease);
drop(previous);
}
}
pub fn protect_implementation(&self, tag: &str, method: &str) {
#[cfg(feature = "no-gc")]
if let Some(lease) = self
.impl_ownership
.borrow()
.get(tag)
.and_then(|methods| methods.get(method))
{
lease.protect_read();
}
#[cfg(not(feature = "no-gc"))]
let _ = (tag, method);
}
}
static PROTOCOL_GENERATION: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub fn protocol_generation() -> u64 {
PROTOCOL_GENERATION.load(std::sync::atomic::Ordering::Acquire)
}
pub fn bump_protocol_generation() {
PROTOCOL_GENERATION.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
}
impl Drop for Protocol {
fn drop(&mut self) {
crate::publish::clear_slots(self as *const _ as usize);
}
}
impl cljrs_gc::Trace for Protocol {
#[cfg(feature = "no-gc")]
fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {
}
#[cfg(not(feature = "no-gc"))]
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
{
let impls = self.impls.lock().unwrap();
for method_map in impls.values() {
for v in method_map.values() {
v.trace(visitor);
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct ProtocolMethod {
pub name: Arc<str>,
pub min_arity: usize,
pub variadic: bool,
}
impl cljrs_gc::Trace for ProtocolMethod {
fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
}
#[derive(Debug)]
pub struct ProtocolFn {
pub protocol: GcPtr<Protocol>,
pub method_name: Arc<str>,
pub min_arity: usize,
pub variadic: bool,
}
impl cljrs_gc::Trace for ProtocolFn {
#[cfg(feature = "no-gc")]
fn immutable_children(&self) -> bool {
true
}
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
use cljrs_gc::GcVisitor as _;
visitor.visit(&self.protocol);
}
}
#[derive(Debug)]
pub struct MultiFn {
pub name: Arc<str>,
pub dispatch_fn: Value,
pub methods: TrackedMutex<HashMap<String, Value>>,
pub dispatch_vals: TrackedMutex<HashMap<String, Value>>,
pub prefers: TrackedMutex<HashMap<String, Vec<String>>>,
pub preference_vals: TrackedMutex<HashMap<String, Value>>,
pub default_dispatch: String,
method_generation: std::sync::atomic::AtomicU64,
method_cache: TrackedMutex<MultiFnMethodCache>,
#[cfg(feature = "no-gc")]
method_ownership: std::cell::RefCell<HashMap<String, crate::publish::ValueLease>>,
}
#[derive(Debug, Default)]
struct MultiFnMethodCache {
hierarchy_generation: u64,
method_generation: u64,
methods: HashMap<String, String>,
}
impl MultiFn {
pub fn new(name: Arc<str>, dispatch_fn: Value, default_dispatch: String) -> Self {
Self {
name,
dispatch_fn,
methods: TrackedMutex::new(HashMap::new()),
dispatch_vals: TrackedMutex::new(HashMap::new()),
prefers: TrackedMutex::new(HashMap::new()),
preference_vals: TrackedMutex::new(HashMap::new()),
default_dispatch,
method_generation: std::sync::atomic::AtomicU64::new(0),
method_cache: TrackedMutex::new(MultiFnMethodCache::default()),
#[cfg(feature = "no-gc")]
method_ownership: Default::default(),
}
}
pub fn method(&self, key: &str) -> Option<Value> {
let value = self.methods.lock().unwrap().get(key).cloned();
#[cfg(feature = "no-gc")]
if let Some(lease) = self.method_ownership.borrow().get(key) {
lease.protect_read();
}
value
}
pub fn set_method(&self, key: String, value: Value) {
#[cfg(feature = "no-gc")]
let (value, lease) = crate::publish::prepare_for_owner(
value,
&cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize),
);
#[cfg(not(feature = "no-gc"))]
let value = crate::publish::publish_value(value);
self.methods.lock().unwrap().insert(key.clone(), value);
#[cfg(feature = "no-gc")]
{
let previous = self.method_ownership.borrow_mut().insert(key, lease);
drop(previous);
}
}
pub fn remove_method(&self, key: &str) -> bool {
let removed = self.methods.lock().unwrap().remove(key).is_some();
#[cfg(feature = "no-gc")]
{
let previous = self.method_ownership.borrow_mut().remove(key);
drop(previous);
}
removed
}
pub fn method_generation(&self) -> u64 {
self.method_generation
.load(std::sync::atomic::Ordering::Acquire)
}
pub fn bump_method_generation(&self) {
self.method_generation
.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
}
pub fn cached_method(
&self,
dispatch_key: &str,
hierarchy_generation: u64,
method_generation: u64,
) -> Option<String> {
let mut cache = self.method_cache.lock().unwrap();
if cache.hierarchy_generation != hierarchy_generation
|| cache.method_generation != method_generation
{
cache.hierarchy_generation = hierarchy_generation;
cache.method_generation = method_generation;
cache.methods.clear();
}
cache.methods.get(dispatch_key).cloned()
}
pub fn cache_method(
&self,
dispatch_key: String,
method_key: String,
hierarchy_generation: u64,
method_generation: u64,
) {
let mut cache = self.method_cache.lock().unwrap();
if cache.hierarchy_generation != hierarchy_generation
|| cache.method_generation != method_generation
{
cache.hierarchy_generation = hierarchy_generation;
cache.method_generation = method_generation;
cache.methods.clear();
}
cache.methods.insert(dispatch_key, method_key);
}
pub fn cached_method_count(&self) -> usize {
self.method_cache.lock().unwrap().methods.len()
}
}
impl Drop for MultiFn {
fn drop(&mut self) {
crate::publish::clear_slots(self as *const _ as usize);
}
}
impl cljrs_gc::Trace for MultiFn {
#[cfg(feature = "no-gc")]
fn init_ownership(&self) {
let methods = self.methods.lock().unwrap().clone();
for (key, value) in methods {
self.set_method(key, value);
}
for (prefix, table) in [
("dispatch", &self.dispatch_vals),
("prefer", &self.preference_vals),
] {
for (key, value) in table.lock().unwrap().iter() {
let lease = cljrs_gc::region::RegionLease::for_value(value);
cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize).set_slot(
self as *const _ as usize,
format!("{prefix}:{key}"),
&lease,
);
}
}
}
#[cfg(feature = "no-gc")]
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
self.dispatch_fn.trace(visitor);
}
#[cfg(not(feature = "no-gc"))]
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
self.dispatch_fn.trace(visitor);
{
let methods = self.methods.lock().unwrap();
for v in methods.values() {
v.trace(visitor);
}
}
{
let dispatch_vals = self.dispatch_vals.lock().unwrap();
for v in dispatch_vals.values() {
v.trace(visitor);
}
}
{
let preference_vals = self.preference_vals.lock().unwrap();
for v in preference_vals.values() {
v.trace(visitor);
}
}
}
}
#[cfg(feature = "no-gc")]
fn prepare_optional(
value: Option<Value>,
address: usize,
) -> (Option<Value>, crate::publish::ValueLease) {
match value {
Some(value) => {
let (value, lease) = crate::publish::prepare_for_owner(
value,
&cljrs_gc::region::AllocationOwner::for_address(address),
);
(Some(value), lease)
}
None => (None, Default::default()),
}
}
fn edit_watches(
watches: &TrackedMutex<Vec<(Value, Value)>>,
added: Option<(Value, Value)>,
removed: Option<&Value>,
) {
let mut watches = watches.lock().unwrap();
if let Some((key, callback)) = added {
if let Some(entry) = watches.iter_mut().find(|(k, _)| k == &key) {
entry.1 = callback;
} else {
watches.push((key, callback));
}
}
if let Some(key) = removed {
watches.retain(|(k, _)| k != key);
}
}
#[cfg(feature = "no-gc")]
fn refresh_watch_lease(
watches: &TrackedMutex<Vec<(Value, Value)>>,
cached: &std::cell::RefCell<crate::publish::ValueLease>,
address: usize,
) {
let values: Vec<Value> = watches
.lock()
.unwrap()
.iter()
.flat_map(|(key, callback)| [key.clone(), callback.clone()])
.collect();
let lease = crate::publish::ValueLease::for_values(
&values,
&cljrs_gc::region::AllocationOwner::for_address(address),
);
*cached.borrow_mut() = lease;
}
#[derive(Debug)]
pub struct Var {
#[cfg(feature = "no-gc")]
read_epoch: std::cell::Cell<u64>,
pub namespace: Arc<str>,
pub name: Arc<str>,
pub value: TrackedMutex<Option<Value>>,
#[cfg(feature = "no-gc")]
value_lease: std::cell::RefCell<crate::publish::ValueLease>,
pub shared_root: Arc<arc_swap::ArcSwap<Option<crate::shared::SharedValue>>>,
pub is_macro: bool,
pub meta: TrackedMutex<Option<Value>>,
#[cfg(feature = "no-gc")]
meta_lease: std::cell::RefCell<crate::publish::ValueLease>,
#[cfg(feature = "no-gc")]
watch_lease: std::cell::RefCell<crate::publish::ValueLease>,
pub watches: TrackedMutex<Vec<(Value, Value)>>,
binding_generation: std::sync::atomic::AtomicU64,
}
impl Var {
pub fn new(namespace: impl Into<Arc<str>>, name: impl Into<Arc<str>>) -> Self {
Self {
namespace: namespace.into(),
name: name.into(),
value: TrackedMutex::new(None),
#[cfg(feature = "no-gc")]
read_epoch: Default::default(),
#[cfg(feature = "no-gc")]
value_lease: Default::default(),
shared_root: Arc::new(arc_swap::ArcSwap::new(Arc::new(None))),
is_macro: false,
meta: TrackedMutex::new(None),
#[cfg(feature = "no-gc")]
meta_lease: Default::default(),
#[cfg(feature = "no-gc")]
watch_lease: Default::default(),
watches: TrackedMutex::new(Vec::new()),
binding_generation: std::sync::atomic::AtomicU64::new(0),
}
}
pub fn from_shared_root(
namespace: impl Into<Arc<str>>,
name: impl Into<Arc<str>>,
is_macro: bool,
shared_root: Arc<arc_swap::ArcSwap<Option<crate::shared::SharedValue>>>,
) -> Self {
let local = shared_root
.load()
.as_ref()
.as_ref()
.map(crate::shared::demote);
#[cfg(feature = "no-gc")]
let (local, lease) = match local {
Some(value) => {
let (value, lease) = crate::publish::prepare_definition(
value,
&cljrs_gc::region::AllocationOwner::current(),
);
(Some(value), lease)
}
None => (None, Default::default()),
};
Self {
namespace: namespace.into(),
name: name.into(),
value: TrackedMutex::new(local),
#[cfg(feature = "no-gc")]
read_epoch: Default::default(),
#[cfg(feature = "no-gc")]
value_lease: std::cell::RefCell::new(lease),
shared_root,
is_macro,
meta: TrackedMutex::new(None),
#[cfg(feature = "no-gc")]
meta_lease: Default::default(),
#[cfg(feature = "no-gc")]
watch_lease: Default::default(),
watches: TrackedMutex::new(Vec::new()),
binding_generation: std::sync::atomic::AtomicU64::new(0),
}
}
pub fn is_bound(&self) -> bool {
self.value.lock().unwrap().is_some()
}
pub fn snapshot(&self) -> Option<crate::publish::ValueSnapshot> {
self.value
.lock()
.unwrap()
.clone()
.map(|value| crate::publish::ValueSnapshot {
value,
#[cfg(feature = "no-gc")]
lease: self.value_lease.borrow().snapshot(),
})
}
pub fn deref(&self) -> Option<Value> {
let value = self.value.lock().unwrap().clone();
#[cfg(feature = "no-gc")]
{
let epoch = cljrs_gc::region::ReadScope::epoch();
if epoch == 0 || self.read_epoch.replace(epoch) != epoch {
self.value_lease.borrow().protect_read();
}
}
value
}
pub fn binding_generation(&self) -> u64 {
self.binding_generation
.load(std::sync::atomic::Ordering::Acquire)
}
pub fn deref_shared(&self) -> Option<Value> {
self.shared_root
.load()
.as_ref()
.as_ref()
.map(crate::shared::demote)
}
pub fn bind(&self, v: Value) {
#[cfg(feature = "no-gc")]
self.read_epoch.set(0);
#[cfg(not(feature = "no-gc"))]
let v = crate::publish::publish_value(v);
#[cfg(feature = "no-gc")]
let previous_lease = self.value_lease.borrow().snapshot();
#[cfg(feature = "no-gc")]
let (v, lease) = crate::publish::prepare_definition(
v,
&cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize),
);
let prev = {
let mut slot = self.value.lock().unwrap();
slot.replace(v.clone())
};
#[cfg(feature = "no-gc")]
{
*self.value_lease.borrow_mut() = lease;
}
let shared = crate::shared::promote(&v).ok();
self.shared_root.store(Arc::new(shared));
self.binding_generation
.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
if let Some(prev) = prev {
crate::jit_hooks::notify_var_rebind(&prev, &v);
}
#[cfg(feature = "no-gc")]
{
drop(previous_lease);
}
}
pub fn add_watch(&self, key: Value, callback: Value) {
edit_watches(&self.watches, Some((key, callback)), None);
#[cfg(feature = "no-gc")]
self.refresh_watch_lease();
}
pub fn remove_watch(&self, key: &Value) {
edit_watches(&self.watches, None, Some(key));
#[cfg(feature = "no-gc")]
self.refresh_watch_lease();
}
#[cfg(feature = "no-gc")]
fn refresh_watch_lease(&self) {
refresh_watch_lease(&self.watches, &self.watch_lease, self as *const _ as usize);
}
pub fn protect_watches(&self) {
#[cfg(feature = "no-gc")]
self.watch_lease.borrow().protect_read();
}
pub fn get_meta(&self) -> Option<Value> {
#[cfg(feature = "no-gc")]
self.meta_lease.borrow().protect_read();
self.meta.lock().unwrap().clone()
}
pub fn set_meta(&self, m: Value) {
#[cfg(not(feature = "no-gc"))]
let m = crate::publish::publish_for_owner(m, self as *const _ as usize);
#[cfg(feature = "no-gc")]
let (m, lease) = crate::publish::prepare_definition(
m,
&cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize),
);
*self.meta.lock().unwrap() = Some(m);
#[cfg(feature = "no-gc")]
{
*self.meta_lease.borrow_mut() = lease;
}
}
pub fn full_name(&self) -> String {
format!("{}/{}", self.namespace, self.name)
}
}
impl cljrs_gc::Trace for Var {
#[cfg(feature = "no-gc")]
fn init_ownership(&self) {
let owner = cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize);
let lease = self.value_lease.borrow().reparent(&owner);
let old = self.value_lease.replace(lease);
drop(old);
}
#[cfg(feature = "no-gc")]
fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
#[cfg(not(feature = "no-gc"))]
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
#[cfg(not(feature = "no-gc"))]
{
let value = self.value.lock().unwrap();
if let Some(v) = value.as_ref() {
v.trace(visitor);
}
}
{
let meta = self.meta.lock().unwrap();
if let Some(m) = meta.as_ref() {
m.trace(visitor);
}
}
{
let watches = self.watches.lock().unwrap();
for (key, f) in watches.iter() {
key.trace(visitor);
f.trace(visitor);
}
}
}
}
#[derive(Debug)]
pub struct Atom {
pub value: TrackedMutex<Value>,
#[cfg(feature = "no-gc")]
value_lease: std::cell::RefCell<crate::publish::ValueLease>,
pub meta: TrackedMutex<Option<Value>>,
#[cfg(feature = "no-gc")]
meta_lease: std::cell::RefCell<crate::publish::ValueLease>,
#[cfg(feature = "no-gc")]
watch_lease: std::cell::RefCell<crate::publish::ValueLease>,
pub validator: TrackedMutex<Option<Value>>,
#[cfg(feature = "no-gc")]
validator_lease: std::cell::RefCell<crate::publish::ValueLease>,
pub watches: TrackedMutex<Vec<(Value, Value)>>,
}
impl Atom {
pub fn new(v: Value) -> Self {
#[cfg(feature = "no-gc")]
let (v, lease) =
crate::publish::prepare_definition(v, &cljrs_gc::region::AllocationOwner::current());
#[cfg(not(feature = "no-gc"))]
let v = crate::publish::publish_value(v);
Self {
value: TrackedMutex::new(v),
#[cfg(feature = "no-gc")]
value_lease: std::cell::RefCell::new(lease),
meta: TrackedMutex::new(None),
#[cfg(feature = "no-gc")]
meta_lease: Default::default(),
#[cfg(feature = "no-gc")]
watch_lease: Default::default(),
validator: TrackedMutex::new(None),
#[cfg(feature = "no-gc")]
validator_lease: Default::default(),
watches: TrackedMutex::new(Vec::new()),
}
}
pub fn snapshot(&self) -> crate::publish::ValueSnapshot {
crate::publish::ValueSnapshot {
value: self.value.lock().unwrap().clone(),
#[cfg(feature = "no-gc")]
lease: self.value_lease.borrow().snapshot(),
}
}
pub fn deref(&self) -> Value {
let snapshot = self.snapshot();
snapshot.protect_current();
snapshot.value
}
pub fn reset(&self, v: Value) -> Value {
#[cfg(not(feature = "no-gc"))]
let v = crate::publish::publish_value(v);
#[cfg(feature = "no-gc")]
let (v, lease) = crate::publish::prepare_for_owner(
v,
&cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize),
);
#[cfg(feature = "no-gc")]
lease.publish_return();
*self.value.lock().unwrap() = v.clone();
#[cfg(feature = "no-gc")]
{
*self.value_lease.borrow_mut() = lease;
}
v
}
pub fn compare_and_set(&self, expected: &Value, value: Value) -> bool {
let matches_expected = |current: &Value| {
matches!((current, expected), (Value::Vector(a), Value::Vector(b)) if GcPtr::ptr_eq(a, b))
|| current == expected
};
if !matches_expected(&self.value.lock().unwrap()) {
return false;
}
#[cfg(not(feature = "no-gc"))]
let value = crate::publish::publish_value(value);
#[cfg(feature = "no-gc")]
let (value, lease) = crate::publish::prepare_for_owner(
value,
&cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize),
);
let mut guard = self.value.lock().unwrap();
if !matches_expected(&guard) {
return false;
}
*guard = value;
drop(guard);
#[cfg(feature = "no-gc")]
{
*self.value_lease.borrow_mut() = lease;
}
true
}
pub fn add_watch(&self, key: Value, callback: Value) {
edit_watches(&self.watches, Some((key, callback)), None);
#[cfg(feature = "no-gc")]
self.refresh_watch_lease();
}
pub fn remove_watch(&self, key: &Value) {
edit_watches(&self.watches, None, Some(key));
#[cfg(feature = "no-gc")]
self.refresh_watch_lease();
}
#[cfg(feature = "no-gc")]
fn refresh_watch_lease(&self) {
refresh_watch_lease(&self.watches, &self.watch_lease, self as *const _ as usize);
}
pub fn protect_watches(&self) {
#[cfg(feature = "no-gc")]
self.watch_lease.borrow().protect_read();
}
pub fn get_meta(&self) -> Option<Value> {
#[cfg(feature = "no-gc")]
self.meta_lease.borrow().protect_read();
self.meta.lock().unwrap().clone()
}
pub fn set_meta(&self, m: Option<Value>) {
#[cfg(feature = "no-gc")]
let (m, lease) = prepare_optional(m, self as *const _ as usize);
*self.meta.lock().unwrap() = m;
#[cfg(feature = "no-gc")]
{
*self.meta_lease.borrow_mut() = lease;
}
}
pub fn get_validator(&self) -> Option<Value> {
#[cfg(feature = "no-gc")]
self.validator_lease.borrow().protect_read();
self.validator.lock().unwrap().clone()
}
pub fn set_validator(&self, vf: Option<Value>) {
#[cfg(feature = "no-gc")]
let (vf, lease) = prepare_optional(vf, self as *const _ as usize);
*self.validator.lock().unwrap() = vf;
#[cfg(feature = "no-gc")]
{
*self.validator_lease.borrow_mut() = lease;
}
}
}
impl cljrs_gc::Trace for Atom {
#[cfg(feature = "no-gc")]
fn init_ownership(&self) {
let owner = cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize);
let lease = self.value_lease.borrow().reparent(&owner);
let old = self.value_lease.replace(lease);
drop(old);
}
#[cfg(feature = "no-gc")]
fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
#[cfg(not(feature = "no-gc"))]
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
#[cfg(not(feature = "no-gc"))]
{
let value = self.value.lock().unwrap();
value.trace(visitor);
}
{
let meta = self.meta.lock().unwrap();
if let Some(m) = meta.as_ref() {
m.trace(visitor);
}
}
{
let validator = self.validator.lock().unwrap();
if let Some(vf) = validator.as_ref() {
vf.trace(visitor);
}
}
{
let watches = self.watches.lock().unwrap();
for (key, f) in watches.iter() {
key.trace(visitor);
f.trace(visitor);
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ReferClojureFilter {
pub only: Option<std::collections::HashSet<Arc<str>>>,
pub exclude: std::collections::HashSet<Arc<str>>,
pub rename: HashMap<Arc<str>, Arc<str>>,
}
impl ReferClojureFilter {
pub fn local_name(&self, name: &Arc<str>) -> Option<Arc<str>> {
if self.exclude.contains(name) {
return None;
}
if let Some(only) = &self.only
&& !only.contains(name)
{
return None;
}
Some(
self.rename
.get(name)
.cloned()
.unwrap_or_else(|| name.clone()),
)
}
}
#[derive(Debug)]
pub struct Namespace {
pub name: Arc<str>,
pub interns: TrackedMutex<HashMap<Arc<str>, GcPtr<Var>>>,
pub refers: TrackedMutex<HashMap<Arc<str>, GcPtr<Var>>>,
pub aliases: TrackedMutex<HashMap<Arc<str>, Arc<str>>>,
pub source_file: TrackedMutex<Option<Arc<str>>>,
pub git_repo_root: TrackedMutex<Option<Arc<str>>>,
pub is_versioned: bool,
pub meta: TrackedMutex<Option<Value>>,
pub refer_clojure_filter: TrackedMutex<Option<ReferClojureFilter>>,
}
impl Namespace {
pub fn new(name: impl Into<Arc<str>>) -> Self {
Self {
name: name.into(),
interns: TrackedMutex::new(HashMap::new()),
refers: TrackedMutex::new(HashMap::new()),
aliases: TrackedMutex::new(HashMap::new()),
source_file: TrackedMutex::new(None),
git_repo_root: TrackedMutex::new(None),
is_versioned: false,
meta: TrackedMutex::new(None),
refer_clojure_filter: TrackedMutex::new(None),
}
}
pub fn new_versioned(name: impl Into<Arc<str>>) -> Self {
let mut namespace = Self::new(name);
namespace.is_versioned = true;
namespace
}
pub fn set_source_location(&self, file: &str, repo_root: Option<&str>) {
*self.source_file.lock().unwrap() = Some(Arc::from(file));
*self.git_repo_root.lock().unwrap() = repo_root.map(Arc::from);
}
pub fn get_meta(&self) -> Option<Value> {
crate::publish::read_slot(self as *const _ as usize, 0);
self.meta.lock().unwrap().clone()
}
pub fn set_meta(&self, m: Value) {
let m = crate::publish::publish_for_owner(m, self as *const _ as usize);
*self.meta.lock().unwrap() = Some(m);
}
}
impl Drop for Namespace {
fn drop(&mut self) {
crate::publish::clear_slots(self as *const _ as usize);
}
}
impl cljrs_gc::Trace for Namespace {
#[cfg(feature = "no-gc")]
fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {
}
#[cfg(not(feature = "no-gc"))]
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
use cljrs_gc::GcVisitor as _;
{
let interns = self.interns.lock().unwrap();
for var in interns.values() {
visitor.visit(var);
}
}
{
let refers = self.refers.lock().unwrap();
for var in refers.values() {
visitor.visit(var);
}
}
{
let meta = self.meta.lock().unwrap();
if let Some(m) = meta.as_ref() {
m.trace(visitor);
}
}
}
}
pub type NativeFnPtr = fn(&[Value]) -> crate::error::ValueResult<Value>;
pub type NativeFnFunc = Arc<dyn Fn(&[Value]) -> crate::error::ValueResult<Value>>;
#[derive(Clone, Debug)]
pub enum Arity {
Fixed(usize),
Variadic { min: usize },
}
pub struct NativeFn {
pub name: Arc<str>,
pub arity: Arity,
pub func: NativeFnFunc,
pub captures: Arc<[Value]>,
pub blocking_func: Option<NativeFnFunc>,
}
impl NativeFn {
pub fn new(name: impl Into<Arc<str>>, arity: Arity, func: NativeFnPtr) -> Self {
Self {
name: name.into(),
arity,
blocking_func: None,
captures: Default::default(),
func: Arc::new(move |args| {
crate::realization::catch(|| func(args))
.unwrap_or_else(|message| Err(crate::ValueError::Other(message)))
}),
}
}
pub fn with_closure(
name: impl Into<Arc<str>>,
arity: Arity,
func: impl Fn(&[Value]) -> crate::error::ValueResult<Value> + 'static,
) -> Self {
Self {
name: name.into(),
arity,
blocking_func: None,
captures: Default::default(),
func: Arc::new(move |args| {
crate::realization::catch(|| func(args))
.unwrap_or_else(|message| Err(crate::ValueError::Other(message)))
}),
}
}
pub fn with_captures(
name: impl Into<Arc<str>>,
arity: Arity,
captures: Vec<Value>,
func: impl Fn(&[Value], &[Value]) -> crate::ValueResult<Value> + 'static,
) -> Self {
let captures: Arc<[Value]> = captures.into();
let shared = captures.clone();
let mut native = Self::with_closure(name, arity, move |args| func(&shared, args));
native.captures = captures;
native
}
pub fn with_blocking(
name: impl Into<Arc<str>>,
arity: Arity,
func: impl Fn(&[Value]) -> crate::ValueResult<Value> + 'static,
blocking_func: impl Fn(&[Value]) -> crate::ValueResult<Value> + 'static,
) -> Self {
let mut native = Self::with_closure(name, arity, func);
native.blocking_func = Some(Arc::new(move |args| {
crate::realization::catch(|| blocking_func(args))
.unwrap_or_else(|message| Err(crate::ValueError::Other(message)))
}));
native
}
pub fn call_blocking(&self, args: &[Value]) -> crate::ValueResult<Value> {
let expected = match self.arity {
Arity::Fixed(n) if args.len() != n => Some(n.to_string()),
Arity::Variadic { min } if args.len() < min => Some(format!("{min}+")),
_ => None,
};
if let Some(expected) = expected {
return Err(crate::ValueError::ArityError {
name: self.name.to_string(),
expected,
got: args.len(),
});
}
let adapter = self.blocking_func.as_ref().ok_or_else(|| {
crate::ValueError::Other(format!(
"blocking cannot offload {}: this function has no owned-work adapter",
self.name
))
})?;
adapter(args)
}
}
impl std::fmt::Debug for NativeFn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NativeFn")
.field("name", &self.name)
.field("arity", &self.arity)
.field("func", &"<fn>")
.finish()
}
}
impl cljrs_gc::Trace for NativeFn {
#[cfg(feature = "no-gc")]
fn immutable_children(&self) -> bool {
true
}
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
for value in self.captures.iter() {
value.trace(visitor);
}
}
}
#[derive(Debug, Clone)]
pub struct CljxFnArity {
pub params: Vec<Arc<str>>,
pub rest_param: Option<Arc<str>>,
pub body: Vec<Form>,
pub destructure_params: Vec<(usize, Form)>,
pub destructure_rest: Option<Form>,
pub ir_arity_id: u64,
pub param_hints: Vec<Option<TypeHint>>,
pub rest_hint: Option<TypeHint>,
}
impl CljxFnArity {
pub fn heap_size(&self) -> usize {
self.params.capacity() * mem::size_of::<Arc<str>>()
+ self.body.capacity() * mem::size_of::<Form>()
+ self.body.iter().map(|f| f.heap_size()).sum::<usize>()
+ self.destructure_params.capacity() * mem::size_of::<(usize, Form)>()
+ self.destructure_params.iter().map(|(_, f)| f.heap_size()).sum::<usize>()
+ self.destructure_rest.as_ref()
.map_or(0, |f| mem::size_of::<Form>() + f.heap_size())
+ self.param_hints.capacity() * mem::size_of::<Option<TypeHint>>()
}
}
#[derive(Debug, Clone)]
pub struct CljxFn {
pub name: Option<Arc<str>>,
pub arities: Vec<CljxFnArity>,
pub closed_over_names: Vec<Arc<str>>,
pub closed_over_vals: Vec<Value>,
pub is_macro: bool,
pub is_async: bool,
pub defining_ns: Arc<str>,
pub self_ptr: Option<GcPtr<CljxFn>>,
}
impl CljxFn {
pub fn new(
name: Option<Arc<str>>,
arities: Vec<CljxFnArity>,
closed_over_names: Vec<Arc<str>>,
closed_over_vals: Vec<Value>,
is_macro: bool,
defining_ns: Arc<str>,
) -> Self {
Self {
name,
arities,
closed_over_names,
closed_over_vals,
is_macro,
is_async: false,
defining_ns,
self_ptr: None,
}
}
}
impl cljrs_gc::Trace for CljxFn {
#[cfg(feature = "no-gc")]
fn immutable_children(&self) -> bool {
true
}
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
use cljrs_gc::GcVisitor as _;
for v in &self.closed_over_vals {
v.trace(visitor);
}
if let Some(ref p) = self.self_ptr {
visitor.visit(p);
}
}
fn gc_size_extra(&self) -> usize {
self.arities.capacity() * mem::size_of::<CljxFnArity>()
+ self
.arities
.iter()
.map(CljxFnArity::heap_size)
.sum::<usize>()
}
}
#[derive(Debug)]
pub struct BoundFn {
pub wrapped: Value,
pub captured_bindings: HashMap<usize, Value>,
}
impl cljrs_gc::Trace for BoundFn {
#[cfg(feature = "no-gc")]
fn immutable_children(&self) -> bool {
true
}
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
self.wrapped.trace(visitor);
for val in self.captured_bindings.values() {
val.trace(visitor);
}
}
fn gc_size_extra(&self) -> usize {
self.captured_bindings.capacity() * (1 + mem::size_of::<usize>() + mem::size_of::<Value>())
}
}
pub trait Thunk: std::fmt::Debug + cljrs_gc::Trace {
fn force(&self) -> Result<Value, String>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Realizer {
thread: std::thread::ThreadId,
fiber: Option<u64>,
}
impl Realizer {
pub fn current() -> Self {
Self {
thread: std::thread::current().id(),
fiber: crate::parking::current(),
}
}
pub fn ensure_can_wait(self) -> Result<(), String> {
if self.thread == std::thread::current().id()
&& self.fiber.is_some()
&& crate::parking::current().is_none()
{
Err("cannot block the isolate thread waiting for a parked fiber. Run this code inside a fiber.".into())
} else {
Ok(())
}
}
}
struct RealizationGuard<'a> {
lazy: Option<&'a LazySeq>,
delay: Option<&'a Delay>,
}
impl Drop for RealizationGuard<'_> {
fn drop(&mut self) {
if let Some(cell) = self.lazy {
let mut state = cell.state.lock().unwrap();
if let LazySeqState::Realizing { thunk, .. } = &*state {
*state = LazySeqState::Pending(thunk.clone());
}
drop(state);
cell.ready.notify_all();
}
if let Some(cell) = self.delay {
let mut state = cell.state.lock().unwrap();
if let DelayState::Realizing { thunk, .. } = &*state {
*state = DelayState::Pending(thunk.clone());
}
drop(state);
cell.ready.notify_all();
}
}
}
pub enum LazySeqState {
Pending(Rc<dyn Thunk>),
Realizing {
owner: Realizer,
thunk: Rc<dyn Thunk>,
},
Forced(Value),
Error(String),
}
pub struct LazySeq {
ready: Event,
pub state: TrackedMutex<LazySeqState>,
}
impl LazySeq {
pub fn new(thunk: Box<dyn Thunk>) -> Self {
Self {
state: TrackedMutex::new(LazySeqState::Pending(Rc::from(thunk))),
ready: Event::default(),
}
}
pub fn realize(&self) -> Value {
match self.try_realize() {
Ok(value) => value,
Err(error) => {
assert!(self.error().is_some(), "{error}");
Value::Nil
}
}
}
pub fn try_realize(&self) -> Result<Value, String> {
let owner = Realizer::current();
let thunk = loop {
let listener = self.ready.listen();
let mut state = self.state.lock().unwrap();
match &*state {
LazySeqState::Forced(v) => {
let value = v.clone();
drop(state);
crate::publish::read_slot(self as *const _ as usize, 0);
return Ok(value);
}
LazySeqState::Error(error) => return Err(error.clone()),
LazySeqState::Realizing { owner: active, .. } => {
if *active == owner {
return Ok(Value::Nil);
}
active.ensure_can_wait()?;
drop(state);
listener.wait(None);
continue;
}
LazySeqState::Pending(_) => {}
}
let LazySeqState::Pending(thunk) =
mem::replace(&mut *state, LazySeqState::Forced(Value::Nil))
else {
unreachable!()
};
*state = LazySeqState::Realizing {
owner,
thunk: thunk.clone(),
};
break thunk;
};
let mut guard = RealizationGuard {
lazy: Some(self),
delay: None,
};
let result = match thunk.force() {
Ok(result) => {
let result = crate::publish::publish_for_owner(result, self as *const _ as usize);
*self.state.lock().unwrap() = LazySeqState::Forced(result.clone());
Ok(result)
}
Err(msg) => {
*self.state.lock().unwrap() = LazySeqState::Error(msg.clone());
Err(msg)
}
};
drop(thunk);
#[cfg(feature = "no-gc")]
cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize).set_slot(
self as *const _ as usize,
usize::MAX,
&Default::default(),
);
guard.lazy = None;
if result.is_ok() {
crate::publish::read_slot(self as *const _ as usize, 0);
}
self.ready.notify_all();
result
}
pub fn error(&self) -> Option<String> {
let guard = self.state.lock().unwrap();
if let LazySeqState::Error(e) = &*guard {
Some(e.clone())
} else {
None
}
}
}
impl std::fmt::Debug for LazySeq {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "LazySeq(...)")
}
}
impl Drop for LazySeq {
fn drop(&mut self) {
crate::publish::clear_slots(self as *const _ as usize);
}
}
impl cljrs_gc::Trace for LazySeq {
#[cfg(feature = "no-gc")]
fn init_ownership(&self) {
let state = self.state.lock().unwrap();
if let LazySeqState::Pending(thunk) = &*state {
struct ThunkRef<'a>(&'a dyn Thunk);
impl cljrs_gc::Trace for ThunkRef<'_> {
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
self.0.trace(visitor);
}
}
let lease = cljrs_gc::region::RegionLease::for_value(&ThunkRef(thunk.as_ref()));
cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize).set_slot(
self as *const _ as usize,
usize::MAX,
&lease,
);
}
}
#[cfg(feature = "no-gc")]
fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
#[cfg(not(feature = "no-gc"))]
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
{
let state = self.state.lock().unwrap();
match &*state {
LazySeqState::Pending(thunk) => thunk.trace(visitor),
LazySeqState::Realizing { thunk, .. } => thunk.trace(visitor),
LazySeqState::Forced(v) => v.trace(visitor),
LazySeqState::Error(_) => {}
}
}
}
}
#[derive(Debug, Clone)]
pub struct CljxCons {
pub head: Value,
pub tail: Value,
}
impl cljrs_gc::Trace for CljxCons {
#[cfg(feature = "no-gc")]
fn immutable_children(&self) -> bool {
true
}
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
self.head.trace(visitor);
self.tail.trace(visitor);
}
}
pub struct Volatile {
pub value: TrackedMutex<Value>,
#[cfg(feature = "no-gc")]
value_lease: std::cell::RefCell<crate::publish::ValueLease>,
}
impl Volatile {
pub fn new(v: Value) -> Self {
#[cfg(feature = "no-gc")]
let (v, lease) =
crate::publish::prepare_for_owner(v, &cljrs_gc::region::AllocationOwner::current());
#[cfg(not(feature = "no-gc"))]
let v = crate::publish::publish_value(v);
Self {
value: TrackedMutex::new(v),
#[cfg(feature = "no-gc")]
value_lease: std::cell::RefCell::new(lease),
}
}
pub fn snapshot(&self) -> crate::publish::ValueSnapshot {
crate::publish::ValueSnapshot {
value: self.value.lock().unwrap().clone(),
#[cfg(feature = "no-gc")]
lease: self.value_lease.borrow().snapshot(),
}
}
pub fn deref(&self) -> Value {
let snapshot = self.snapshot();
snapshot.protect_current();
snapshot.value
}
pub fn reset(&self, v: Value) -> Value {
#[cfg(not(feature = "no-gc"))]
let v = crate::publish::publish_value(v);
#[cfg(feature = "no-gc")]
let (v, lease) = crate::publish::prepare_for_owner(
v,
&cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize),
);
#[cfg(feature = "no-gc")]
lease.publish_return();
*self.value.lock().unwrap() = v.clone();
#[cfg(feature = "no-gc")]
{
*self.value_lease.borrow_mut() = lease;
}
v
}
}
impl std::fmt::Debug for Volatile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Volatile")
}
}
impl cljrs_gc::Trace for Volatile {
#[cfg(feature = "no-gc")]
fn init_ownership(&self) {
let owner = cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize);
let lease = self.value_lease.borrow().reparent(&owner);
let old = self.value_lease.replace(lease);
drop(old);
}
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
#[cfg(not(feature = "no-gc"))]
{
let value = self.value.lock().unwrap();
value.trace(visitor);
}
}
}
pub enum DelayState {
Error(String),
Pending(Rc<dyn Thunk>),
Realizing {
owner: Realizer,
thunk: Rc<dyn Thunk>,
},
Forced(Value),
}
pub struct Delay {
ready: Event,
pub state: TrackedMutex<DelayState>,
}
impl Delay {
pub fn new(thunk: Box<dyn Thunk>) -> Self {
Self {
state: TrackedMutex::new(DelayState::Pending(Rc::from(thunk))),
ready: Event::default(),
}
}
pub fn force(&self) -> Result<Value, String> {
let owner = Realizer::current();
let thunk = loop {
let listener = self.ready.listen();
let mut state = self.state.lock().unwrap();
match &*state {
DelayState::Forced(v) => {
let value = v.clone();
drop(state);
crate::publish::read_slot(self as *const _ as usize, 0);
return Ok(value);
}
DelayState::Error(e) => return Err(e.clone()),
DelayState::Realizing { owner: active, .. } => {
if *active == owner {
return Err("recursive delay realization".into());
}
active.ensure_can_wait()?;
drop(state);
listener.wait(None);
continue;
}
DelayState::Pending(_) => {}
}
let DelayState::Pending(thunk) =
mem::replace(&mut *state, DelayState::Forced(Value::Nil))
else {
unreachable!()
};
*state = DelayState::Realizing {
owner,
thunk: thunk.clone(),
};
break thunk;
};
let mut guard = RealizationGuard {
lazy: None,
delay: Some(self),
};
let result = thunk
.force()
.map(|value| crate::publish::publish_for_owner(value, self as *const _ as usize));
*self.state.lock().unwrap() = match &result {
Ok(value) => DelayState::Forced(value.clone()),
Err(error) => DelayState::Error(error.clone()),
};
drop(thunk);
#[cfg(feature = "no-gc")]
cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize).set_slot(
self as *const _ as usize,
usize::MAX,
&Default::default(),
);
guard.delay = None;
if result.is_ok() {
crate::publish::read_slot(self as *const _ as usize, 0);
}
self.ready.notify_all();
result
}
pub fn is_realized(&self) -> bool {
matches!(
&*self.state.lock().unwrap(),
DelayState::Forced(_) | DelayState::Error(_)
)
}
}
impl std::fmt::Debug for Delay {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Delay")
}
}
impl Drop for Delay {
fn drop(&mut self) {
crate::publish::clear_slots(self as *const _ as usize);
}
}
impl cljrs_gc::Trace for Delay {
#[cfg(feature = "no-gc")]
fn init_ownership(&self) {
let state = self.state.lock().unwrap();
if let DelayState::Pending(thunk) = &*state {
struct ThunkRef<'a>(&'a dyn Thunk);
impl cljrs_gc::Trace for ThunkRef<'_> {
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
self.0.trace(visitor);
}
}
let lease = cljrs_gc::region::RegionLease::for_value(&ThunkRef(thunk.as_ref()));
cljrs_gc::region::AllocationOwner::for_address(self as *const _ as usize).set_slot(
self as *const _ as usize,
usize::MAX,
&lease,
);
}
}
#[cfg(feature = "no-gc")]
fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
#[cfg(not(feature = "no-gc"))]
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
{
let state = self.state.lock().unwrap();
match &*state {
DelayState::Pending(thunk) => thunk.trace(visitor),
DelayState::Realizing { thunk, .. } => thunk.trace(visitor),
DelayState::Error(_) => {}
DelayState::Forced(v) => v.trace(visitor),
}
}
}
}
pub struct CljxPromise {
pub ready: Event,
pub value: Mutex<Option<Value>>,
pub cond: Condvar,
}
impl CljxPromise {
pub fn new() -> Self {
Self {
value: Mutex::new(None),
cond: Condvar::new(),
ready: Event::default(),
}
}
pub fn deliver(&self, v: Value) {
if self.value.lock().unwrap().is_some() {
return;
}
let v = crate::publish::publish_for_owner(v, self as *const _ as usize);
let mut guard = self.value.lock().unwrap();
if guard.is_none() {
*guard = Some(v);
self.cond.notify_all();
drop(guard);
self.ready.notify_all();
}
}
pub fn deref_blocking(&self) -> Value {
self.wait_value(None).unwrap()
}
pub fn wait_value(&self, timeout: Option<Duration>) -> Option<Value> {
let deadline = timeout.map(|d| Instant::now() + d);
loop {
let listener = self.ready.listen();
let value = self.value.lock().unwrap().clone();
if let Some(value) = value {
crate::publish::read_slot(self as *const _ as usize, 0);
return Some(value);
}
if !listener.wait(deadline.map(|d| d.saturating_duration_since(Instant::now()))) {
return self.value.lock().unwrap().clone();
}
}
}
pub fn is_realized(&self) -> bool {
self.value.lock().unwrap().is_some()
}
}
impl Drop for CljxPromise {
fn drop(&mut self) {
crate::publish::clear_slots(self as *const _ as usize);
}
}
impl Default for CljxPromise {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for CljxPromise {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Promise")
}
}
impl cljrs_gc::Trace for CljxPromise {
#[cfg(feature = "no-gc")]
fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
#[cfg(not(feature = "no-gc"))]
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
{
let value = self.value.lock().unwrap();
if let Some(v) = value.as_ref() {
v.trace(visitor);
}
}
}
}
pub enum FutureState {
Running,
Done(Value),
Failed(Value),
GasExhausted,
MemoryLimitExceeded(cljrs_gc::MemoryLimitExceeded),
Cancelled,
}
pub const FUTURE_CANCELLED_MSG: &str = "future was cancelled";
pub struct CljxFuture {
pub ready: Event,
pub state: Mutex<FutureState>,
pub cond: Condvar,
executor_thread: Option<std::thread::ThreadId>,
observed: std::sync::atomic::AtomicBool,
}
impl CljxFuture {
pub fn new() -> Self {
Self {
state: Mutex::new(FutureState::Running),
cond: Condvar::new(),
ready: Event::default(),
executor_thread: None,
observed: std::sync::atomic::AtomicBool::new(false),
}
}
pub fn new_local() -> Self {
let mut future = Self::new();
future.executor_thread = Some(std::thread::current().id());
future
}
pub fn wait_value(&self, timeout: Option<Duration>) -> crate::ValueResult<Option<Value>> {
let deadline = timeout.map(|d| Instant::now() + d);
loop {
let listener = self.ready.listen();
if let Some(result) = self.settled_value() {
return result.map(Some);
}
if deadline.is_some_and(|deadline| deadline <= Instant::now()) {
return self.settled_value().transpose();
}
if crate::parking::current().is_none()
&& self.executor_thread == Some(std::thread::current().id())
{
return Err(crate::ValueError::Other(
"cannot wait for an isolate future outside a fiber; use await in async code or fiber::run_root for synchronous evaluation".into(),
));
}
if !listener.wait(deadline.map(|d| d.saturating_duration_since(Instant::now()))) {
return self.settled_value().transpose();
}
}
}
pub fn settled_value(&self) -> Option<crate::ValueResult<Value>> {
use crate::ValueError;
let state = self.state.lock().unwrap();
let value = match &*state {
FutureState::Running => return None,
FutureState::Done(v) => Ok(v.clone()),
FutureState::Failed(v) => Err(ValueError::Thrown(v.clone())),
FutureState::GasExhausted => Err(ValueError::GasExhausted),
FutureState::MemoryLimitExceeded(f) => Err(ValueError::MemoryLimitExceeded(*f)),
FutureState::Cancelled => {
drop(state);
return Some(Err(ValueError::Thrown(Self::cancelled_error())));
}
};
self.mark_observed();
drop(state);
#[cfg(feature = "no-gc")]
match &value {
Ok(value) | Err(crate::ValueError::Thrown(value)) => {
cljrs_gc::region::RegionLease::for_value(value).protect_read()
}
_ => {}
}
Some(value)
}
pub fn is_done(&self) -> bool {
!matches!(&*self.state.lock().unwrap(), FutureState::Running)
}
pub fn is_cancelled(&self) -> bool {
matches!(&*self.state.lock().unwrap(), FutureState::Cancelled)
}
pub fn cancel(&self) -> bool {
let mut state = self.state.lock().unwrap();
if matches!(&*state, FutureState::Running) {
*state = FutureState::Cancelled;
self.cond.notify_all();
drop(state);
self.ready.notify_all();
true
} else {
false
}
}
pub fn cancelled_error() -> Value {
Value::Error(GcPtr::new(crate::ExceptionInfo::new(
crate::ValueError::Other(FUTURE_CANCELLED_MSG.to_string()),
FUTURE_CANCELLED_MSG.to_string(),
None,
None,
)))
}
pub fn mark_observed(&self) {
self.observed
.store(true, std::sync::atomic::Ordering::Relaxed);
}
}
impl Drop for CljxFuture {
fn drop(&mut self) {
if !self.observed.load(std::sync::atomic::Ordering::Relaxed)
&& let Ok(state) = self.state.lock()
&& matches!(
&*state,
FutureState::Failed(_)
| FutureState::GasExhausted
| FutureState::MemoryLimitExceeded(_)
)
{
eprintln!(
"[clojurust warning] a failed future was discarded without its error \
being observed (no await/deref); the failure was lost"
);
}
crate::publish::clear_slots(self as *const _ as usize);
}
}
impl Default for CljxFuture {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for CljxFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Future")
}
}
impl cljrs_gc::Trace for CljxFuture {
#[cfg(feature = "no-gc")]
fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
#[cfg(not(feature = "no-gc"))]
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
{
let state = self.state.lock().unwrap();
if let FutureState::Done(v) | FutureState::Failed(v) = &*state {
v.trace(visitor);
}
}
}
}
pub struct Agent {
pub state: Arc<Mutex<Value>>,
pub error: Arc<Mutex<Option<Value>>>,
pub watches: TrackedMutex<Vec<(Value, Value)>>,
}
impl Agent {
pub fn get_state(&self) -> Value {
self.state.lock().unwrap().clone()
}
pub fn get_error(&self) -> Option<Value> {
self.error.lock().unwrap().clone()
}
pub fn clear_error(&self) {
*self.error.lock().unwrap() = None;
}
}
impl std::fmt::Debug for Agent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Agent")
}
}
impl cljrs_gc::Trace for Agent {
fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
{
let state = self.state.lock().unwrap();
state.trace(visitor);
}
{
let error = self.error.lock().unwrap();
if let Some(e) = error.as_ref() {
e.trace(visitor);
}
}
{
let watches = self.watches.lock().unwrap();
for (key, f) in watches.iter() {
key.trace(visitor);
f.trace(visitor);
}
}
}
}
#[cfg(test)]
mod var_tests {
use super::*;
use crate::shared::SharedValue;
#[test]
fn native_captures_share_one_immutable_allocation() {
let native = NativeFn::with_captures(
"capture",
Arity::Fixed(0),
vec![Value::Long(7)],
|captures, _| Ok(captures[0].clone()),
);
assert_eq!(Arc::strong_count(&native.captures), 2);
assert_eq!((native.func)(&[]).unwrap(), Value::Long(7));
}
#[test]
fn bind_promotable_mirrors_shared_root() {
let var = Var::new("user", "x");
assert!(var.shared_root.load().is_none());
var.bind(Value::Long(7));
assert!(matches!(
var.shared_root.load().as_ref().as_ref(),
Some(SharedValue::Long(7))
));
assert_eq!(var.deref(), Some(Value::Long(7)));
assert_eq!(var.deref_shared(), Some(Value::Long(7)));
}
#[test]
fn bind_nonpromotable_clears_shared_root() {
let var = Var::new("user", "f");
var.bind(Value::Long(1));
assert!(var.shared_root.load().is_some());
let f = Value::NativeFunction(GcPtr::new(NativeFn::new("f", Arity::Fixed(0), |_| {
Ok(Value::Nil)
})));
var.bind(f);
assert!(var.shared_root.load().is_none());
assert!(var.is_bound());
assert_eq!(var.deref_shared(), None);
}
#[test]
fn from_shared_root_seeds_local_slot() {
let src = Var::new("user", "y");
src.bind(Value::Long(99));
let recv = Var::from_shared_root("user", "y", false, src.shared_root.clone());
assert_eq!(recv.deref(), Some(Value::Long(99)));
src.bind(Value::Long(100));
assert_eq!(recv.deref_shared(), Some(Value::Long(100)));
}
}