isorun 0.1.0.pre-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,156 @@
1
+ use deno_core::error::AnyError;
2
+ use magnus::r_hash::ForEach;
3
+ use magnus::value::{Qfalse, Qtrue};
4
+ use magnus::{
5
+ Integer, RArray, RFloat, RHash, RString, RStruct, Symbol, Value, QFALSE,
6
+ QNIL, QTRUE,
7
+ };
8
+ use v8::{Array, GetPropertyNamesArgs, HandleScope, Local, Object};
9
+
10
+ pub fn convert_v8_to_ruby(
11
+ value: Local<v8::Value>,
12
+ scope: &mut HandleScope,
13
+ ) -> Result<Value, AnyError> {
14
+ if value.is_null() {
15
+ return Ok(Value::from(QNIL));
16
+ }
17
+
18
+ if value.is_int32() {
19
+ return Ok(Value::from(Integer::from_i64(
20
+ value.int32_value(scope).unwrap() as i64,
21
+ )));
22
+ }
23
+
24
+ if value.is_number() {
25
+ return Ok(Value::from(
26
+ RFloat::from_f64(value.number_value(scope).unwrap()).unwrap(),
27
+ ));
28
+ }
29
+
30
+ if value.is_true() {
31
+ return Ok(Value::from(QTRUE));
32
+ }
33
+
34
+ if value.is_false() {
35
+ return Ok(Value::from(QFALSE));
36
+ }
37
+
38
+ if value.is_string() {
39
+ let raw = value.to_rust_string_lossy(scope);
40
+ return Ok(Value::from(RString::from(raw)));
41
+ }
42
+
43
+ if value.is_array() {
44
+ let arr = Local::<Array>::try_from(value).unwrap();
45
+ let length = arr.length();
46
+ let r_arr = RArray::with_capacity(length as usize);
47
+ for i in 0..length {
48
+ let raw = arr.get_index(scope, i).unwrap();
49
+ let val = convert_v8_to_ruby(raw, scope).unwrap();
50
+ r_arr.push(val).expect("cannot add item to array");
51
+ }
52
+ return Ok(Value::from(r_arr));
53
+ }
54
+
55
+ if value.is_object() {
56
+ let obj = Local::<Object>::try_from(value).unwrap();
57
+ let properties = obj
58
+ .get_own_property_names(scope, GetPropertyNamesArgs::default())
59
+ .unwrap();
60
+ let length = properties.length();
61
+ let r_hash = RHash::new();
62
+ for i in 0..length {
63
+ let raw_key = properties.get_index(scope, i).unwrap();
64
+ let raw_val = obj.get(scope, raw_key).unwrap();
65
+ let key = convert_v8_to_ruby(raw_key, scope).unwrap();
66
+ let val = convert_v8_to_ruby(raw_val, scope).unwrap();
67
+ r_hash.aset(key, val).expect("cannot set item to hash");
68
+ }
69
+ return Ok(Value::from(r_hash));
70
+ }
71
+
72
+ Ok(Value::from(QNIL))
73
+ }
74
+
75
+ pub fn convert_ruby_to_v8<'s>(
76
+ value: Value,
77
+ scope: &mut HandleScope<'s>,
78
+ ) -> Result<Local<'s, v8::Value>, AnyError> {
79
+ if value.is_nil() {
80
+ return Ok(v8::null(scope).into());
81
+ }
82
+
83
+ if let Some(v) = Qtrue::from_value(value) {
84
+ return Ok(v8::Boolean::new(scope, v.to_bool()).into());
85
+ }
86
+
87
+ if let Some(v) = Qfalse::from_value(value) {
88
+ return Ok(v8::Boolean::new(scope, v.to_bool()).into());
89
+ }
90
+
91
+ if let Some(v) = Symbol::from_value(value) {
92
+ return Ok(v8::String::new(scope, v.to_string().as_str())
93
+ .unwrap()
94
+ .into());
95
+ }
96
+
97
+ if let Some(v) = Integer::from_value(value) {
98
+ return Ok(v8::Integer::new(scope, v.to_i32().unwrap()).into());
99
+ }
100
+
101
+ if let Some(v) = RFloat::from_value(value) {
102
+ return Ok(v8::Number::new(scope, v.to_f64()).into());
103
+ }
104
+
105
+ if let Some(v) = RString::from_value(value) {
106
+ return Ok(v8::String::new(scope, v.to_string().unwrap().as_str())
107
+ .unwrap()
108
+ .into());
109
+ }
110
+
111
+ if let Some(v) = RArray::from_value(value) {
112
+ let arr;
113
+ {
114
+ arr = Array::new(scope, v.len() as i32);
115
+ }
116
+
117
+ for (i, val) in v.each().enumerate() {
118
+ let v8_value;
119
+ {
120
+ v8_value = convert_ruby_to_v8(val.unwrap(), scope).unwrap();
121
+ }
122
+ arr.set_index(scope, i as u32, v8_value);
123
+ }
124
+ return Ok(arr.into());
125
+ }
126
+
127
+ if let Some(v) = RHash::from_value(value) {
128
+ let obj = Object::new(scope);
129
+ v.foreach(|key: Value, val: Value| {
130
+ let key = convert_ruby_to_v8(key, scope).unwrap();
131
+ let val = convert_ruby_to_v8(val, scope).unwrap();
132
+ obj.set(scope, key, val);
133
+
134
+ Ok(ForEach::Continue)
135
+ })
136
+ .expect("cannot convert hash into JavaScript object");
137
+
138
+ return Ok(obj.into());
139
+ }
140
+
141
+ if let Some(v) = RStruct::from_value(value) {
142
+ let obj = Object::new(scope);
143
+ for member in v.members().unwrap() {
144
+ let key = member.to_string();
145
+ let val = v.getmember::<&str, Value>(key.as_str()).unwrap();
146
+ let v8_key = v8::String::new(scope, key.as_str()).unwrap();
147
+ let v8_val = convert_ruby_to_v8(val, scope).unwrap();
148
+
149
+ obj.set(scope, v8_key.into(), v8_val);
150
+ }
151
+
152
+ return Ok(obj.into());
153
+ }
154
+
155
+ Ok(v8::null(scope).into())
156
+ }
@@ -0,0 +1,3 @@
1
+ pub(crate) mod module;
2
+ pub(crate) mod module_item;
3
+ pub(crate) mod worker;
@@ -0,0 +1,51 @@
1
+ use crate::js::module_item::{Function, ModuleItem, Value as JsValue};
2
+ use crate::js::worker::WORKER;
3
+ use deno_core::error::AnyError;
4
+ use deno_core::{JsRealm, ModuleId};
5
+ use std::cell::RefCell;
6
+ use std::rc::Rc;
7
+ use v8::{Global, Local, Value};
8
+
9
+ pub(crate) struct Module {
10
+ pub(crate) id: ModuleId,
11
+ pub(crate) realm: Rc<RefCell<JsRealm>>,
12
+ }
13
+
14
+ impl Module {
15
+ pub(crate) fn import(
16
+ &self,
17
+ export_name: &str,
18
+ ) -> Result<ModuleItem, AnyError> {
19
+ WORKER.with(|worker| {
20
+ let namespace = {
21
+ let mut worker = worker.worker.borrow_mut();
22
+ worker.js_runtime.get_module_namespace(self.id).unwrap()
23
+ };
24
+
25
+ let realm = self.realm.borrow();
26
+
27
+ let mut worker = worker.worker.borrow_mut();
28
+ let mut scope = realm.handle_scope(worker.js_runtime.v8_isolate());
29
+
30
+ let namespace = Local::new(&mut scope, namespace);
31
+
32
+ let export_name = v8::String::new(&mut scope, export_name).unwrap();
33
+
34
+ let binding =
35
+ namespace.get(&mut scope, export_name.into()).unwrap();
36
+ let global_binding = Global::<Value>::new(&mut scope, binding);
37
+
38
+ if binding.is_function() {
39
+ Ok(ModuleItem::Function(Function {
40
+ binding: global_binding,
41
+ realm: self.realm.clone(),
42
+ }))
43
+ } else {
44
+ Ok(ModuleItem::Value(JsValue {
45
+ binding: global_binding,
46
+ realm: self.realm.clone(),
47
+ }))
48
+ }
49
+ })
50
+ }
51
+ }
@@ -0,0 +1,71 @@
1
+ use crate::js::worker::WORKER;
2
+ use deno_core::JsRealm;
3
+ use magnus::gvl::without_gvl;
4
+ use std::cell::RefCell;
5
+ use std::ops::Deref;
6
+ use std::rc::Rc;
7
+ use v8::Global;
8
+
9
+ pub(crate) enum ModuleItem {
10
+ Value(Value),
11
+ Function(Function),
12
+ }
13
+
14
+ pub(crate) struct Function {
15
+ pub(crate) binding: Global<v8::Value>,
16
+ pub(crate) realm: Rc<RefCell<JsRealm>>,
17
+ }
18
+
19
+ impl Function {
20
+ pub(crate) fn call(
21
+ &self,
22
+ args: &[Global<v8::Value>],
23
+ ) -> Result<magnus::Value, magnus::Error> {
24
+ WORKER.with(|worker| {
25
+ let realm = self.realm.borrow();
26
+ let realm = realm.deref();
27
+ worker
28
+ .runtime
29
+ // we block here instead of the worker, due to a refcell issue
30
+ // when borrowing within an await
31
+ .block_on(worker.call(realm, &self.binding, args))
32
+ })
33
+ }
34
+
35
+ pub(crate) fn call_without_gvl(
36
+ &self,
37
+ args: &[Global<v8::Value>],
38
+ ) -> Result<magnus::Value, magnus::Error> {
39
+ WORKER.with(|worker| {
40
+ let realm = self.realm.borrow();
41
+ let realm = realm.deref();
42
+ let result = without_gvl(
43
+ |gvl_context| {
44
+ worker.ruby_context.replace(Some(gvl_context));
45
+ let result = worker
46
+ .runtime
47
+ // we block here instead of the worker, due to a refcell issue
48
+ // when borrowing within an await
49
+ .block_on(worker.call(realm, &self.binding, args));
50
+ worker.ruby_context.replace(None);
51
+ result
52
+ },
53
+ None::<fn()>,
54
+ );
55
+ result.0.unwrap()
56
+ })
57
+ }
58
+ }
59
+
60
+ pub(crate) struct Value {
61
+ pub(crate) binding: Global<v8::Value>,
62
+ pub(crate) realm: Rc<RefCell<JsRealm>>,
63
+ }
64
+
65
+ impl Value {
66
+ pub(crate) fn to_ruby(&self) -> Option<magnus::Value> {
67
+ let realm = self.realm.borrow();
68
+ let realm = realm.deref();
69
+ WORKER.with(|worker| worker.to_ruby(realm, &self.binding))
70
+ }
71
+ }
@@ -0,0 +1,265 @@
1
+ use crate::isorun::utils::{convert_ruby_to_v8, convert_v8_to_ruby};
2
+ use deno_core::error::AnyError;
3
+ use deno_core::serde_v8::from_v8;
4
+ use deno_core::{op, serde_v8, Extension, FsModuleLoader, JsRealm, ModuleId};
5
+ use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
6
+ use deno_runtime::permissions::Permissions;
7
+ use deno_runtime::worker::{MainWorker, WorkerOptions};
8
+ use deno_runtime::BootstrapOptions;
9
+ use deno_web::BlobStore;
10
+ use magnus::block::Proc;
11
+ use magnus::gvl::GVLContext;
12
+ use magnus::{Error, Value};
13
+ use std::borrow::BorrowMut;
14
+ use std::cell::RefCell;
15
+ use std::collections::{HashMap, HashSet};
16
+ use std::path::Path;
17
+ use std::rc::Rc;
18
+ use std::string::ToString;
19
+ use std::sync::Arc;
20
+ use tokio::runtime::Runtime;
21
+ use v8::{Global, Local};
22
+
23
+ fn get_error_class_name(e: &AnyError) -> &'static str {
24
+ deno_runtime::errors::get_error_class_name(e).unwrap_or("Error")
25
+ }
26
+
27
+ const USER_AGENT: &str = "isorun";
28
+
29
+ pub(crate) struct Worker {
30
+ pub(crate) runtime: Runtime,
31
+ pub(crate) worker: RefCell<MainWorker>,
32
+ module_map: RefCell<HashMap<String, ModuleId>>,
33
+ pub(crate) ruby_context: RefCell<Option<GVLContext>>,
34
+ pub(crate) ruby_receiver: RefCell<Option<Proc>>,
35
+ }
36
+
37
+ impl Worker {
38
+ pub(crate) fn create_realm(&self) -> Result<JsRealm, AnyError> {
39
+ let mut worker = self.worker.borrow_mut();
40
+ worker.js_runtime.create_realm()
41
+ }
42
+
43
+ pub(crate) fn load_module(&self, path: &str) -> Result<ModuleId, AnyError> {
44
+ let mut module_map = self.module_map.borrow_mut();
45
+ if module_map.contains_key(path) {
46
+ return Ok(*module_map.get(path).unwrap());
47
+ }
48
+
49
+ let module_id = {
50
+ let mut worker = self.worker.borrow_mut();
51
+
52
+ let module_specifier =
53
+ deno_core::resolve_url_or_path(path).unwrap();
54
+ let module_id = self
55
+ .runtime
56
+ .block_on(worker.preload_side_module(&module_specifier))?;
57
+ self.runtime.block_on(worker.evaluate_module(module_id))?;
58
+
59
+ module_id
60
+ };
61
+
62
+ module_map.insert(path.to_string(), module_id);
63
+
64
+ Ok(module_id)
65
+ }
66
+
67
+ pub(crate) async fn call(
68
+ &self,
69
+ realm: &JsRealm,
70
+ callee: &Global<v8::Value>,
71
+ args: &[Global<v8::Value>],
72
+ ) -> Result<Value, Error> {
73
+ let promise = {
74
+ let mut worker = self.worker.borrow_mut();
75
+ let mut scope = realm.handle_scope(worker.js_runtime.v8_isolate());
76
+
77
+ let callee = Local::<v8::Value>::new(&mut scope, callee);
78
+ let callee = Local::<v8::Function>::try_from(callee).unwrap();
79
+
80
+ let mut local_args: Vec<Local<v8::Value>> = vec![];
81
+ for arg in args {
82
+ let local_arg = Local::<v8::Value>::new(&mut scope, arg);
83
+ local_args.push(local_arg);
84
+ }
85
+ let receiver = v8::undefined(scope.borrow_mut());
86
+ let promise = callee
87
+ .call(&mut scope, receiver.into(), local_args.as_slice())
88
+ .unwrap();
89
+ Global::<v8::Value>::new(&mut scope, promise)
90
+ };
91
+
92
+ let value = {
93
+ let mut worker = self.worker.borrow_mut();
94
+ worker.js_runtime.resolve_value(promise).await.unwrap()
95
+ };
96
+
97
+ let value = self.to_ruby(realm, &value).unwrap();
98
+
99
+ Ok(value)
100
+ }
101
+
102
+ pub(crate) fn to_ruby(
103
+ &self,
104
+ realm: &JsRealm,
105
+ value: &Global<v8::Value>,
106
+ ) -> Option<Value> {
107
+ let mut worker = self.worker.borrow_mut();
108
+ let mut scope = realm.handle_scope(worker.js_runtime.v8_isolate());
109
+ let value = Local::new(&mut scope, value);
110
+ let result = convert_v8_to_ruby(value, &mut scope);
111
+
112
+ match result {
113
+ Ok(v) => Some(v),
114
+ Err(_) => None,
115
+ }
116
+ }
117
+
118
+ pub(crate) fn to_v8(
119
+ &self,
120
+ realm: &JsRealm,
121
+ value: Value,
122
+ ) -> Option<Global<v8::Value>> {
123
+ let mut worker = self.worker.borrow_mut();
124
+ let mut scope = realm.handle_scope(worker.js_runtime.v8_isolate());
125
+ let value = convert_ruby_to_v8(value, &mut scope).unwrap();
126
+ let value = Global::<v8::Value>::new(&mut scope, value);
127
+
128
+ Some(value)
129
+ }
130
+
131
+ fn send(&self, value: Value) -> Result<Value, Error> {
132
+ // we need to deref the receiver as mut, as it is behind an Option
133
+ if let (Some(ctx), Some(rec)) = (
134
+ self.ruby_context.borrow_mut().as_mut(),
135
+ self.ruby_receiver.borrow_mut().as_mut(),
136
+ ) {
137
+ ctx.with_gvl(|| {
138
+ let args: (Value,) = (value,);
139
+ rec.call::<(Value,), Value>(args)
140
+ })?
141
+ } else {
142
+ Err(Error::runtime_error(
143
+ "Cannot send to ruby. Is the ruby receiver and context initialized and set?",
144
+ ))
145
+ }
146
+ }
147
+ }
148
+
149
+ impl Default for Worker {
150
+ fn default() -> Self {
151
+ let module_loader = Rc::new(FsModuleLoader);
152
+ let create_web_worker_cb = Arc::new(|_| {
153
+ todo!("Web workers are not supported in the example");
154
+ });
155
+ let web_worker_event_cb = Arc::new(|_| {
156
+ todo!("Web workers are not supported in the example");
157
+ });
158
+
159
+ let extension_send = Extension::builder()
160
+ .ops(vec![op_send_to_ruby::decl()])
161
+ .build();
162
+ let mut extensions = vec![extension_send];
163
+
164
+ let options = WorkerOptions {
165
+ bootstrap: BootstrapOptions {
166
+ args: vec![],
167
+ cpu_count: 1,
168
+ debug_flag: false,
169
+ enable_testing_features: false,
170
+ locale: v8::icu::get_language_tag(),
171
+ location: None,
172
+ no_color: false,
173
+ is_tty: false,
174
+ runtime_version: "x".to_string(),
175
+ ts_version: "x".to_string(),
176
+ unstable: false,
177
+ user_agent: USER_AGENT.to_string(),
178
+ inspect: false,
179
+ },
180
+ extensions: std::mem::take(&mut extensions),
181
+ startup_snapshot: None,
182
+ unsafely_ignore_certificate_errors: None,
183
+ root_cert_store: None,
184
+ seed: None,
185
+ source_map_getter: None,
186
+ format_js_error_fn: None,
187
+ web_worker_preload_module_cb: web_worker_event_cb.clone(),
188
+ web_worker_pre_execute_module_cb: web_worker_event_cb,
189
+ create_web_worker_cb,
190
+ maybe_inspector_server: None,
191
+ should_break_on_first_statement: false,
192
+ module_loader,
193
+ npm_resolver: None,
194
+ get_error_class_fn: Some(&get_error_class_name),
195
+ cache_storage_dir: None,
196
+ origin_storage_dir: None,
197
+ blob_store: BlobStore::default(),
198
+ broadcast_channel: InMemoryBroadcastChannel::default(),
199
+ shared_array_buffer_store: None,
200
+ compiled_wasm_module_store: None,
201
+ stdio: Default::default(),
202
+ };
203
+
204
+ // todo: we don't use the main module at all, but it could be used as an
205
+ // entry point for "eval" JavaScript.
206
+ let js_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/call.js");
207
+ let main_module =
208
+ deno_core::resolve_path(&js_path.to_string_lossy()).unwrap();
209
+ let permissions = Permissions::allow_all();
210
+ let mut worker = MainWorker::bootstrap_from_options(
211
+ main_module.clone(),
212
+ permissions,
213
+ options,
214
+ );
215
+
216
+ let runtime = tokio::runtime::Builder::new_current_thread()
217
+ .enable_all()
218
+ .build()
219
+ .unwrap();
220
+
221
+ runtime.block_on(async {
222
+ let module_id =
223
+ worker.preload_main_module(&main_module).await.unwrap();
224
+ worker
225
+ .evaluate_module(module_id)
226
+ .await
227
+ .expect("cannot evaluate core module");
228
+ });
229
+
230
+ Worker {
231
+ runtime,
232
+ module_map: RefCell::from(HashMap::default()),
233
+ worker: RefCell::from(worker),
234
+ ruby_context: RefCell::from(None),
235
+ ruby_receiver: RefCell::from(None),
236
+ }
237
+ }
238
+ }
239
+
240
+ thread_local! {
241
+ pub(crate) static WORKER: Worker = Worker::default();
242
+ pub(crate) static MODULE_MAP: HashSet<ModuleId> = HashSet::default();
243
+ }
244
+
245
+ #[allow(clippy::extra_unused_lifetimes)]
246
+ #[op(v8)]
247
+ fn op_send_to_ruby<'a>(
248
+ // do not remove the v8:: prefix, otherwise the macro complains
249
+ scope: &mut v8::HandleScope,
250
+ data: serde_v8::Value<'a>,
251
+ ) -> Result<serde_v8::Value<'a>, AnyError> {
252
+ let value = convert_v8_to_ruby(data.v8_value, scope)?;
253
+
254
+ WORKER.with(|worker| {
255
+ worker
256
+ .send(value)
257
+ .map(|v| {
258
+ let v = convert_ruby_to_v8(v, scope).unwrap();
259
+ from_v8(scope, v).unwrap()
260
+ })
261
+ .map_err(|error| {
262
+ AnyError::msg(format!("failed to send to ruby: {}", error))
263
+ })
264
+ })
265
+ }
@@ -0,0 +1,51 @@
1
+ use crate::isorun::context::Context;
2
+ use isorun::configure::set_receiver;
3
+ use isorun::function::Function;
4
+ use isorun::module::Module;
5
+ use magnus::{define_module, function, method, Error, Module as M, Object};
6
+
7
+ mod isorun;
8
+ mod js;
9
+
10
+ #[magnus::init]
11
+ fn init() -> Result<(), Error> {
12
+ let root = define_module("Isorun").expect("cannot define module: Isorun");
13
+
14
+ root.define_module_function("receiver=", function!(set_receiver, 1))
15
+ .expect("cannot define module function: receiver=");
16
+
17
+ let context = root
18
+ .define_class("Context", Default::default())
19
+ .expect("cannot define class: Isorun::Context");
20
+ context
21
+ .define_singleton_method("new", function!(Context::new, 0))
22
+ .expect("cannot define singelton method: new");
23
+ context
24
+ .define_method("load", method!(Context::load, 1))
25
+ .expect("cannot load module");
26
+
27
+ let module = root
28
+ .define_class("Module", Default::default())
29
+ .expect("cannot define class: Isorun::Module");
30
+ module
31
+ .define_private_method("id", method!(Module::id, 0))
32
+ .expect("cannot define method: module_id");
33
+ module
34
+ .define_method("import", method!(Module::import, 1))
35
+ .expect("cannot define method: import");
36
+
37
+ let function = root
38
+ .define_class("Function", Default::default())
39
+ .expect("cannot define class: Isorun::Function");
40
+ function
41
+ .define_method("call", method!(Function::call, -1))
42
+ .expect("cannot define method: call");
43
+ function
44
+ .define_method(
45
+ "call_without_gvl",
46
+ method!(Function::call_without_gvl, -1),
47
+ )
48
+ .expect("cannot define method: call_without_gvl");
49
+
50
+ Ok(())
51
+ }
Binary file
Binary file
Binary file
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Isorun
4
+ class Config
5
+ # Abstract base class for isorun and it's extensions configuration
6
+ # builder. Instantiates and validates gem configuration.
7
+ #
8
+ class AbstractBuilder
9
+ attr_reader :config
10
+
11
+ # @param [Class] config class
12
+ #
13
+ def initialize(config = Config.new, &block)
14
+ @config = config
15
+ instance_eval(&block)
16
+ end
17
+
18
+ # Builds and validates configuration.
19
+ #
20
+ # @return [Isorun::Config] config instance
21
+ #
22
+ def build
23
+ @config.validate! if @config.respond_to?(:validate!)
24
+ @config
25
+ end
26
+ end
27
+ end
28
+ end