From d4f659d1d3caa550dfc15ca9e62d4ad6a31db7ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 27 Nov 2022 00:44:39 +0100 Subject: [PATCH] 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. --- cli/tests/inspector_tests.rs | 35 +++++++++++++++++++++++----------- core/inspector.rs | 37 +++++++++++++++++++++++++++++++++--- core/runtime.rs | 18 +++++++++++++++--- 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/cli/tests/inspector_tests.rs b/cli/tests/inspector_tests.rs index febff7c28f..94ebbb2058 100644 --- a/cli/tests/inspector_tests.rs +++ b/cli/tests/inspector_tests.rs @@ -303,7 +303,6 @@ mod inspector { .unwrap(); let msg = ws_read_msg(&mut socket).await; - println!("response msg 1 {}", msg); assert_starts_with!(msg, r#"{"id":6,"result":{"debuggerId":"#); socket @@ -312,7 +311,6 @@ mod inspector { .unwrap(); let msg = ws_read_msg(&mut socket).await; - println!("response msg 2 {}", msg); assert_eq!(msg, r#"{"id":31,"result":{}}"#); child.kill().unwrap(); @@ -546,6 +544,10 @@ mod inspector { .filter(|s| !s.starts_with("Deno ")); assert_stderr_for_inspect(&mut stderr_lines); + assert_eq!( + &stdout_lines.next().unwrap(), + "exit using ctrl+d, ctrl+c, or close()" + ); assert_inspector_messages( &mut socket_tx, @@ -564,11 +566,6 @@ mod inspector { ) .await; - assert_eq!( - &stdout_lines.next().unwrap(), - "exit using ctrl+d, ctrl+c, or close()" - ); - assert_inspector_messages( &mut socket_tx, &[ @@ -577,7 +574,6 @@ mod inspector { &mut socket_rx, &[r#"{"id":3,"result":{}}"#], &[] ).await; - assert_inspector_messages( &mut socket_tx, &[ @@ -587,7 +583,6 @@ mod inspector { &[r#"{"id":4,"result":{"result":{"type":"string","value":""#], &[], ).await; - assert_inspector_messages( &mut socket_tx, &[ @@ -597,9 +592,7 @@ mod inspector { &[r#"{"id":5,"result":{"result":{"type":"undefined"}}}"#], &[r#"{"method":"Runtime.consoleAPICalled"#], ).await; - assert_eq!(&stderr_lines.next().unwrap(), "done"); - drop(stdin); child.wait().unwrap(); } @@ -905,6 +898,26 @@ mod inspector { assert_eq!(&stdout_lines.next().unwrap(), "hello"); 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.wait().unwrap(); } diff --git a/core/inspector.rs b/core/inspector.rs index e3db65afe5..8a91360919 100644 --- a/core/inspector.rs +++ b/core/inspector.rs @@ -35,6 +35,7 @@ use std::ptr::NonNull; use std::rc::Rc; use std::sync::Arc; use std::thread; +use v8::HandleScope; pub enum InspectorMsgKind { Notification, @@ -213,10 +214,28 @@ impl JsRuntimeInspector { self__ } + pub fn context_destroyed( + &mut self, + scope: &mut HandleScope, + context: v8::Global, + ) { + 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 { self.sessions.borrow().has_active_sessions() } + pub fn has_blocking_sessions(&self) -> bool { + self.sessions.borrow().has_blocking_sessions() + } + fn poll_sessions( &self, mut invoker_cx: Option<&mut Context>, @@ -262,8 +281,11 @@ impl JsRuntimeInspector { // Accept new connections. let poll_result = sessions.session_rx.poll_next_unpin(cx); if let Poll::Ready(Some(session_proxy)) = poll_result { - let session = - InspectorSession::new(sessions.v8_inspector.clone(), session_proxy); + let session = InspectorSession::new( + sessions.v8_inspector.clone(), + session_proxy, + false, + ); let prev = sessions.handshake.replace(session); assert!(prev.is_none()); } @@ -378,7 +400,7 @@ impl JsRuntimeInspector { // InspectorSessions for a local session is added directly to the "established" // sessions, so it doesn't need to go through the session sender. let inspector_session = - InspectorSession::new(self.v8_inspector.clone(), proxy); + InspectorSession::new(self.v8_inspector.clone(), proxy, true); self .sessions .borrow_mut() @@ -432,6 +454,10 @@ impl SessionContainer { !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 /// instance of V8Inspector is created. It's used in favor /// of `Default` implementation to signal that it's not meant @@ -528,6 +554,9 @@ struct InspectorSession { v8_channel: v8::inspector::ChannelBase, v8_session: v8::UniqueRef, 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 { @@ -536,6 +565,7 @@ impl InspectorSession { pub fn new( v8_inspector_rc: Rc>>, session_proxy: InspectorSessionProxy, + blocking: bool, ) -> Box { new_box_with(move |self_ptr| { let v8_channel = v8::inspector::ChannelBase::new::(); @@ -556,6 +586,7 @@ impl InspectorSession { v8_channel, v8_session, proxy: session_proxy, + blocking, } }) } diff --git a/core/runtime.rs b/core/runtime.rs index 8cb38de2f9..9ee3aad70c 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -1099,9 +1099,21 @@ impl JsRuntime { let pending_state = self.event_loop_pending_state(); if !pending_state.is_pending() && !maybe_scheduling { if has_inspector { - let inspector_has_active_sessions = - self.inspector().borrow_mut().has_active_sessions(); - if wait_for_inspector && inspector_has_active_sessions { + let inspector = self.inspector(); + let has_active_sessions = inspector.borrow().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; } }