use crate::{
binding::{ractor, vm},
rubysys::ractor::RbRactorLocalKey,
AnyException, AnyObject, Object, IO,
};
pub struct Ractor;
impl Ractor {
pub fn stdin() -> IO {
IO::from(ractor::stdin())
}
pub fn stdout() -> IO {
IO::from(ractor::stdout())
}
pub fn stderr() -> IO {
IO::from(ractor::stderr())
}
pub fn set_stdin(io: &IO) {
ractor::set_stdin(io.value())
}
pub fn set_stdout(io: &IO) {
ractor::set_stdout(io.value())
}
pub fn set_stderr(io: &IO) {
ractor::set_stderr(io.value())
}
pub fn is_shareable<T: Object>(object: &T) -> bool {
ractor::is_shareable(object.value())
}
pub fn make_shareable<T: Object>(object: &T) -> Result<T, AnyException> {
let object = object.value();
vm::protect_value(|| ractor::make_shareable(object))
.map(T::from)
.map_err(AnyException::from)
}
pub fn make_shareable_copy<T: Object>(object: &T) -> Result<T, AnyException> {
let object = object.value();
vm::protect_value(|| ractor::make_shareable_copy(object))
.map(T::from)
.map_err(AnyException::from)
}
}
#[derive(Clone, Copy, Debug)]
pub struct RactorLocalKey {
key: RbRactorLocalKey,
}
unsafe impl Send for RactorLocalKey {}
unsafe impl Sync for RactorLocalKey {}
impl RactorLocalKey {
pub fn new() -> Self {
RactorLocalKey {
key: ractor::local_storage_value_newkey(),
}
}
pub fn get(&self) -> Option<AnyObject> {
ractor::local_storage_value_lookup(self.key).map(AnyObject::from)
}
pub fn set<T: Object>(&self, value: &T) {
ractor::local_storage_value_set(self.key, value.value())
}
}
#[cfg(test)]
mod tests {
use super::{Ractor, RactorLocalKey};
use crate::{AnyObject, Array, Class, NilClass, Object, RString, Symbol, IO, VM};
lazy_static! {
static ref RUTIE_TEST_KEY: RactorLocalKey = RactorLocalKey::new();
}
crate::class!(RutieRactorLocal);
crate::methods!(
RutieRactorLocal,
_rtself,
fn rutie_ractor_local_get() -> AnyObject {
RUTIE_TEST_KEY
.get()
.unwrap_or_else(|| NilClass::new().into())
},
fn rutie_ractor_local_set(value: AnyObject) -> NilClass {
RUTIE_TEST_KEY.set(&value.unwrap());
NilClass::new()
},
fn rutie_ractor_shareable(value: AnyObject) -> crate::Boolean {
crate::Boolean::new(Ractor::is_shareable(&value.unwrap()))
}
);
#[test]
fn test_ractor_local_storage_per_ractor() {
crate::on_ruby_thread(|| {
Class::new("RutieRactorLocal", None).define(|klass| {
unsafe { VM::ext_ractor_safe(true) };
klass.def_self("get", rutie_ractor_local_get);
klass.def_self("set", rutie_ractor_local_set);
klass.def_self("shareable?", rutie_ractor_shareable);
VM::ext_ractor_unsafe();
});
assert!(RUTIE_TEST_KEY.get().is_none());
RUTIE_TEST_KEY.set(&Symbol::new("main"));
let results = VM::eval(
"Warning[:experimental] = false
GC.start
rs = 3.times.map do |i|
Ractor.new(i) do |i|
before = RutieRactorLocal.get
RutieRactorLocal.set(\"ractor #{i}\")
GC.start
[before, RutieRactorLocal.get, RutieRactorLocal.shareable?(1), RutieRactorLocal.shareable?([])]
end
end
rs.map(&:take).inspect",
)
.unwrap()
.try_convert_to::<RString>()
.unwrap();
assert_eq!(
results.to_str(),
"[[nil, \"ractor 0\", true, false], [nil, \"ractor 1\", true, false], \
[nil, \"ractor 2\", true, false]]"
);
RUTIE_TEST_KEY.set(&RString::new_utf8("kept"));
crate::GC::start();
assert_eq!(
RUTIE_TEST_KEY
.get()
.unwrap()
.try_convert_to::<RString>()
.unwrap()
.to_str(),
"kept"
);
let other = RactorLocalKey::new();
assert!(other.get().is_none());
});
}
#[test]
fn test_ractor_shareable() {
crate::on_ruby_thread(|| {
assert!(Ractor::is_shareable(&NilClass::new()));
assert!(Ractor::is_shareable(&Symbol::new("sym")));
assert!(Ractor::is_shareable(&Class::string()));
assert!(!Ractor::is_shareable(&Array::new()));
let nested = VM::eval("[{ key: ['value'] }]").unwrap();
assert!(!Ractor::is_shareable(&nested));
let copy = Ractor::make_shareable_copy(&nested).unwrap();
assert!(Ractor::is_shareable(©));
assert!(!copy.is_equal(&nested));
assert!(!nested.is_frozen());
let shared = Ractor::make_shareable(&nested).unwrap();
assert!(shared.is_equal(&nested));
assert!(Ractor::is_shareable(&nested));
let error = Ractor::make_shareable(&VM::eval("Mutex.new").unwrap()).unwrap_err();
assert!(Class::from_existing("Ractor")
.get_nested_class("Error")
.case_equals(&error));
});
}
#[test]
fn test_ractor_streams() {
crate::on_ruby_thread(|| {
assert!(Ractor::stdin().is_equal(&IO::stdin()));
assert!(Ractor::stdout().is_equal(&IO::stdout()));
assert!(Ractor::stderr().is_equal(&IO::stderr()));
let pipe = VM::eval("IO.pipe")
.unwrap()
.try_convert_to::<Array>()
.unwrap();
let reader = pipe.at(0).try_convert_to::<IO>().unwrap();
let writer = pipe.at(1).try_convert_to::<IO>().unwrap();
let original = Ractor::stdout();
Ractor::set_stdout(&writer);
assert!(VM::eval("$stdout").unwrap().is_equal(&writer));
VM::eval("print 'to pipe'").unwrap();
Ractor::set_stdout(&original);
writer.close().unwrap();
let output = unsafe { reader.send("read", &[]) };
assert_eq!(
output.try_convert_to::<RString>().unwrap().to_str(),
"to pipe"
);
assert!(Ractor::stdout().is_equal(&original));
});
}
}