Skip to content

Commit

Permalink
Add experimental inspector support
Browse files Browse the repository at this point in the history
  • Loading branch information
mtharrison authored and piscisaureus committed Oct 4, 2019
1 parent 9049213 commit df38691
Show file tree
Hide file tree
Showing 22 changed files with 1,002 additions and 54 deletions.
326 changes: 326 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ ansi_term = "0.12.1"
atty = "0.2.13"
clap = "2.33.0"
dirs = "2.0.2"
enclose = "1.1.8"
futures = "0.1.29"
http = "0.1.18"
hyper = "0.12.34"
Expand Down Expand Up @@ -57,6 +58,7 @@ tokio-rustls = "0.10.0"
tokio-threadpool = "0.1.15"
url = "1.7.2"
utime = "0.2.1"
warp = "0.1.20"
webpki = "0.21.0"
webpki-roots = "0.17.0"

Expand Down
18 changes: 18 additions & 0 deletions cli/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ pub struct DenoFlags {
pub v8_flags: Option<Vec<String>>,
// Use tokio::runtime::current_thread
pub current_thread: bool,
pub debug: bool,
pub debug_address: Option<String>,
}

static ENV_VARIABLES_HELP: &str = "ENVIRONMENT VARIABLES:
Expand Down Expand Up @@ -126,6 +128,15 @@ fn add_run_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
.long("no-fetch")
.help("Do not download remote modules"),
)
.arg(
Arg::with_name("debug")
.long("debug")
.takes_value(true)
.require_equals(true)
.min_values(0)
.value_name("ADDRESS")
.help("Enable debugger and pause on first statement"),
)
}

pub fn create_cli_app<'a, 'b>() -> App<'a, 'b> {
Expand Down Expand Up @@ -575,6 +586,13 @@ pub fn parse_flags(
flags = parse_run_args(flags.clone(), test_matches);
}

if matches.is_present("debug") {
flags.debug = true;
if matches.value_of("debug").is_some() {
flags.debug_address = Some(matches.value_of("debug").unwrap().to_owned());
}
}

flags
}

Expand Down
202 changes: 202 additions & 0 deletions cli/inspector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#![allow(clippy::mutex_atomic)]

use crate::version::DENO;
use futures::sync::oneshot::{spawn, SpawnHandle};
use futures::{Future, Sink, Stream};
use std::net::SocketAddrV4;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use warp::ws::{Message, WebSocket};
use warp::Filter;

pub struct Inspector {
enable: bool,
address: SocketAddrV4,
// sharable handle to channels passed to isolate
pub handle: deno::InspectorHandle,
// sending/receving messages from isolate
inbound_tx: Arc<Mutex<Sender<String>>>,
outbound_rx: Arc<Mutex<Receiver<String>>>,
// signals readiness of inspector
// TODO(mtharrison): Is a condvar more appropriate?
ready_tx: Arc<Mutex<Sender<()>>>,
ready_rx: Arc<Mutex<Receiver<()>>>,
server_spawn_handle: Option<SpawnHandle<(), ()>>,
// TODO(mtharrison): Maybe use an atomic bool instead?
started: Arc<Mutex<bool>>,
connected: Arc<Mutex<bool>>,
}

