rquickjs_core/context/
base.rs1use super::{
2 intrinsic,
3 owner::{ContextOwner, DropContext},
4 ContextBuilder, Intrinsic,
5};
6use crate::{qjs, Ctx, Error, Result, Runtime};
7use core::{mem, ptr::NonNull};
8
9impl DropContext for Runtime {
10 unsafe fn drop_context(&self, ctx: NonNull<qjs::JSContext>) {
11 let guard = match self.inner.try_lock() {
13 Some(x) => x,
14 None => {
15 #[cfg(not(feature = "parallel"))]
19 {
20 unsafe { qjs::JS_FreeContext(ctx.as_ptr()) }
21 return;
22 }
23 #[cfg(feature = "parallel")]
24 {
25 self.pending_free
26 .send(ctx)
27 .expect("runtime should be alive while contexts life");
28 return;
29 }
30 }
31 };
32 guard.update_stack_top();
33 unsafe { qjs::JS_FreeContext(ctx.as_ptr()) }
34 guard.drain_pending_free();
35 mem::drop(guard);
37 }
38}
39
40#[derive(Clone)]
44pub struct Context(pub(crate) ContextOwner<Runtime>);
45
46impl Context {
47 pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>, rt: Runtime) -> Self {
54 Context(ContextOwner::new(ctx, rt))
55 }
56
57 pub fn as_raw(&self) -> NonNull<qjs::JSContext> {
58 self.0.ctx()
59 }
60
61 pub fn base(runtime: &Runtime) -> Result<Self> {
65 Self::custom::<intrinsic::None>(runtime)
66 }
67
68 pub fn custom<I: Intrinsic>(runtime: &Runtime) -> Result<Self> {
72 let guard = runtime.inner.lock();
73 let ctx = NonNull::new(unsafe { qjs::JS_NewContextRaw(guard.rt.as_ptr()) })
74 .ok_or(Error::Allocation)?;
75 unsafe { qjs::JS_AddIntrinsicBaseObjects(ctx.as_ptr()) };
77 unsafe { I::add_intrinsic(ctx) };
78 let res = unsafe { ContextOwner::new(ctx, runtime.clone()) };
79 mem::drop(guard);
80
81 Ok(Context(res))
82 }
83
84 pub fn full(runtime: &Runtime) -> Result<Self> {
88 let guard = runtime.inner.lock();
89 let ctx = NonNull::new(unsafe { qjs::JS_NewContext(guard.rt.as_ptr()) })
90 .ok_or(Error::Allocation)?;
91 let res = unsafe { ContextOwner::new(ctx, runtime.clone()) };
92 mem::drop(guard);
94
95 Ok(Context(res))
96 }
97
98 pub fn builder() -> ContextBuilder<()> {
100 ContextBuilder::default()
101 }
102
103 pub fn runtime(&self) -> &Runtime {
105 self.0.rt()
106 }
107
108 #[allow(dead_code)]
109 pub fn get_runtime_ptr(&self) -> *mut qjs::JSRuntime {
110 unsafe { qjs::JS_GetRuntime(self.0.ctx().as_ptr()) }
111 }
112
113 pub fn with<F, R>(&self, f: F) -> R
122 where
123 F: FnOnce(Ctx) -> R,
124 {
125 let guard = self.0.rt().inner.lock();
126 guard.update_stack_top();
127 let ctx = unsafe { Ctx::new(self) };
128 f(ctx)
129 }
130}
131
132#[cfg(feature = "parallel")]
135unsafe impl Send for Context {}
136
137#[cfg(feature = "parallel")]
140unsafe impl Sync for Context {}
141
142#[cfg(test)]
143mod test {
144 use super::*;
145 use crate::*;
146
147 #[test]
148 fn basic() {
149 test_with(|ctx| {
150 let val: Value = ctx.eval(r#"1+1"#).unwrap();
151
152 assert_eq!(val.type_of(), Type::Int);
153 assert_eq!(i32::from_js(&ctx, val).unwrap(), 2);
154 println!("{:?}", ctx.globals());
155 });
156 }
157
158 #[test]
159 fn minimal() {
160 let rt = Runtime::new().unwrap();
161 let ctx = Context::builder()
162 .with::<intrinsic::Eval>()
163 .build(&rt)
164 .unwrap();
165 ctx.with(|ctx| {
166 let val: i32 = ctx.eval(r#"1+1"#).unwrap();
167
168 assert_eq!(val, 2);
169 println!("{:?}", ctx.globals());
170 });
171 }
172
173 #[test]
174 fn base() {
175 let rt = Runtime::new().unwrap();
176 let _ = Context::base(&rt).unwrap();
177 }
178
179 #[test]
180 fn module() {
181 test_with(|ctx| {
182 Module::evaluate(
183 ctx,
184 "test_mod",
185 r#"
186 let t = "3";
187 let b = (a) => a + 3;
188 export { b, t}
189 "#,
190 )
191 .unwrap()
192 .finish::<()>()
193 .unwrap();
194 });
195 }
196
197 #[test]
198 fn clone_ctx() {
199 let rt = Runtime::new().unwrap();
200 let ctx = Context::builder()
201 .with::<intrinsic::Eval>()
202 .build(&rt)
203 .unwrap();
204
205 let ctx_clone = ctx.clone();
206
207 ctx.with(|ctx| {
208 let val: i32 = ctx.eval(r#"1+1"#).unwrap();
209
210 assert_eq!(val, 2);
211 println!("{:?}", ctx.globals());
212 });
213
214 ctx_clone.with(|ctx| {
215 let val: i32 = ctx.eval(r#"1+1"#).unwrap();
216
217 assert_eq!(val, 2);
218 println!("{:?}", ctx.globals());
219 });
220 }
221
222 #[test]
223 #[cfg(feature = "parallel")]
224 fn parallel() {
225 use std::thread;
226
227 let rt = Runtime::new().unwrap();
228 let ctx = Context::full(&rt).unwrap();
229 ctx.with(|ctx| {
230 let _: () = ctx.eval("this.foo = 42").unwrap();
231 });
232 thread::spawn(move || {
233 ctx.with(|ctx| {
234 let i: i32 = ctx.eval("foo + 8").unwrap();
235 assert_eq!(i, 50);
236 });
237 })
238 .join()
239 .unwrap();
240 }
241
242 #[test]
243 #[cfg(feature = "parallel")]
244 fn parallel_drop() {
245 use std::{
246 sync::{Arc, Barrier},
247 thread,
248 };
249
250 let wait_for_entry = Arc::new(Barrier::new(2));
251
252 let rt = Runtime::new().unwrap();
253 let ctx_1 = Context::full(&rt).unwrap();
254 let ctx_2 = Context::full(&rt).unwrap();
255 let wait_for_entry_c = wait_for_entry.clone();
256 thread::spawn(move || {
257 wait_for_entry_c.wait();
258 std::mem::drop(ctx_1);
259 println!("done");
260 });
261
262 ctx_2.with(|ctx| {
263 wait_for_entry.wait();
264 let i: i32 = ctx.eval("2 + 8").unwrap();
265 assert_eq!(i, 10);
266 });
267 println!("done");
268 }
269
270 #[test]
271 #[should_panic(
272 expected = "Error: invalid first character of private name\n at eval_script:1:5\n"
273 )]
274 fn exception() {
275 test_with(|ctx| {
276 let val = ctx.eval::<(), _>("bla?#@!@ ").catch(&ctx);
277 if let Err(e) = val {
278 assert!(e.is_exception());
279 panic!("{}", e);
280 }
281 });
282 }
283}