Skip to content

Commit b247994

Browse files
committed
no more time threads
1 parent 6703bbc commit b247994

File tree

2 files changed

+51
-42
lines changed

2 files changed

+51
-42
lines changed

libs/core/runtime/jsruntime.rs

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -436,8 +436,10 @@ pub struct JsRuntimeState {
436436
pub(crate) function_templates: Rc<RefCell<FunctionTemplateData>>,
437437
pub(crate) callsite_prototype: RefCell<Option<v8::Global<v8::Object>>>,
438438
waker: Arc<AtomicWaker>,
439-
/// Guards the 100ms safety-net timer so at most one is active at a time.
440-
safety_net_active: Arc<std::sync::atomic::AtomicBool>,
439+
/// Level-triggered flag set by the custom V8 platform when a foreground
440+
/// task is posted. Checked and cleared each poll to ensure pumping even
441+
/// if the edge-triggered AtomicWaker notification was lost.
442+
has_foreground_task: Arc<std::sync::atomic::AtomicBool>,
441443
/// Accessed through [`JsRuntimeState::with_inspector`].
442444
inspector: RefCell<Option<Rc<JsRuntimeInspector>>>,
443445
has_inspector: Cell<bool>,
@@ -743,6 +745,8 @@ impl JsRuntime {
743745
// ...now let's set up ` JsRuntimeState`, we'll need to set some fields
744746
// later, after `JsRuntime` is all set up...
745747
let waker = op_state.waker.clone();
748+
let has_foreground_task =
749+
Arc::new(std::sync::atomic::AtomicBool::new(false));
746750
let op_state = Rc::new(RefCell::new(op_state));
747751
let (eval_context_get_code_cache_cb, eval_context_set_code_cache_cb) =
748752
options
@@ -765,7 +769,7 @@ impl JsRuntime {
765769
eval_context_set_code_cache_cb,
766770
),
767771
waker: waker.clone(),
768-
safety_net_active: Arc::new(std::sync::atomic::AtomicBool::new(false)),
772+
has_foreground_task: has_foreground_task.clone(),
769773
// Some fields are initialized later after isolate is created
770774
inspector: None.into(),
771775
has_inspector: false.into(),
@@ -847,6 +851,7 @@ impl JsRuntime {
847851
setup::register_isolate_waker(
848852
setup::isolate_ptr_to_key(isolate_ptr),
849853
waker.clone(),
854+
has_foreground_task,
850855
);
851856

852857
// ...isolate is fully set up, we can forward its pointer to the ops to finish
@@ -2108,7 +2113,14 @@ impl JsRuntime {
21082113
if has_inspector {
21092114
self.inspector().poll_sessions_from_event_loop(cx);
21102115
}
2111-
if poll_options.pump_v8_message_loop {
2116+
// Clear the foreground-task flag (set by the custom V8 platform callback).
2117+
// If it was set, we must pump the message loop regardless of other state.
2118+
let had_foreground_task = self
2119+
.inner
2120+
.state
2121+
.has_foreground_task
2122+
.swap(false, std::sync::atomic::Ordering::Relaxed);
2123+
if poll_options.pump_v8_message_loop || had_foreground_task {
21122124
self.pump_v8_message_loop(scope)?;
21132125
}
21142126

@@ -2265,27 +2277,6 @@ impl JsRuntime {
22652277
scope.perform_microtask_checkpoint();
22662278
}
22672279

2268-
// Safety net: if V8 has pending background tasks (e.g. module compilation),
2269-
// schedule a delayed wake to pump the message loop in case the platform
2270-
// callback was missed due to a race condition. Uses an OS thread (not
2271-
// tokio) to avoid depending on the async runtime being cooperative.
2272-
// The AtomicBool guard ensures at most one safety-net timer is active.
2273-
if pending_state.has_pending_background_tasks
2274-
&& !self
2275-
.inner
2276-
.state
2277-
.safety_net_active
2278-
.swap(true, std::sync::atomic::Ordering::Relaxed)
2279-
{
2280-
let waker = cx.waker().clone();
2281-
let flag = self.inner.state.safety_net_active.clone();
2282-
std::thread::spawn(move || {
2283-
std::thread::sleep(std::time::Duration::from_millis(100));
2284-
flag.store(false, std::sync::atomic::Ordering::Relaxed);
2285-
waker.wake_by_ref();
2286-
});
2287-
}
2288-
22892280
// Re-wake logic for next iteration
22902281
#[allow(clippy::suspicious_else_formatting, clippy::if_same_then_else)]
22912282
{

libs/core/runtime/setup.rs

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,51 +32,69 @@ pub(crate) fn isolate_ptr_to_key(ptr: v8::UnsafeRawIsolatePtr) -> usize {
3232
unsafe { std::mem::transmute::<v8::UnsafeRawIsolatePtr, usize>(ptr) }
3333
}
3434

35-
/// Global registry mapping isolate pointers to their event loop wakers.
36-
/// When V8 posts a foreground task for an isolate, the callback looks up
37-
/// the waker here and wakes the event loop. Isolates that received a
38-
/// notification before their waker was registered are tracked in
39-
/// `pending_wakes` so `register_isolate_waker` can wake them immediately.
35+
/// Per-isolate waker state: an `AtomicWaker` for edge-triggered wakeups
36+
/// and an `AtomicBool` flag for level-triggered detection.
37+
///
38+
/// The `AtomicWaker` can lose wakes if `wake()` is called while the stored
39+
/// waker was already consumed (e.g. by the re-wake logic in the poll loop).
40+
/// The `AtomicBool` flag ensures the poll loop always sees that a foreground
41+
/// task was posted, even if the waker-based notification was lost.
42+
struct IsolateWakerEntry {
43+
waker: std::sync::Arc<AtomicWaker>,
44+
has_foreground_task: std::sync::Arc<AtomicBool>,
45+
}
46+
47+
/// Global registry mapping isolate pointers to their waker state.
4048
struct IsolateWakerRegistry {
41-
wakers: HashMap<usize, std::sync::Arc<AtomicWaker>>,
49+
entries: HashMap<usize, IsolateWakerEntry>,
4250
pending_wakes: std::collections::HashSet<usize>,
4351
}
4452

4553
static ISOLATE_WAKERS: std::sync::LazyLock<Mutex<IsolateWakerRegistry>> =
4654
std::sync::LazyLock::new(|| {
4755
Mutex::new(IsolateWakerRegistry {
48-
wakers: HashMap::new(),
56+
entries: HashMap::new(),
4957
pending_wakes: std::collections::HashSet::new(),
5058
})
5159
});
5260

53-
/// Register a waker for an isolate so foreground task notifications
54-
/// wake the correct event loop. If a notification arrived before
55-
/// registration, the waker is triggered immediately.
61+
/// Register a waker and foreground-task flag for an isolate.
62+
/// If a notification arrived before registration, the flag is set
63+
/// and the waker is triggered immediately.
5664
pub fn register_isolate_waker(
5765
isolate_ptr: usize,
5866
waker: std::sync::Arc<AtomicWaker>,
67+
has_foreground_task: std::sync::Arc<AtomicBool>,
5968
) {
6069
let mut reg = ISOLATE_WAKERS.lock().unwrap();
6170
if reg.pending_wakes.remove(&isolate_ptr) {
71+
has_foreground_task.store(true, Ordering::Relaxed);
6272
waker.wake();
6373
}
64-
reg.wakers.insert(isolate_ptr, waker);
74+
reg.entries.insert(
75+
isolate_ptr,
76+
IsolateWakerEntry {
77+
waker,
78+
has_foreground_task,
79+
},
80+
);
6581
}
6682

67-
/// Unregister an isolate's waker (called on isolate drop).
83+
/// Unregister an isolate's waker state (called on isolate drop).
6884
pub fn unregister_isolate_waker(isolate_ptr: usize) {
6985
let mut reg = ISOLATE_WAKERS.lock().unwrap();
70-
reg.wakers.remove(&isolate_ptr);
86+
reg.entries.remove(&isolate_ptr);
7187
reg.pending_wakes.remove(&isolate_ptr);
7288
}
7389

74-
/// Wake the event loop for a given isolate. If the isolate's waker
75-
/// is not yet registered, marks it as pending so registration wakes it.
90+
/// Notify that a foreground task was posted for the given isolate.
91+
/// Sets the level-triggered flag and wakes the event loop.
92+
/// If the isolate's state is not yet registered, marks it as pending.
7693
fn wake_isolate(key: usize) {
7794
let mut reg = ISOLATE_WAKERS.lock().unwrap();
78-
if let Some(waker) = reg.wakers.get(&key) {
79-
waker.wake();
95+
if let Some(entry) = reg.entries.get(&key) {
96+
entry.has_foreground_task.store(true, Ordering::Relaxed);
97+
entry.waker.wake();
8098
} else {
8199
reg.pending_wakes.insert(key);
82100
}

0 commit comments

Comments
 (0)