impl Inspector {
pub fn new(enable: bool, address: Option<String>) -> Self {
let (inbound_tx, inbound_rx) = channel::<String>();
let (outbound_tx, outbound_rx) = channel::<String>();
let (ready_tx, ready_rx) = channel::<()>();

let address = match address {
Some(address) => address.parse::<SocketAddrV4>().unwrap(),
None => "127.0.0.1:9888".parse::<SocketAddrV4>().unwrap(),
};

Inspector {
enable,
address,
handle: deno::InspectorHandle::new(outbound_tx, inbound_rx),
inbound_tx: Arc::new(Mutex::new(inbound_tx)),
outbound_rx: Arc::new(Mutex::new(outbound_rx)),
ready_rx: Arc::new(Mutex::new(ready_rx)),
ready_tx: Arc::new(Mutex::new(ready_tx)),
server_spawn_handle: None,
started: Arc::new(Mutex::new(false)),
connected: Arc::new(Mutex::new(false)),
}
}

pub fn serve(&self) -> impl Future<Item = (), Error = ()> {
let state = ClientState {
sender: self.inbound_tx.clone(),
receiver: self.outbound_rx.clone(),
ready_tx: self.ready_tx.clone(),
connected: self.connected.clone(),
};

let websocket = warp::path("websocket").and(warp::ws2()).map(
enclose!((state) move |ws: warp::ws::Ws2| {
ws.on_upgrade(enclose!((state) move |socket| {
let client = Client::new(state, socket);
client.on_connection()
}))
}),
);

// todo(matt): Make this unique per run (https://github.com/denoland/deno/pull/2696#discussion_r309282566)
let uuid = "97690037-256e-4e27-add0-915ca5421e2f";

let ip = format!("{}", self.address.ip());
let port = self.address.port();

let response_json = json!([{
"description": "deno",
"devtoolsFrontendUrl": format!("chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws={}:{}/websocket", ip, port),
"devtoolsFrontendUrlCompat": format!("chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws={}:{}/websocket", ip, port),
"faviconUrl": "https://www.deno-play.app/images/deno.svg",
"id": uuid,
"title": format!("deno[{}]", std::process::id()),
"type": "deno",
"url": "file://",
"webSocketDebuggerUrl": format!("ws://{}:{}/websocket", ip, port)
}]);

let response_version = json!({
"Browser": format!("Deno/{}", DENO),
"Protocol-Version": "1.1",
"webSocketDebuggerUrl": format!("ws://{}:{}/{}", ip, port, uuid)
});

let json =
warp::path("json").map(move || warp::reply::json(&response_json));

let version = path!("json" / "version")
.map(move || warp::reply::json(&response_version));

let routes = websocket.or(version).or(json);

warp::serve(routes).bind(self.address)
}

pub fn start(&mut self) {
let mut started = self.started.lock().unwrap();

if *started || !self.enable {
return;
}

*started = true;

self.server_spawn_handle = Some(spawn(
self.serve(),
&tokio_executor::DefaultExecutor::current(),
));

println!(
"Debugger listening on ws://{}:{}/97690037-256e-4e27-add0-915ca5421e2f",
self.address.ip(),
self.address.port(),
);

self
.ready_rx
.lock()
.unwrap()
.recv()
.expect("Error waiting for inspector server to start.");
// TODO(mtharrison): This is to allow v8 to ingest some inspector messages - find a more reliable way
std::thread::sleep(std::time::Duration::from_secs(1));
println!("Inspector frontend connected.");
}
}

impl Drop for Inspector {
fn drop(&mut self) {
if *self.connected.lock().unwrap() {
println!("Waiting for debugger to disconnect...");
}
}
}

#[derive(Clone)]
pub struct ClientState {
sender: Arc<Mutex<Sender<String>>>,
receiver: Arc<Mutex<Receiver<String>>>,
ready_tx: Arc<Mutex<Sender<()>>>,
connected: Arc<Mutex<bool>>,
}

pub struct Client {
state: ClientState,
socket: WebSocket,
}

