agentos_v8_runtime/
isolate.rs1use std::collections::HashMap;
4use std::ffi::c_void;
5use std::sync::{Mutex, Once};
6
7use crate::ipc::ExecutionError;
8use agentos_bridge::queue_tracker::{warn_limit_exhausted, TrackedLimit};
9
10static V8_INIT: Once = Once::new();
11static V8_ISOLATE_LIFECYCLE: Mutex<()> = Mutex::new(());
12const MAX_UNHANDLED_PROMISE_REJECTIONS: usize = 1024;
13
14#[repr(align(16))]
15struct AlignedBytes<const N: usize>([u8; N]);
16
17static ICU_COMMON_DATA: AlignedBytes<
18 { include_bytes!(concat!(env!("OUT_DIR"), "/icudtl.dat")).len() },
19> = AlignedBytes(*include_bytes!(concat!(env!("OUT_DIR"), "/icudtl.dat")));
20
21#[derive(Default)]
22pub struct PromiseRejectState {
23 pub unhandled: HashMap<i32, ExecutionError>,
24 overflow_count: usize,
25}
26
27impl PromiseRejectState {
28 fn record_unhandled(&mut self, promise_id: i32, error: ExecutionError) {
29 use std::collections::hash_map::Entry;
30 let under_limit = self.unhandled.len() < MAX_UNHANDLED_PROMISE_REJECTIONS;
33 match self.unhandled.entry(promise_id) {
34 Entry::Occupied(mut entry) => {
36 entry.insert(error);
37 }
38 Entry::Vacant(entry) => {
40 if under_limit {
41 entry.insert(error);
42 } else {
43 self.overflow_count = self.overflow_count.saturating_add(1);
44 }
45 }
46 }
47 }
48
49 fn mark_handled(&mut self, promise_id: i32) {
50 if self.unhandled.remove(&promise_id).is_none() && self.overflow_count > 0 {
51 self.overflow_count -= 1;
52 }
53 }
54
55 pub fn take_next_unhandled(&mut self) -> Option<ExecutionError> {
56 if self.overflow_count > 0 {
57 self.overflow_count = 0;
58 self.unhandled.clear();
59 return Some(ExecutionError {
60 error_type: "Error".into(),
61 message: format!(
62 "unhandled promise rejection registry exceeded limit of {MAX_UNHANDLED_PROMISE_REJECTIONS} rejections"
63 ),
64 stack: String::new(),
65 code: Some("ERR_AGENTOS_UNHANDLED_REJECTION_LIMIT".into()),
66 });
67 }
68 self.unhandled.drain().next().map(|(_, err)| err)
69 }
70}
71
72extern "C" fn promise_reject_callback(msg: v8::PromiseRejectMessage) {
73 let scope = &mut unsafe { v8::CallbackScope::new(&msg) };
74 let promise_id = msg.get_promise().get_identity_hash().get();
75 match msg.get_event() {
76 v8::PromiseRejectEvent::PromiseRejectWithNoHandler => {
77 let error = {
78 let scope = &mut v8::HandleScope::new(scope);
79 let value = msg
80 .get_value()
81 .unwrap_or_else(|| v8::undefined(scope).into());
82 crate::execution::extract_error_info(scope, value)
83 };
84 if let Some(state) = scope.get_slot_mut::<PromiseRejectState>() {
85 state.record_unhandled(promise_id, error);
86 }
87 }
88 v8::PromiseRejectEvent::PromiseHandlerAddedAfterReject => {
89 if let Some(state) = scope.get_slot_mut::<PromiseRejectState>() {
90 state.mark_handled(promise_id);
91 }
92 }
93 _ => {}
94 }
95}
96
97pub fn configure_isolate(isolate: &mut v8::OwnedIsolate) {
98 isolate.set_slot(PromiseRejectState::default());
99 isolate.set_promise_reject_callback(promise_reject_callback);
100}
101
102pub fn init_v8_platform() {
105 V8_INIT.call_once(|| {
106 v8::icu::set_common_data_74(&ICU_COMMON_DATA.0)
107 .expect("failed to initialize V8 ICU common data");
108 let platform = v8::new_default_platform(0, false).make_shared();
109 v8::V8::initialize_platform(platform);
110 v8::V8::initialize();
111 });
112}
113
114const NEAR_HEAP_LIMIT_HEADROOM_BYTES: usize = 16 * 1024 * 1024;
120
121pub const DEFAULT_HEAP_LIMIT_MB: u32 = 128;
129
130extern "C" fn near_heap_limit_callback(
136 data: *mut c_void,
137 current_heap_limit: usize,
138 initial_heap_limit: usize,
139) -> usize {
140 if !data.is_null() {
141 let handle = unsafe { &*(data as *const v8::IsolateHandle) };
145 handle.terminate_execution();
148 }
149 warn_limit_exhausted(
150 TrackedLimit::V8HeapBytes,
151 current_heap_limit,
152 initial_heap_limit.max(1),
153 );
154 current_heap_limit
157 .max(initial_heap_limit)
158 .saturating_add(NEAR_HEAP_LIMIT_HEADROOM_BYTES)
159}
160
161pub fn install_heap_limit_guard(isolate: &mut v8::OwnedIsolate) {
169 let handle = Box::new(isolate.thread_safe_handle());
174 let data = Box::into_raw(handle) as *mut c_void;
175 isolate.add_near_heap_limit_callback(near_heap_limit_callback, data);
176}
177
178pub fn create_isolate(heap_limit_mb: Option<u32>) -> v8::OwnedIsolate {
183 let limit = heap_limit_mb.unwrap_or(DEFAULT_HEAP_LIMIT_MB);
184 let mut params = v8::CreateParams::default();
185 let limit_bytes = (limit as usize) * 1024 * 1024;
186 params = params.heap_limits(0, limit_bytes);
187 let mut isolate = with_isolate_lifecycle_lock(|| v8::Isolate::new(params));
188 configure_isolate(&mut isolate);
189 install_heap_limit_guard(&mut isolate);
190 isolate
191}
192
193pub fn with_isolate_lifecycle_lock<T>(f: impl FnOnce() -> T) -> T {
200 let _guard = V8_ISOLATE_LIFECYCLE
201 .lock()
202 .expect("V8 isolate lifecycle lock poisoned");
203 f()
204}
205
206pub fn drop_isolate(isolate: Option<v8::OwnedIsolate>) {
207 if let Some(isolate) = isolate {
208 with_isolate_lifecycle_lock(|| drop(isolate));
209 }
210}
211
212pub fn create_context(isolate: &mut v8::OwnedIsolate) -> v8::Global<v8::Context> {
215 let scope = &mut v8::HandleScope::new(isolate);
216 let context = v8::Context::new(scope, Default::default());
217 v8::Global::new(scope, context)
218}
219
220