1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 15:24:46 -05:00

feat(core): send "executionContextDestroyed" notification on program end (#16831)

This commit changes "JsRuntime" to send "executionContextDestroyed"
notification when the program finishes and shows a prompt informing 
that runtime is waiting for inspector to disconnect.
This commit is contained in:
Bartek Iwańczuk 2022-11-27 00:44:39 +01:00 committed by GitHub
parent 6344b9e0a5
commit d4f659d1d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 17 deletions

View file

@ -303,7 +303,6 @@ mod inspector {
.unwrap(); .unwrap();
let msg = ws_read_msg(&mut socket).await; let msg = ws_read_msg(&mut socket).await;
println!("response msg 1 {}", msg);
assert_starts_with!(msg, r#"{"id":6,"result":{"debuggerId":"#); assert_starts_with!(msg, r#"{"id":6,"result":{"debuggerId":"#);
socket socket
@ -312,7 +311,6 @@ mod inspector {
.unwrap(); .unwrap();
let msg = ws_read_msg(&mut socket).await; let msg = ws_read_msg(&mut socket).await;
println!("response msg 2 {}", msg);
assert_eq!(msg, r#"{"id":31,"result":{}}"#); assert_eq!(msg, r#"{"id":31,"result":{}}"#);
child.kill().unwrap(); child.kill().unwrap();
@ -546,6 +544,10 @@ mod inspector {
.filter(|s| !s.starts_with("Deno ")); .filter(|s| !s.starts_with("Deno "));
assert_stderr_for_inspect(&mut stderr_lines); assert_stderr_for_inspect(&mut stderr_lines);
assert_eq!(
&stdout_lines.next().unwrap(),
"exit using ctrl+d, ctrl+c, or close()"
);
assert_inspector_messages( assert_inspector_messages(
&mut socket_tx, &mut socket_tx,
@ -564,11 +566,6 @@ mod inspector {
) )
.await; .await;
assert_eq!(
&stdout_lines.next().unwrap(),
"exit using ctrl+d, ctrl+c, or close()"
);
assert_inspector_messages( assert_inspector_messages(
&mut socket_tx, &mut socket_tx,
&[ &[
@ -577,7 +574,6 @@ mod inspector {
&mut socket_rx, &mut socket_rx,
&[r#"{"id":3,"result":{}}"#], &[] &[r#"{"id":3,"result":{}}"#], &[]
).await; ).await;
assert_inspector_messages( assert_inspector_messages(
&mut socket_tx, &mut socket_tx,
&[ &[
@ -587,7 +583,6 @@ mod inspector {
&[r#"{"id":4,"result":{"result":{"type":"string","value":""#], &[r#"{"id":4,"result":{"result":{"type":"string","value":""#],
&[], &[],
).await; ).await;
assert_inspector_messages( assert_inspector_messages(
&mut socket_tx, &mut socket_tx,
&[ &[
@ -597,9 +592,7 @@ mod inspector {
&[r#"{"id":5,"result":{"result":{"type":"undefined"}}}"#], &[r#"{"id":5,"result":{"result":{"type":"undefined"}}}"#],
&[r#"{"method":"Runtime.consoleAPICalled"#], &[r#"{"method":"Runtime.consoleAPICalled"#],
).await; ).await;
assert_eq!(&stderr_lines.next().unwrap(), "done"); assert_eq!(&stderr_lines.next().unwrap(), "done");
drop(stdin); drop(stdin);
child.wait().unwrap(); child.wait().unwrap();
} }
@ -905,6 +898,26 @@ mod inspector {
assert_eq!(&stdout_lines.next().unwrap(), "hello"); assert_eq!(&stdout_lines.next().unwrap(), "hello");
assert_eq!(&stdout_lines.next().unwrap(), "world"); assert_eq!(&stdout_lines.next().unwrap(), "world");
assert_inspector_messages(
&mut socket_tx,
&[],
&mut socket_rx,
&[],
&[
r#"{"method":"Debugger.resumed","params":{}}"#,
r#"{"method":"Runtime.consoleAPICalled","#,
r#"{"method":"Runtime.consoleAPICalled","#,
r#"{"method":"Runtime.executionContextDestroyed","params":{"executionContextId":1}}"#,
],
)
.await;
let line = &stdout_lines.next().unwrap();
assert_eq!(
line,
"Program finished. Waiting for inspector to disconnect to exit the process..."
);
child.kill().unwrap(); child.kill().unwrap();
child.wait().unwrap(); child.wait().unwrap();
} }

View file

@ -35,6 +35,7 @@ use std::ptr::NonNull;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use v8::HandleScope;
pub enum InspectorMsgKind { pub enum InspectorMsgKind {
Notification, Notification,
@ -213,10 +214,28 @@ impl JsRuntimeInspector {
self__ self__
} }
pub fn context_destroyed(
&mut self,
scope: &mut HandleScope,
context: v8::Global<v8::Context>,
) {
let context = v8::Local::new(scope, context);
self
.v8_inspector
.borrow_mut()
.as_mut()
.unwrap()
.context_destroyed(context);
}
pub fn has_active_sessions(&self) -> bool { pub fn has_active_sessions(&self) -> bool {
self.sessions.borrow().has_active_sessions() self.sessions.borrow().has_active_sessions()
} }
pub fn has_blocking_sessions(&self) -> bool {
self.sessions.borrow().has_blocking_sessions()
}
fn poll_sessions( fn poll_sessions(
&self, &self,
mut invoker_cx: Option<&mut Context>, mut invoker_cx: Option<&mut Context>,
@ -262,8 +281,11 @@ impl JsRuntimeInspector {
// Accept new connections. // Accept new connections.
let poll_result = sessions.session_rx.poll_next_unpin(cx); let poll_result = sessions.session_rx.poll_next_unpin(cx);
if let Poll::Ready(Some(session_proxy)) = poll_result { if let Poll::Ready(Some(session_proxy)) = poll_result {
let session = let session = InspectorSession::new(
InspectorSession::new(sessions.v8_inspector.clone(), session_proxy); sessions.v8_inspector.clone(),
session_proxy,
false,
);
let prev = sessions.handshake.replace(session); let prev = sessions.handshake.replace(session);
assert!(prev.is_none()); assert!(prev.is_none());
} }
@ -378,7 +400,7 @@ impl JsRuntimeInspector {
// InspectorSessions for a local session is added directly to the "established" // InspectorSessions for a local session is added directly to the "established"
// sessions, so it doesn't need to go through the session sender. // sessions, so it doesn't need to go through the session sender.
let inspector_session = let inspector_session =
InspectorSession::new(self.v8_inspector.clone(), proxy); InspectorSession::new(self.v8_inspector.clone(), proxy, true);
self self
.sessions .sessions
.borrow_mut() .borrow_mut()
@ -432,6 +454,10 @@ impl SessionContainer {
!self.established.is_empty() || self.handshake.is_some() !self.established.is_empty() || self.handshake.is_some()
} }
fn has_blocking_sessions(&self) -> bool {
self.established.iter().any(|s| s.blocking)
}
/// A temporary placeholder that should be used before actual /// A temporary placeholder that should be used before actual
/// instance of V8Inspector is created. It's used in favor /// instance of V8Inspector is created. It's used in favor
/// of `Default` implementation to signal that it's not meant /// of `Default` implementation to signal that it's not meant
@ -528,6 +554,9 @@ struct InspectorSession {
v8_channel: v8::inspector::ChannelBase, v8_channel: v8::inspector::ChannelBase,
v8_session: v8::UniqueRef<v8::inspector::V8InspectorSession>, v8_session: v8::UniqueRef<v8::inspector::V8InspectorSession>,
proxy: InspectorSessionProxy, proxy: InspectorSessionProxy,
// Describes if session should keep event loop alive, eg. a local REPL
// session should keep event loop alive, but a Websocket session shouldn't.
blocking: bool,
} }
impl InspectorSession { impl InspectorSession {
@ -536,6 +565,7 @@ impl InspectorSession {
pub fn new( pub fn new(
v8_inspector_rc: Rc<RefCell<v8::UniquePtr<v8::inspector::V8Inspector>>>, v8_inspector_rc: Rc<RefCell<v8::UniquePtr<v8::inspector::V8Inspector>>>,
session_proxy: InspectorSessionProxy, session_proxy: InspectorSessionProxy,
blocking: bool,
) -> Box<Self> { ) -> Box<Self> {
new_box_with(move |self_ptr| { new_box_with(move |self_ptr| {
let v8_channel = v8::inspector::ChannelBase::new::<Self>(); let v8_channel = v8::inspector::ChannelBase::new::<Self>();
@ -556,6 +586,7 @@ impl InspectorSession {
v8_channel, v8_channel,
v8_session, v8_session,
proxy: session_proxy, proxy: session_proxy,
blocking,
} }
}) })
} }

View file

@ -1099,9 +1099,21 @@ impl JsRuntime {
let pending_state = self.event_loop_pending_state(); let pending_state = self.event_loop_pending_state();
if !pending_state.is_pending() && !maybe_scheduling { if !pending_state.is_pending() && !maybe_scheduling {
if has_inspector { if has_inspector {
let inspector_has_active_sessions = let inspector = self.inspector();
self.inspector().borrow_mut().has_active_sessions(); let has_active_sessions = inspector.borrow().has_active_sessions();
if wait_for_inspector && inspector_has_active_sessions { let has_blocking_sessions = inspector.borrow().has_blocking_sessions();
if wait_for_inspector && has_active_sessions {
// If there are no blocking sessions (eg. REPL) we can now notify
// debugger that the program has finished running and we're ready
// to exit the process once debugger disconnects.
if !has_blocking_sessions {
let context = self.global_context();
let scope = &mut self.handle_scope();
inspector.borrow_mut().context_destroyed(scope, context);
println!("Program finished. Waiting for inspector to disconnect to exit the process...");
}
return Poll::Pending; return Poll::Pending;
} }
} }