impl Client {
fn new(state: ClientState, socket: WebSocket) -> Client {
Client { state, socket }
}

fn on_connection(self) -> impl Future<Item = (), Error = ()> {
let socket = self.socket;
let sender = self.state.sender;
let receiver = self.state.receiver;
let connected = self.state.connected;
let ready_tx = self.state.ready_tx;

let (mut user_ws_tx, user_ws_rx) = socket.split();

let fut_rx = user_ws_rx
.for_each(move |msg| {
let msg_str = msg.to_str().unwrap();
sender
.lock()
.unwrap()
.send(msg_str.to_owned())
.unwrap_or_else(|err| println!("Err: {}", err));
Ok(())
})
.map_err(|_| {});

// TODO(mtharrison): This is a mess. There must be a better way to do this - maybe use async channels or wrap them with a stream?

std::thread::spawn(move || {
let receiver = receiver.lock().unwrap();
loop {
let received = receiver.recv();
if let Ok(msg) = received {
let _ = ready_tx.lock().unwrap().send(());
*connected.lock().unwrap() = true;
user_ws_tx = user_ws_tx.send(Message::text(msg)).wait().unwrap();
}
}
});

fut_rx
}
}
5 changes: 5 additions & 0 deletions cli/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ extern crate lazy_static;
#[macro_use]
extern crate log;
#[macro_use]
extern crate enclose;
#[macro_use]
extern crate futures;
#[macro_use]
extern crate serde_json;
Expand All @@ -17,6 +19,8 @@ extern crate rand;
extern crate serde;
extern crate serde_derive;
extern crate url;
#[macro_use]
extern crate warp;

pub mod colors;
pub mod compilers;
Expand All @@ -32,6 +36,7 @@ mod global_timer;
mod http_body;
mod http_util;
mod import_map;
mod inspector;
pub mod msg;
pub mod ops;
pub mod permissions;
Expand Down
40 changes: 37 additions & 3 deletions cli/worker.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use crate::fmt_errors::JSError;
use crate::inspector::Inspector;
use crate::ops::json_op;
use crate::ops::minimal_op;
use crate::ops::*;
Expand All @@ -23,6 +24,7 @@ use url::Url;
pub struct Worker {
isolate: Arc<Mutex<deno::Isolate>>,
pub state: ThreadSafeState,
inspector: Arc<Mutex<Inspector>>,
}

impl Worker {
Expand All @@ -31,7 +33,13 @@ impl Worker {
startup_data: StartupData,
state: ThreadSafeState,
) -> Worker {
let isolate = Arc::new(Mutex::new(deno::Isolate::new(startup_data, false)));
let inspector =
Inspector::new(state.flags.debug, state.flags.debug_address.clone());
let isolate = Arc::new(Mutex::new(deno::Isolate::new(
startup_data,
false,
Some(inspector.handle.clone()),
)));
{
let mut i = isolate.lock().unwrap();
let state_ = state.clone();
Expand Down Expand Up @@ -299,9 +307,32 @@ impl Worker {
let state_ = state.clone();
i.set_js_error_create(move |v8_exception| {
JSError::from_v8_exception(v8_exception, &state_.ts_compiler)
})
});
i.setup_inspector();
}

// TODO(mtharrison): This is all wrong but I'm not sure how to structure it?
// perhaps this should all live inside Inspector who can call into the isolate somehow?
let isolate_clone = isolate.clone();
let rx = inspector.handle.rx.clone();

std::thread::spawn(move || loop {
{
let message = { rx.lock().unwrap().try_recv() };

if let Ok(msg) = message {
isolate_clone.lock().unwrap().inspector_message(msg);
}
}

std::thread::sleep(std::time::Duration::from_millis(5));
});

Self {
isolate,
state,
inspector: Arc::new(Mutex::new(inspector)),
}
Self { isolate, state }
}

/// Same as execute2() but the filename defaults to "$CWD/__anonymous__".
Expand All @@ -318,6 +349,9 @@ impl Worker {
js_filename: &str,
js_source: &str,
) -> Result<(), ErrBox> {
{
self.inspector.lock().unwrap().start();
}
let mut isolate = self.isolate.lock().unwrap();
isolate.execute(js_filename, js_source)
}
Expand Down
2 changes: 1 addition & 1 deletion core/examples/http_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ fn main() {
filename: "http_bench.js",
});

let mut isolate = deno::Isolate::new(startup_data, false);
let mut isolate = deno::Isolate::new(startup_data, false, None);
isolate.register_op("listen", http_op(op_listen));
isolate.register_op("accept", http_op(op_accept));
isolate.register_op("read", http_op(op_read));
Expand Down
Loading

0 comments on commit df38691

Please sign in to comment.