From 22166f88e8f70629a17f76eaa39e16fa0eaf85f1 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 24 Mar 2020 17:21:47 -0400 Subject: [PATCH 01/41] WIP --- Cargo.lock | 379 ++++++++++++++++++++++++++++++++++++++++---- cli/Cargo.toml | 1 + cli/flags.rs | 10 ++ cli/global_state.rs | 9 ++ cli/inspector.rs | 218 +++++++++++++++++++++++++ cli/lib.rs | 13 ++ cli/worker.rs | 1 + core/lib.rs | 2 +- 8 files changed, 605 insertions(+), 28 deletions(-) create mode 100644 cli/inspector.rs diff --git a/Cargo.lock b/Cargo.lock index c42b86cf6e307a..9c6c0f216973ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,6 +169,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +[[package]] +name = "base64" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3" + [[package]] name = "bitflags" version = "1.2.1" @@ -186,6 +192,27 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + [[package]] name = "brotli" version = "3.3.0" @@ -207,12 +234,28 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + [[package]] name = "bumpalo" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "byteorder" version = "1.3.4" @@ -435,7 +478,7 @@ dependencies = [ "indexmap", "lazy_static", "libc", - "log", + "log 0.4.8", "nix", "notify", "os_pipe", @@ -458,6 +501,7 @@ dependencies = [ "url 2.1.1", "utime", "walkdir", + "warp", "webpki", "webpki-roots 0.19.0", "winapi 0.3.8", @@ -472,7 +516,7 @@ dependencies = [ "futures 0.3.4", "lazy_static", "libc", - "log", + "log 0.4.8", "rusty_v8", "serde_json", "tokio", @@ -499,6 +543,15 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + [[package]] name = "dirs" version = "2.0.2" @@ -625,6 +678,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + [[package]] name = "filetime" version = "0.2.8" @@ -827,6 +886,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + [[package]] name = "getrandom" version = "0.1.14" @@ -857,7 +925,7 @@ dependencies = [ "futures-util", "http", "indexmap", - "log", + "log 0.4.8", "slab", "tokio", "tokio-util", @@ -873,6 +941,31 @@ dependencies = [ "autocfg 0.1.7", ] +[[package]] +name = "headers" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed18eb2459bf1a09ad2d6b1547840c3e5e62882fa09b9a6a20b1de8e3228848f" +dependencies = [ + "base64 0.12.0", + "bitflags", + "bytes 0.5.4", + "headers-core", + "http", + "mime 0.3.16", + "sha-1", + "time", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "hermit-abi" version = "0.1.8" @@ -924,7 +1017,7 @@ dependencies = [ "http-body", "httparse", "itoa", - "log", + "log 0.4.8", "net2", "pin-project", "time", @@ -943,7 +1036,7 @@ dependencies = [ "ct-logs", "futures-util", "hyper", - "log", + "log 0.4.8", "rustls", "rustls-native-certs", "tokio", @@ -1022,6 +1115,15 @@ dependencies = [ "libc", ] +[[package]] +name = "input_buffer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" +dependencies = [ + "bytes 0.5.4", +] + [[package]] name = "iovec" version = "0.1.4" @@ -1084,6 +1186,15 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.8", +] + [[package]] name = "log" version = "0.4.8" @@ -1111,20 +1222,41 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +[[package]] +name = "mime" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" +dependencies = [ + "log 0.3.9", +] + [[package]] name = "mime" version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "mime_guess" +version = "1.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216929a5ee4dd316b1702eedf5e74548c123d370f47841ceaac38ca154690ca3" +dependencies = [ + "mime 0.2.6", + "phf", + "phf_codegen", + "unicase 1.4.2", +] + [[package]] name = "mime_guess" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" dependencies = [ - "mime", - "unicase", + "mime 0.3.16", + "unicase 2.6.0", ] [[package]] @@ -1148,7 +1280,7 @@ dependencies = [ "iovec", "kernel32-sys", "libc", - "log", + "log 0.4.8", "miow 0.2.1", "net2", "slab", @@ -1162,7 +1294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" dependencies = [ "lazycell", - "log", + "log 0.4.8", "mio", "slab", ] @@ -1173,7 +1305,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" dependencies = [ - "log", + "log 0.4.8", "mio", "miow 0.3.3", "winapi 0.3.8", @@ -1212,6 +1344,24 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "multipart" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136eed74cadb9edd2651ffba732b19a450316b680e4f48d6c79e905799e19d01" +dependencies = [ + "buf_redux", + "httparse", + "log 0.4.8", + "mime 0.2.6", + "mime_guess 1.8.8", + "quick-error", + "rand 0.6.5", + "safemem", + "tempfile", + "twoway", +] + [[package]] name = "net2" version = "0.2.33" @@ -1310,6 +1460,12 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "openssl-probe" version = "0.1.2" @@ -1401,23 +1557,62 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "phf" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" +dependencies = [ + "phf_shared 0.7.24", +] + +[[package]] +name = "phf_codegen" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" +dependencies = [ + "phf_generator 0.7.24", + "phf_shared 0.7.24", +] + +[[package]] +name = "phf_generator" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" +dependencies = [ + "phf_shared 0.7.24", + "rand 0.6.5", +] + [[package]] name = "phf_generator" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" dependencies = [ - "phf_shared", + "phf_shared 0.8.0", "rand 0.7.3", ] +[[package]] +name = "phf_shared" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" +dependencies = [ + "siphasher 0.2.3", + "unicase 1.4.2", +] + [[package]] name = "phf_shared" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" dependencies = [ - "siphasher", + "siphasher 0.3.1", ] [[package]] @@ -1520,6 +1715,12 @@ dependencies = [ "libc", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "0.6.13" @@ -1779,9 +1980,9 @@ dependencies = [ "hyper-rustls", "js-sys", "lazy_static", - "log", - "mime", - "mime_guess", + "log 0.4.8", + "mime 0.3.16", + "mime_guess 2.0.3", "percent-encoding 2.1.0", "pin-project-lite", "rustls", @@ -1847,7 +2048,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1" dependencies = [ "base64 0.11.0", - "log", + "log 0.4.8", "ring", "sct", "webpki", @@ -1887,7 +2088,7 @@ dependencies = [ "cfg-if", "dirs", "libc", - "log", + "log 0.4.8", "memchr", "nix", "unicode-segmentation", @@ -1902,6 +2103,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + [[package]] name = "same-file" version = "1.0.6" @@ -2030,6 +2237,18 @@ dependencies = [ "url 2.1.1", ] +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + [[package]] name = "signal-hook-registry" version = "1.2.0" @@ -2040,6 +2259,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" + [[package]] name = "siphasher" version = "0.3.1" @@ -2115,7 +2340,7 @@ checksum = "2940c75beb4e3bf3a494cef919a747a2cb81e52571e212bfbd185074add7208a" dependencies = [ "lazy_static", "new_debug_unreachable", - "phf_shared", + "phf_shared 0.8.0", "precomputed-hash", "serde", ] @@ -2126,8 +2351,8 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.8.0", + "phf_shared 0.8.0", "proc-macro2 1.0.9", "quote 1.0.3", ] @@ -2181,7 +2406,7 @@ dependencies = [ "from_variant", "fxhash", "hashbrown", - "log", + "log 0.4.8", "parking_lot 0.7.1", "scoped-tls", "serde", @@ -2213,7 +2438,7 @@ checksum = "9519ab89ac37f65eb7d58f892f89aaa4dc64979b660a029025dbb9ee2a2b2903" dependencies = [ "either", "enum_kind", - "log", + "log 0.4.8", "num-bigint", "once_cell", "regex", @@ -2376,7 +2601,7 @@ checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" dependencies = [ "bytes 0.4.12", "futures 0.1.29", - "log", + "log 0.4.8", ] [[package]] @@ -2402,6 +2627,19 @@ dependencies = [ "webpki", ] +[[package]] +name = "tokio-tungstenite" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b8fe88007ebc363512449868d7da4389c9400072a3f666f212c7280082882a" +dependencies = [ + "futures 0.3.4", + "log 0.4.8", + "pin-project", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.2.0" @@ -2411,7 +2649,7 @@ dependencies = [ "bytes 0.5.4", "futures-core", "futures-sink", - "log", + "log 0.4.8", "pin-project-lite", "tokio", ] @@ -2428,13 +2666,56 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" +[[package]] +name = "tungstenite" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfea31758bf674f990918962e8e5f07071a3161bd7c4138ed23e416e1ac4264e" +dependencies = [ + "base64 0.11.0", + "byteorder", + "bytes 0.5.4", + "http", + "httparse", + "input_buffer", + "log 0.4.8", + "rand 0.7.3", + "sha-1", + "url 2.1.1", + "utf-8", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + +[[package]] +name = "typenum" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" + +[[package]] +name = "unicase" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" +dependencies = [ + "version_check 0.1.5", +] + [[package]] name = "unicase" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ - "version_check", + "version_check 0.9.1", ] [[package]] @@ -2507,6 +2788,18 @@ dependencies = [ "percent-encoding 2.1.0", ] +[[package]] +name = "urlencoding" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df3561629a8bb4c57e5a2e4c43348d9e29c7c29d9b1c4c1f47166deca8f37ed" + +[[package]] +name = "utf-8" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" + [[package]] name = "utf8parse" version = "0.1.1" @@ -2530,6 +2823,12 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + [[package]] name = "version_check" version = "0.9.1" @@ -2559,10 +2858,36 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log", + "log 0.4.8", "try-lock", ] +[[package]] +name = "warp" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54cd1e2b3eb3539284d88b76a9afcf5e20f2ef2fab74db5b21a1c30d7d945e82" +dependencies = [ + "bytes 0.5.4", + "futures 0.3.4", + "headers", + "http", + "hyper", + "log 0.4.8", + "mime 0.3.16", + "mime_guess 2.0.3", + "multipart", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-tungstenite", + "tower-service", + "urlencoding", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -2589,7 +2914,7 @@ checksum = "e0da9c9a19850d3af6df1cb9574970b566d617ecfaf36eb0b706b6f3ef9bd2f8" dependencies = [ "bumpalo", "lazy_static", - "log", + "log 0.4.8", "proc-macro2 1.0.9", "quote 1.0.3", "syn 1.0.16", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index c7a2fbf75d9dad..aebfd0b1a14c20 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -61,6 +61,7 @@ utime = "0.2.1" webpki = "0.21.2" webpki-roots = "0.19.0" walkdir = "2.3.1" +warp = "0.2.2" semver-parser = "0.9.0" [target.'cfg(windows)'.dependencies] diff --git a/cli/flags.rs b/cli/flags.rs index 475172f0a7469e..1963f6def2a5b0 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -101,6 +101,7 @@ pub struct Flags { pub no_prompts: bool, pub no_remote: bool, pub cached_only: bool, + pub debug: bool, pub seed: Option, pub v8_flags: Option>, @@ -479,6 +480,10 @@ fn run_test_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { flags.cached_only = true; } + if matches.is_present("debug") { + flags.debug = true; + } + if matches.is_present("seed") { let seed_string = matches.value_of("seed").unwrap(); let seed = seed_string.parse::().unwrap(); @@ -839,6 +844,11 @@ fn run_test_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { .long("cached-only") .help("Require that remote dependencies are already cached"), ) + .arg( + Arg::with_name("debug") + .long("debug") + .help("Enable debugger and pause on first statement"), + ) .arg( Arg::with_name("seed") .long("seed") diff --git a/cli/global_state.rs b/cli/global_state.rs index 45a31406cbeb03..525df22fd8b1aa 100644 --- a/cli/global_state.rs +++ b/cli/global_state.rs @@ -9,6 +9,7 @@ use crate::deno_dir; use crate::file_fetcher::SourceFileFetcher; use crate::flags; use crate::http_cache; +use crate::inspector::InspectorServer; use crate::lockfile::Lockfile; use crate::msg; use crate::permissions::DenoPermissions; @@ -42,6 +43,7 @@ pub struct GlobalStateInner { pub wasm_compiler: WasmCompiler, pub lockfile: Option>, pub compiler_starts: AtomicUsize, + pub inspector_server: Option>, compile_lock: AsyncMutex<()>, } @@ -82,7 +84,14 @@ impl GlobalState { None }; + let inspector_server = if flags.debug { + Some(Mutex::new(InspectorServer::new())) + } else { + None + }; + let inner = GlobalStateInner { + inspector_server, dir, permissions: DenoPermissions::from_flags(&flags), flags, diff --git a/cli/inspector.rs b/cli/inspector.rs new file mode 100644 index 00000000000000..02c05d0df709c0 --- /dev/null +++ b/cli/inspector.rs @@ -0,0 +1,218 @@ +#![allow(unused_variables)] +#![allow(dead_code)] + +use deno_core::v8; +use std::collections::HashMap; +use std::net::SocketAddrV4; +use warp; +use warp::Filter; + +const CONTEXT_GROUP_ID: i32 = 1; + +/// Owned by GlobalState +pub struct InspectorServer { + thread_handle: Option>, +} + +impl InspectorServer { + pub fn new() -> Self { + let thread_handle = std::thread::spawn(move || { + println!("debug"); + println!("debug before run_basic"); + crate::tokio_util::run_basic(server()); + println!("debug after run_basic"); + }); + Self { + // control_sender: sender, + thread_handle: Some(thread_handle), + } + } + + // TODO this should probably be done in impl Drop, but it seems we're leaking + // GlobalState and so it can't be done there... + pub fn exit(&mut self) { + self.thread_handle.take().unwrap().join().unwrap(); + } + + pub async fn register_worker() { + todo!() + } +} + +async fn server() -> () { + let websocket = + warp::path("websocket") + .and(warp::ws()) + .map(move |ws: warp::ws::Ws| { + ws.on_upgrade(move |_socket| async { + // here + + // send a message back so register_worker can return... + + todo!() + // 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 address = "127.0.0.1:9229".parse::().unwrap(); + let ip = format!("{}", address.ip()); + let port = 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/{}", crate::version::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 = warp::path!("json" / "version") + .map(move || warp::reply::json(&response_version)); + + let routes = websocket.or(version).or(json); + warp::serve(routes).bind(address).await; +} + +/// sub-class of v8::inspector::Channel +pub struct DenoInspectorSession { + base: v8::inspector::ChannelBase, + session: Option>, +} + +impl DenoInspectorSession { + pub fn new(inspector: &mut v8::inspector::V8Inspector) -> Self { + let mut x = Self { + base: v8::inspector::ChannelBase::new::(), + session: None, + }; + let empty_view = v8::inspector::StringView::empty(); + let session = inspector.connect(CONTEXT_GROUP_ID, &mut x, &empty_view); + + x.session = Some(session); + + x + } +} + +impl v8::inspector::ChannelImpl for DenoInspectorSession { + fn base(&self) -> &v8::inspector::ChannelBase { + &self.base + } + + fn base_mut(&mut self) -> &mut v8::inspector::ChannelBase { + &mut self.base + } + + fn send_response( + &mut self, + call_id: i32, + message: v8::UniquePtr, + ) { + // deno_isolate.inspector_message_cb(message) + todo!() + } + + fn send_notification( + &mut self, + message: v8::UniquePtr, + ) { + // deno_isolate.inspector_message_cb(message) + todo!() + } + + fn flush_protocol_notifications(&mut self) { + // pass + todo!() + } +} + +#[repr(C)] +pub struct DenoInspectorClient { + base: v8::inspector::V8InspectorClientBase, + + // Note this is the same as NodeInspectorClient::client_ + inspector: Option>, + + terminated: bool, + + // Note this is the same as NodeInspectorClient::channels_ + sessions: HashMap, + + next_session_id: usize, +} + +impl DenoInspectorClient { + pub fn new

(scope: &mut P, context: v8::Local) -> Self + where + P: v8::InIsolate, + { + let mut client = Self { + base: v8::inspector::V8InspectorClientBase::new::(), + inspector: None, + terminated: false, + sessions: HashMap::new(), + next_session_id: 1, + }; + + let mut inspector = v8::inspector::V8Inspector::create(scope, &mut client); + + let empty_view = v8::inspector::StringView::empty(); + let buffer = v8::inspector::StringBuffer::create(&empty_view).unwrap(); + + let state = b""; + let state_view = v8::inspector::StringView::from(&state[..]); + + inspector.context_created(context, CONTEXT_GROUP_ID, &state_view); + + client.inspector = Some(inspector); + client + } + + pub fn connect(&mut self) { + let id = self.next_session_id; + self.next_session_id += 1; + let session = DenoInspectorSession::new(self.inspector.as_mut().unwrap()); + self.sessions.insert(id, session); + } +} + +impl v8::inspector::V8InspectorClientImpl for DenoInspectorClient { + fn base(&self) -> &v8::inspector::V8InspectorClientBase { + &self.base + } + + fn base_mut(&mut self) -> &mut v8::inspector::V8InspectorClientBase { + &mut self.base + } + + fn run_message_loop_on_pause(&mut self, context_group_id: i32) { + // while !self.terminated { + // self.deno_isolate.inspector_block_recv(); + // } + todo!() + } + + fn quit_message_loop_on_pause(&mut self) { + todo!() + } + + fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) { + todo!() + } +} diff --git a/cli/lib.rs b/cli/lib.rs index ba5152bd6221eb..a71110a2cc1455 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -37,6 +37,7 @@ mod global_timer; pub mod http_cache; mod http_util; mod import_map; +mod inspector; pub mod installer; mod js; mod lockfile; @@ -372,6 +373,18 @@ async fn run_command(flags: Flags, script: String) -> Result<(), ErrBox> { std::process::exit(11); } } + + // TODO This should probably be done in impl Drop for InspectorServer, but it + // seems GlobalState is being leaked and the drop is never called. + { + let mut s = global_state + .inspector_server + .as_ref() + .unwrap() + .lock() + .unwrap(); + s.exit(); + } Ok(()) } diff --git a/cli/worker.rs b/cli/worker.rs index 6593ade0b10cb8..856b87828006fc 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -105,6 +105,7 @@ impl Worker { let mut isolate = deno_core::EsIsolate::new(loader, startup_data, false); let global_state_ = state.borrow().global_state.clone(); + isolate.set_js_error_create_fn(move |core_js_error| { JSError::create(core_js_error, &global_state_.ts_compiler) }); diff --git a/core/lib.rs b/core/lib.rs index c4fbb33f4eec6f..d1776fb69846cc 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -21,7 +21,7 @@ mod plugins; mod resources; mod shared_queue; -use rusty_v8 as v8; +pub use rusty_v8 as v8; pub use crate::any_error::*; pub use crate::es_isolate::*; From 672fce7bfef575ecfe9aa07be8bcfbe72f95a603 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 24 Mar 2020 17:59:20 -0400 Subject: [PATCH 02/41] WIP --- cli/worker.rs | 28 ++++++++++++++++++++++++++++ core/isolate.rs | 4 ++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/cli/worker.rs b/cli/worker.rs index 856b87828006fc..cd3395a10c41a0 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -1,4 +1,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +#![allow(dead_code)] + use crate::fmt_errors::JSError; use crate::ops; use crate::state::State; @@ -97,6 +100,7 @@ pub struct Worker { pub waker: AtomicWaker, pub(crate) internal_channels: WorkerChannelsInternal, external_channels: WorkerHandle, + inspector_client: Option, } impl Worker { @@ -106,6 +110,29 @@ impl Worker { let global_state_ = state.borrow().global_state.clone(); + /* + if let Some(inspector_server) = global_state_.inspector_server { + inspector_server.register_worker(worker); + } + */ + + let inspector_client = if global_state_.inspector_server.is_some() { + use deno_core::v8; + let deno_core::Isolate { + v8_isolate, + global_context, + .. + } = &mut **isolate; + let mut hs = v8::HandleScope::new(v8_isolate.as_mut().unwrap()); + let scope = hs.enter(); + let context = global_context.get(scope).unwrap(); + let inspector_client = + crate::inspector::DenoInspectorClient::new(scope, context); + Some(inspector_client) + } else { + None + }; + isolate.set_js_error_create_fn(move |core_js_error| { JSError::create(core_js_error, &global_state_.ts_compiler) }); @@ -119,6 +146,7 @@ impl Worker { waker: AtomicWaker::new(), internal_channels, external_channels, + inspector_client, } } diff --git a/core/isolate.rs b/core/isolate.rs index 3f4f897960fb3d..f876f2452245c7 100644 --- a/core/isolate.rs +++ b/core/isolate.rs @@ -159,11 +159,11 @@ type IsolateErrorHandleFn = dyn FnMut(ErrBox) -> Result<(), ErrBox>; /// as arguments. An async Op corresponds exactly to a Promise in JavaScript. #[allow(unused)] pub struct Isolate { - pub(crate) v8_isolate: Option, + pub v8_isolate: Option, snapshot_creator: Option, has_snapshotted: bool, snapshot: Option, - pub(crate) global_context: v8::Global, + pub global_context: v8::Global, pub(crate) shared_ab: v8::Global, pub(crate) js_recv_cb: v8::Global, pub(crate) js_macrotask_cb: v8::Global, From 6e181c0c2b950f06523814d1821fa4d62e477426 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Wed, 25 Mar 2020 04:12:14 +0100 Subject: [PATCH 03/41] Session and Channel should not move, so put them in a Box --- cli/inspector.rs | 96 +++++++++++++++++++++++++----------------------- cli/worker.rs | 11 +++--- 2 files changed, 56 insertions(+), 51 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 02c05d0df709c0..bff1857cfa95ea 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -3,7 +3,9 @@ use deno_core::v8; use std::collections::HashMap; +use std::mem::MaybeUninit; use std::net::SocketAddrV4; +use std::ptr; use warp; use warp::Filter; @@ -91,32 +93,35 @@ async fn server() -> () { /// sub-class of v8::inspector::Channel pub struct DenoInspectorSession { - base: v8::inspector::ChannelBase, - session: Option>, + channel: v8::inspector::ChannelBase, + session: v8::UniqueRef, } impl DenoInspectorSession { - pub fn new(inspector: &mut v8::inspector::V8Inspector) -> Self { - let mut x = Self { - base: v8::inspector::ChannelBase::new::(), - session: None, - }; - let empty_view = v8::inspector::StringView::empty(); - let session = inspector.connect(CONTEXT_GROUP_ID, &mut x, &empty_view); - - x.session = Some(session); - - x + pub fn new(inspector: &mut v8::inspector::V8Inspector) -> Box { + new_box_with(|address| { + let empty_view = v8::inspector::StringView::empty(); + Self { + channel: v8::inspector::ChannelBase::new::(), + session: inspector.connect( + CONTEXT_GROUP_ID, + // Todo(piscisaureus): V8Inspector::connect() should require that + // the 'channel' argument cannot move. + unsafe { &mut *address }, + &empty_view, + ), + } + }) } } impl v8::inspector::ChannelImpl for DenoInspectorSession { fn base(&self) -> &v8::inspector::ChannelBase { - &self.base + &self.channel } fn base_mut(&mut self) -> &mut v8::inspector::ChannelBase { - &mut self.base + &mut self.channel } fn send_response( @@ -143,62 +148,56 @@ impl v8::inspector::ChannelImpl for DenoInspectorSession { } #[repr(C)] -pub struct DenoInspectorClient { - base: v8::inspector::V8InspectorClientBase, - - // Note this is the same as NodeInspectorClient::client_ - inspector: Option>, - +pub struct DenoInspector { + client: v8::inspector::V8InspectorClientBase, + inspector: v8::UniqueRef, terminated: bool, - - // Note this is the same as NodeInspectorClient::channels_ - sessions: HashMap, - + sessions: HashMap>, next_session_id: usize, } -impl DenoInspectorClient { - pub fn new

(scope: &mut P, context: v8::Local) -> Self +impl DenoInspector { + pub fn new

(scope: &mut P, context: v8::Local) -> Box where P: v8::InIsolate, { - let mut client = Self { - base: v8::inspector::V8InspectorClientBase::new::(), - inspector: None, + let mut deno_inspector = new_box_with(|address| Self { + client: v8::inspector::V8InspectorClientBase::new::(), + // Todo(piscisaureus): V8Inspector::create() should require that + // the 'client' argument cannot move. + inspector: v8::inspector::V8Inspector::create(scope, unsafe { + &mut *address + }), terminated: false, sessions: HashMap::new(), next_session_id: 1, - }; - - let mut inspector = v8::inspector::V8Inspector::create(scope, &mut client); + }); let empty_view = v8::inspector::StringView::empty(); - let buffer = v8::inspector::StringBuffer::create(&empty_view).unwrap(); - - let state = b""; - let state_view = v8::inspector::StringView::from(&state[..]); + deno_inspector.inspector.context_created( + context, + CONTEXT_GROUP_ID, + &empty_view, + ); - inspector.context_created(context, CONTEXT_GROUP_ID, &state_view); - - client.inspector = Some(inspector); - client + deno_inspector } pub fn connect(&mut self) { let id = self.next_session_id; self.next_session_id += 1; - let session = DenoInspectorSession::new(self.inspector.as_mut().unwrap()); + let session = DenoInspectorSession::new(&mut self.inspector); self.sessions.insert(id, session); } } -impl v8::inspector::V8InspectorClientImpl for DenoInspectorClient { +impl v8::inspector::V8InspectorClientImpl for DenoInspector { fn base(&self) -> &v8::inspector::V8InspectorClientBase { - &self.base + &self.client } fn base_mut(&mut self) -> &mut v8::inspector::V8InspectorClientBase { - &mut self.base + &mut self.client } fn run_message_loop_on_pause(&mut self, context_group_id: i32) { @@ -216,3 +215,10 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspectorClient { todo!() } } + +fn new_box_with(new_fn: impl FnOnce(*mut T) -> T) -> Box { + let b = Box::new(MaybeUninit::::uninit()); + let p = Box::into_raw(b) as *mut T; + unsafe { ptr::write(p, new_fn(p)) }; + unsafe { Box::from_raw(p) } +} diff --git a/cli/worker.rs b/cli/worker.rs index cd3395a10c41a0..add8904d22b071 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -100,7 +100,7 @@ pub struct Worker { pub waker: AtomicWaker, pub(crate) internal_channels: WorkerChannelsInternal, external_channels: WorkerHandle, - inspector_client: Option, + inspector: Option>, } impl Worker { @@ -116,7 +116,7 @@ impl Worker { } */ - let inspector_client = if global_state_.inspector_server.is_some() { + let inspector = if global_state_.inspector_server.is_some() { use deno_core::v8; let deno_core::Isolate { v8_isolate, @@ -126,9 +126,8 @@ impl Worker { let mut hs = v8::HandleScope::new(v8_isolate.as_mut().unwrap()); let scope = hs.enter(); let context = global_context.get(scope).unwrap(); - let inspector_client = - crate::inspector::DenoInspectorClient::new(scope, context); - Some(inspector_client) + let inspector = crate::inspector::DenoInspector::new(scope, context); + Some(inspector) } else { None }; @@ -146,7 +145,7 @@ impl Worker { waker: AtomicWaker::new(), internal_channels, external_channels, - inspector_client, + inspector, } } From da1ca22dfc45f513b7e21cf4deba64b075b6d160 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 25 Mar 2020 02:00:57 -0400 Subject: [PATCH 04/41] Support real UUIDs, handle multiple inspectors --- Cargo.lock | 10 ++++ cli/Cargo.toml | 2 + cli/inspector.rs | 136 +++++++++++++++++++++++++++++------------------ cli/worker.rs | 41 +++++++------- 4 files changed, 116 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c6c0f216973ae..b801e113483d0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -500,6 +500,7 @@ dependencies = [ "tokio-rustls", "url 2.1.1", "utime", + "uuid", "walkdir", "warp", "webpki", @@ -2817,6 +2818,15 @@ dependencies = [ "winapi 0.2.8", ] +[[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +dependencies = [ + "rand 0.7.3", +] + [[package]] name = "vec_map" version = "0.8.1" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index aebfd0b1a14c20..8c9fe95edc78d5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -63,6 +63,8 @@ webpki-roots = "0.19.0" walkdir = "2.3.1" warp = "0.2.2" semver-parser = "0.9.0" +uuid = { version = "0.8", features = ["v4"] } + [target.'cfg(windows)'.dependencies] winapi = "0.3.8" diff --git a/cli/inspector.rs b/cli/inspector.rs index bff1857cfa95ea..d11f91108ad63e 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -6,88 +6,120 @@ use std::collections::HashMap; use std::mem::MaybeUninit; use std::net::SocketAddrV4; use std::ptr; +use std::sync::Arc; +use std::sync::Mutex; +use uuid::Uuid; use warp; use warp::Filter; const CONTEXT_GROUP_ID: i32 = 1; +// TODO Currently the value in this map is just (), but we will fill that in +// later with more stuff. Probably channels. +type InspectorMap = Arc>>; + /// Owned by GlobalState pub struct InspectorServer { + address: SocketAddrV4, thread_handle: Option>, + inspector_map: InspectorMap, } impl InspectorServer { pub fn new() -> Self { - let thread_handle = std::thread::spawn(move || { - println!("debug"); - println!("debug before run_basic"); - crate::tokio_util::run_basic(server()); - println!("debug after run_basic"); - }); Self { - // control_sender: sender, - thread_handle: Some(thread_handle), + address: "127.0.0.1:9229".parse::().unwrap(), + thread_handle: None, + inspector_map: Arc::new(Mutex::new(HashMap::new())), } } // TODO this should probably be done in impl Drop, but it seems we're leaking // GlobalState and so it can't be done there... pub fn exit(&mut self) { - self.thread_handle.take().unwrap().join().unwrap(); + if let Some(thread_handle) = self.thread_handle.take() { + thread_handle.join().unwrap(); + } } - pub async fn register_worker() { - todo!() + /// Each worker/isolate to be debugged should call this exactly one. + pub fn register(&mut self) -> Uuid { + let uuid = Uuid::new_v4(); + + let inspector_map_ = self.inspector_map.clone(); + let mut inspector_map = self.inspector_map.lock().unwrap(); + inspector_map.insert(uuid.clone(), ()); + + // Don't start the InspectorServer thread until we have one UUID registered. + if inspector_map.len() == 1 { + let address = self.address; + self.thread_handle = Some(std::thread::spawn(move || { + println!("Open chrome://inspect/"); + crate::tokio_util::run_basic(server(address, inspector_map_)); + })); + } + + eprintln!( + "Debugger listening on {}", + websocket_debugger_url(self.address, &uuid) + ); + + uuid } } -async fn server() -> () { - let websocket = - warp::path("websocket") - .and(warp::ws()) - .map(move |ws: warp::ws::Ws| { - ws.on_upgrade(move |_socket| async { - // here +fn websocket_debugger_url(address: SocketAddrV4, uuid: &Uuid) -> String { + format!("ws://{}", websocket_debugger_url2(address, uuid)) +} - // send a message back so register_worker can return... +/// Same thing but without "ws://" prefix +fn websocket_debugger_url2(address: SocketAddrV4, uuid: &Uuid) -> String { + format!("{}:{}/ws/{}", address.ip(), address.port(), uuid) +} - todo!() - // let client = Client::new(state, socket); - // client.on_connection() - }) +async fn server(address: SocketAddrV4, inspector_map: InspectorMap) -> () { + let websocket = warp::path("ws") + .and(warp::path::param()) + .and(warp::ws()) + .map(move |param: String, ws: warp::ws::Ws| { + println!("ws connection {}", param); + ws.on_upgrade(move |_socket| async { + // send a message back so register_worker can return... + todo!() + }) + }); + + let json_list = + warp::path("json") + .map(move || { + let im = inspector_map.lock().unwrap(); + let json_values: Vec = im.iter().map(|(uuid, _)| { + let url = websocket_debugger_url(address, uuid); + let url2 = websocket_debugger_url2(address, uuid); + json!({ + "description": "deno", + "devtoolsFrontendUrl": format!("chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws={}", url2), + "faviconUrl": "https://deno.land/favicon.ico", + "id": uuid.to_string(), + "title": format!("deno[{}]", std::process::id()), + "type": "deno", + "url": "file://", + "webSocketDebuggerUrl": url, + }) + }).collect(); + warp::reply::json(&json!(json_values)) }); - // todo(matt): Make this unique per run (https://github.com/denoland/deno/pull/2696#discussion_r309282566) - let uuid = "97690037-256e-4e27-add0-915ca5421e2f"; - - let address = "127.0.0.1:9229".parse::().unwrap(); - let ip = format!("{}", address.ip()); - let port = 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/{}", crate::version::DENO), - "Protocol-Version": "1.1", - "webSocketDebuggerUrl": format!("ws://{}:{}/{}", ip, port, uuid) + let version = warp::path!("json" / "version").map(|| { + warp::reply::json(&json!({ + "Browser": format!("Deno/{}", crate::version::DENO), + // TODO upgrade to 1.3? https://chromedevtools.github.io/devtools-protocol/ + "Protocol-Version": "1.1", + "V8-Version": crate::version::v8(), + })) }); - let json = warp::path("json").map(move || warp::reply::json(&response_json)); - - let version = warp::path!("json" / "version") - .map(move || warp::reply::json(&response_version)); - - let routes = websocket.or(version).or(json); + let routes = websocket.or(version).or(json_list); warp::serve(routes).bind(address).await; } diff --git a/cli/worker.rs b/cli/worker.rs index add8904d22b071..31b9785ee187ce 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -110,27 +110,26 @@ impl Worker { let global_state_ = state.borrow().global_state.clone(); - /* - if let Some(inspector_server) = global_state_.inspector_server { - inspector_server.register_worker(worker); - } - */ - - let inspector = if global_state_.inspector_server.is_some() { - use deno_core::v8; - let deno_core::Isolate { - v8_isolate, - global_context, - .. - } = &mut **isolate; - let mut hs = v8::HandleScope::new(v8_isolate.as_mut().unwrap()); - let scope = hs.enter(); - let context = global_context.get(scope).unwrap(); - let inspector = crate::inspector::DenoInspector::new(scope, context); - Some(inspector) - } else { - None - }; + let inspector = + if let Some(inspector_server) = &global_state_.inspector_server { + use deno_core::v8; + let deno_core::Isolate { + v8_isolate, + global_context, + .. + } = &mut **isolate; + let mut hs = v8::HandleScope::new(v8_isolate.as_mut().unwrap()); + let scope = hs.enter(); + let context = global_context.get(scope).unwrap(); + let inspector = crate::inspector::DenoInspector::new(scope, context); + + let mut x = inspector_server.lock().unwrap(); + let _uuid = x.register(); + + Some(inspector) + } else { + None + }; isolate.set_js_error_create_fn(move |core_js_error| { JSError::create(core_js_error, &global_state_.ts_compiler) From 04232fb19781bb2c14c27b5cc5aa7185e2b01091 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Wed, 25 Mar 2020 13:20:03 +0100 Subject: [PATCH 05/41] wip --- cli/inspector.rs | 140 ++++++++++++++++++++++++++++++++--------------- cli/worker.rs | 2 +- 2 files changed, 96 insertions(+), 46 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index d11f91108ad63e..c0a7fc73ec688d 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -1,70 +1,90 @@ -#![allow(unused_variables)] -#![allow(dead_code)] - use deno_core::v8; +use futures; +use futures::FutureExt; +use futures::StreamExt; use std::collections::HashMap; use std::mem::MaybeUninit; use std::net::SocketAddrV4; use std::ptr; use std::sync::Arc; use std::sync::Mutex; +use tokio; +use tokio::sync::mpsc; use uuid::Uuid; use warp; use warp::Filter; const CONTEXT_GROUP_ID: i32 = 1; -// TODO Currently the value in this map is just (), but we will fill that in -// later with more stuff. Probably channels. -type InspectorMap = Arc>>; +// These messages can be sent from any thread to the server thread. +enum ServerMsg { + AddInspector { + uuid: Uuid, + inspector_tx: InspectorTx, + }, +} + +type ServerMsgTx = mpsc::UnboundedSender; +type ServerMsgRx = mpsc::UnboundedReceiver; + +enum InspectorMsg {} -/// Owned by GlobalState +type InspectorTx = mpsc::UnboundedSender; +type InspectorRx = mpsc::UnboundedReceiver; + +/// Owned by GlobalState. pub struct InspectorServer { address: SocketAddrV4, thread_handle: Option>, - inspector_map: InspectorMap, + server_msg_tx: ServerMsgTx, } impl InspectorServer { pub fn new() -> Self { + let address = "127.0.0.1:9229".parse::().unwrap(); + let address_ = address.clone(); + let (mut server_msg_tx, mut server_msg_rx) = + mpsc::unbounded_channel::(); + let thread_handle = std::thread::spawn(move || { + crate::tokio_util::run_basic(server(address, server_msg_rx)); + }); Self { - address: "127.0.0.1:9229".parse::().unwrap(), - thread_handle: None, - inspector_map: Arc::new(Mutex::new(HashMap::new())), + address, + thread_handle: Some(thread_handle), + server_msg_tx, } } // TODO this should probably be done in impl Drop, but it seems we're leaking // GlobalState and so it can't be done there... pub fn exit(&mut self) { - if let Some(thread_handle) = self.thread_handle.take() { - thread_handle.join().unwrap(); - } + self.thread_handle.take().unwrap().join().unwrap(); } /// Each worker/isolate to be debugged should call this exactly one. - pub fn register(&mut self) -> Uuid { - let uuid = Uuid::new_v4(); - - let inspector_map_ = self.inspector_map.clone(); - let mut inspector_map = self.inspector_map.lock().unwrap(); - inspector_map.insert(uuid.clone(), ()); - - // Don't start the InspectorServer thread until we have one UUID registered. - if inspector_map.len() == 1 { - let address = self.address; - self.thread_handle = Some(std::thread::spawn(move || { - println!("Open chrome://inspect/"); - crate::tokio_util::run_basic(server(address, inspector_map_)); - })); + pub fn add_inspector(&mut self) -> impl futures::future::Future { + let server_msg_tx = self.server_msg_tx.clone(); + let address = self.address.clone(); + + async move { + let (mut inspector_tx, mut inspector_rx) = + mpsc::unbounded_channel::(); + let uuid = Uuid::new_v4(); + + eprintln!( + "Debugger listening on {}", + websocket_debugger_url(address, &uuid) + ); + + server_msg_tx + .send(ServerMsg::AddInspector { + uuid: uuid, + inspector_tx, + }) + .map_err(|_| { + panic!("sending message to inspector server thread failed"); + }); } - - eprintln!( - "Debugger listening on {}", - websocket_debugger_url(self.address, &uuid) - ); - - uuid } } @@ -77,25 +97,53 @@ fn websocket_debugger_url2(address: SocketAddrV4, uuid: &Uuid) -> String { format!("{}:{}/ws/{}", address.ip(), address.port(), uuid) } -async fn server(address: SocketAddrV4, inspector_map: InspectorMap) -> () { +async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { + let inspector_map = HashMap::::new(); + let inspector_map = Arc::new(std::sync::Mutex::new(inspector_map)); + + let inspector_map_ = inspector_map.clone(); + let msg_handler = async move { + while let Some(msg) = server_msg_rx.next().await { + match msg { + ServerMsg::AddInspector { uuid, inspector_tx } => { + inspector_map_ + .lock() + .unwrap() + .insert(uuid, inspector_tx) + .map(|_| panic!("UUID already in map")); + } + }; + } + }; + let websocket = warp::path("ws") .and(warp::path::param()) .and(warp::ws()) - .map(move |param: String, ws: warp::ws::Ws| { - println!("ws connection {}", param); - ws.on_upgrade(move |_socket| async { + .map(move |uuid: String, ws: warp::ws::Ws| { + ws.on_upgrade(move |mut socket| async move { + let uuid = match Uuid::parse_str(&uuid) { + Ok(uuid) => uuid, + Err(_) => { + return; + } + }; // send a message back so register_worker can return... - todo!() + println!("ws connection {}", uuid); + + while let Some(Ok(msg)) = socket.next().await { + println!("m: {:?}", msg); + } }) }); + let inspector_map_ = inspector_map.clone(); + let address_ = address.clone(); let json_list = warp::path("json") .map(move || { - let im = inspector_map.lock().unwrap(); - let json_values: Vec = im.iter().map(|(uuid, _)| { - let url = websocket_debugger_url(address, uuid); - let url2 = websocket_debugger_url2(address, uuid); + let json_values: Vec = inspector_map_.lock().unwrap().iter().map(|(uuid, _)| { + let url = websocket_debugger_url(address_, uuid); + let url2 = websocket_debugger_url2(address_, uuid); json!({ "description": "deno", "devtoolsFrontendUrl": format!("chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws={}", url2), @@ -120,7 +168,9 @@ async fn server(address: SocketAddrV4, inspector_map: InspectorMap) -> () { }); let routes = websocket.or(version).or(json_list); - warp::serve(routes).bind(address).await; + let web_handler = warp::serve(routes).bind(address); + + futures::future::join(msg_handler, web_handler).await; } /// sub-class of v8::inspector::Channel diff --git a/cli/worker.rs b/cli/worker.rs index 31b9785ee187ce..f6f59f8e659bd3 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -124,7 +124,7 @@ impl Worker { let inspector = crate::inspector::DenoInspector::new(scope, context); let mut x = inspector_server.lock().unwrap(); - let _uuid = x.register(); + tokio::spawn(x.add_inspector()); Some(inspector) } else { From 007ca992736f751d5e7d6c2e829aeddcbf142ad3 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Wed, 25 Mar 2020 13:56:41 +0100 Subject: [PATCH 06/41] wip --- cli/global_state.rs | 4 ++-- cli/inspector.rs | 46 +++++++++++++++++++++++---------------------- cli/lib.rs | 10 +--------- cli/worker.rs | 13 ++++++++++--- 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/cli/global_state.rs b/cli/global_state.rs index 525df22fd8b1aa..6985adc222d5c2 100644 --- a/cli/global_state.rs +++ b/cli/global_state.rs @@ -43,7 +43,7 @@ pub struct GlobalStateInner { pub wasm_compiler: WasmCompiler, pub lockfile: Option>, pub compiler_starts: AtomicUsize, - pub inspector_server: Option>, + pub inspector_server: Option, compile_lock: AsyncMutex<()>, } @@ -85,7 +85,7 @@ impl GlobalState { }; let inspector_server = if flags.debug { - Some(Mutex::new(InspectorServer::new())) + Some(InspectorServer::new()) } else { None }; diff --git a/cli/inspector.rs b/cli/inspector.rs index c0a7fc73ec688d..254732f21646c5 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -1,13 +1,14 @@ +#![allow(dead_code)] + use deno_core::v8; use futures; -use futures::FutureExt; use futures::StreamExt; use std::collections::HashMap; use std::mem::MaybeUninit; use std::net::SocketAddrV4; use std::ptr; use std::sync::Arc; -use std::sync::Mutex; + use tokio; use tokio::sync::mpsc; use uuid::Uuid; @@ -36,38 +37,30 @@ type InspectorRx = mpsc::UnboundedReceiver; pub struct InspectorServer { address: SocketAddrV4, thread_handle: Option>, - server_msg_tx: ServerMsgTx, + server_msg_tx: Option, } impl InspectorServer { pub fn new() -> Self { let address = "127.0.0.1:9229".parse::().unwrap(); - let address_ = address.clone(); - let (mut server_msg_tx, mut server_msg_rx) = - mpsc::unbounded_channel::(); + let (server_msg_tx, server_msg_rx) = mpsc::unbounded_channel::(); let thread_handle = std::thread::spawn(move || { crate::tokio_util::run_basic(server(address, server_msg_rx)); }); Self { address, thread_handle: Some(thread_handle), - server_msg_tx, + server_msg_tx: Some(server_msg_tx), } } - // TODO this should probably be done in impl Drop, but it seems we're leaking - // GlobalState and so it can't be done there... - pub fn exit(&mut self) { - self.thread_handle.take().unwrap().join().unwrap(); - } - /// Each worker/isolate to be debugged should call this exactly one. - pub fn add_inspector(&mut self) -> impl futures::future::Future { + pub fn add_inspector(&self) -> impl futures::future::Future { let server_msg_tx = self.server_msg_tx.clone(); - let address = self.address.clone(); + let address = self.address; async move { - let (mut inspector_tx, mut inspector_rx) = + let (inspector_tx, _inspector_rx) = mpsc::unbounded_channel::(); let uuid = Uuid::new_v4(); @@ -77,17 +70,26 @@ impl InspectorServer { ); server_msg_tx + .as_ref() + .unwrap() .send(ServerMsg::AddInspector { uuid: uuid, inspector_tx, }) - .map_err(|_| { + .unwrap_or_else(|_| { panic!("sending message to inspector server thread failed"); }); } } } +impl Drop for InspectorServer { + fn drop(&mut self) { + self.server_msg_tx.take(); + self.thread_handle.take().unwrap().join().unwrap(); + } +} + fn websocket_debugger_url(address: SocketAddrV4, uuid: &Uuid) -> String { format!("ws://{}", websocket_debugger_url2(address, uuid)) } @@ -208,8 +210,8 @@ impl v8::inspector::ChannelImpl for DenoInspectorSession { fn send_response( &mut self, - call_id: i32, - message: v8::UniquePtr, + _call_id: i32, + _message: v8::UniquePtr, ) { // deno_isolate.inspector_message_cb(message) todo!() @@ -217,7 +219,7 @@ impl v8::inspector::ChannelImpl for DenoInspectorSession { fn send_notification( &mut self, - message: v8::UniquePtr, + _message: v8::UniquePtr, ) { // deno_isolate.inspector_message_cb(message) todo!() @@ -282,7 +284,7 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspector { &mut self.client } - fn run_message_loop_on_pause(&mut self, context_group_id: i32) { + fn run_message_loop_on_pause(&mut self, _context_group_id: i32) { // while !self.terminated { // self.deno_isolate.inspector_block_recv(); // } @@ -293,7 +295,7 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspector { todo!() } - fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) { + fn run_if_waiting_for_debugger(&mut self, _context_group_id: i32) { todo!() } } diff --git a/cli/lib.rs b/cli/lib.rs index a71110a2cc1455..b00d8af946424a 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -376,15 +376,7 @@ async fn run_command(flags: Flags, script: String) -> Result<(), ErrBox> { // TODO This should probably be done in impl Drop for InspectorServer, but it // seems GlobalState is being leaked and the drop is never called. - { - let mut s = global_state - .inspector_server - .as_ref() - .unwrap() - .lock() - .unwrap(); - s.exit(); - } + // global_state.inspector_server.take(); Ok(()) } diff --git a/cli/worker.rs b/cli/worker.rs index f6f59f8e659bd3..9eaf86f3808dc9 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -111,7 +111,7 @@ impl Worker { let global_state_ = state.borrow().global_state.clone(); let inspector = - if let Some(inspector_server) = &global_state_.inspector_server { + if let Some(inspector_server) = global_state_.inspector_server.as_ref() { use deno_core::v8; let deno_core::Isolate { v8_isolate, @@ -123,8 +123,7 @@ impl Worker { let context = global_context.get(scope).unwrap(); let inspector = crate::inspector::DenoInspector::new(scope, context); - let mut x = inspector_server.lock().unwrap(); - tokio::spawn(x.add_inspector()); + tokio::spawn(inspector_server.add_inspector()); Some(inspector) } else { @@ -202,6 +201,14 @@ impl Worker { } } +impl Drop for Worker { + fn drop(&mut self) { + // The Isolate object must outlive the Inspector object, but this is + // currently not enforced by the type system. + self.inspector.take(); + } +} + impl Future for Worker { type Output = Result<(), ErrBox>; From ed964cd8a1e36ff8b73416d2806dee61dd123bc0 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 25 Mar 2020 11:03:00 -0400 Subject: [PATCH 07/41] Provide IsolateHandle to ws server --- cli/inspector.rs | 13 ++++++++++--- cli/worker.rs | 3 ++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 254732f21646c5..ff6465b96f5033 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -22,6 +22,7 @@ enum ServerMsg { AddInspector { uuid: Uuid, inspector_tx: InspectorTx, + isolate_handle: v8::IsolateHandle, }, } @@ -55,7 +56,10 @@ impl InspectorServer { } /// Each worker/isolate to be debugged should call this exactly one. - pub fn add_inspector(&self) -> impl futures::future::Future { + pub fn add_inspector( + &self, + isolate_handle: v8::IsolateHandle, + ) -> impl futures::future::Future { let server_msg_tx = self.server_msg_tx.clone(); let address = self.address; @@ -73,8 +77,9 @@ impl InspectorServer { .as_ref() .unwrap() .send(ServerMsg::AddInspector { - uuid: uuid, + uuid, inspector_tx, + isolate_handle, }) .unwrap_or_else(|_| { panic!("sending message to inspector server thread failed"); @@ -107,7 +112,9 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { let msg_handler = async move { while let Some(msg) = server_msg_rx.next().await { match msg { - ServerMsg::AddInspector { uuid, inspector_tx } => { + ServerMsg::AddInspector { + uuid, inspector_tx, .. + } => { inspector_map_ .lock() .unwrap() diff --git a/cli/worker.rs b/cli/worker.rs index 9eaf86f3808dc9..0d152fa77f8670 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -118,12 +118,13 @@ impl Worker { global_context, .. } = &mut **isolate; + let isolate_handle = v8_isolate.as_mut().unwrap().thread_safe_handle(); let mut hs = v8::HandleScope::new(v8_isolate.as_mut().unwrap()); let scope = hs.enter(); let context = global_context.get(scope).unwrap(); let inspector = crate::inspector::DenoInspector::new(scope, context); - tokio::spawn(inspector_server.add_inspector()); + tokio::spawn(inspector_server.add_inspector(isolate_handle)); Some(inspector) } else { From e1aafdfa308b540b1a9f978e68c35f2730e13855 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 25 Mar 2020 11:06:08 -0400 Subject: [PATCH 08/41] Add strong reminder that InspectorServer::drop is broken --- cli/inspector.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/inspector.rs b/cli/inspector.rs index ff6465b96f5033..9c7cb16aa2a543 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -92,6 +92,7 @@ impl Drop for InspectorServer { fn drop(&mut self) { self.server_msg_tx.take(); self.thread_handle.take().unwrap().join().unwrap(); + panic!("TODO: this drop is never called"); } } From 50e17f2302a7ae96865538b751d64a77bfccf3f3 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 25 Mar 2020 11:28:57 -0400 Subject: [PATCH 09/41] send connection message --- cli/inspector.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 9c7cb16aa2a543..dc62cc811e13a6 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -29,7 +29,9 @@ enum ServerMsg { type ServerMsgTx = mpsc::UnboundedSender; type ServerMsgRx = mpsc::UnboundedReceiver; -enum InspectorMsg {} +enum InspectorMsg { + WsConnection, // TODO add send half of websocket connection? +} type InspectorTx = mpsc::UnboundedSender; type InspectorRx = mpsc::UnboundedReceiver; @@ -126,10 +128,12 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { } }; + let inspector_map_ = inspector_map.clone(); let websocket = warp::path("ws") .and(warp::path::param()) .and(warp::ws()) .map(move |uuid: String, ws: warp::ws::Ws| { + let inspector_map__ = inspector_map_.clone(); ws.on_upgrade(move |mut socket| async move { let uuid = match Uuid::parse_str(&uuid) { Ok(uuid) => uuid, @@ -137,9 +141,22 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { return; } }; + + let inspector_tx = { + let g = inspector_map__.lock().unwrap(); + let maybe = g.get(&uuid); + if maybe.is_none() { + return; + } + maybe.unwrap().clone() + }; + // send a message back so register_worker can return... println!("ws connection {}", uuid); + let r = inspector_tx.send(InspectorMsg::WsConnection); + assert!(r.is_ok()); + while let Some(Ok(msg)) = socket.next().await { println!("m: {:?}", msg); } From 9eb4c2fcf0776341a4b49ccf5b40245d7fb4d750 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 25 Mar 2020 13:16:18 -0400 Subject: [PATCH 10/41] cleanup --- cli/inspector.rs | 92 ++++++++++++++++++++++++------------------------ cli/worker.rs | 2 +- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index dc62cc811e13a6..541e8be61c493e 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -30,10 +30,17 @@ type ServerMsgTx = mpsc::UnboundedSender; type ServerMsgRx = mpsc::UnboundedReceiver; enum InspectorMsg { - WsConnection, // TODO add send half of websocket connection? + WsConnection { + ws_tx: futures::stream::SplitSink< + warp::filters::ws::WebSocket, + warp::filters::ws::Message, + >, + }, } +/// Owned by the web socket server type InspectorTx = mpsc::UnboundedSender; +/// Owned by the isolate/worker type InspectorRx = mpsc::UnboundedReceiver; /// Owned by GlobalState. @@ -58,35 +65,28 @@ impl InspectorServer { } /// Each worker/isolate to be debugged should call this exactly one. - pub fn add_inspector( - &self, - isolate_handle: v8::IsolateHandle, - ) -> impl futures::future::Future { - let server_msg_tx = self.server_msg_tx.clone(); + pub fn add_inspector(&self, isolate_handle: v8::IsolateHandle) { + let server_msg_tx = self.server_msg_tx.as_ref().unwrap().clone(); let address = self.address; - async move { - let (inspector_tx, _inspector_rx) = - mpsc::unbounded_channel::(); - let uuid = Uuid::new_v4(); - - eprintln!( - "Debugger listening on {}", - websocket_debugger_url(address, &uuid) - ); - - server_msg_tx - .as_ref() - .unwrap() - .send(ServerMsg::AddInspector { - uuid, - inspector_tx, - isolate_handle, - }) - .unwrap_or_else(|_| { - panic!("sending message to inspector server thread failed"); - }); - } + let (inspector_tx, _inspector_rx) = + mpsc::unbounded_channel::(); + let uuid = Uuid::new_v4(); + + eprintln!( + "Debugger listening on {}", + websocket_debugger_url(address, &uuid) + ); + + server_msg_tx + .send(ServerMsg::AddInspector { + uuid, + inspector_tx, + isolate_handle, + }) + .unwrap_or_else(|_| { + panic!("sending message to inspector server thread failed"); + }); } } @@ -134,30 +134,31 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { .and(warp::ws()) .map(move |uuid: String, ws: warp::ws::Ws| { let inspector_map__ = inspector_map_.clone(); - ws.on_upgrade(move |mut socket| async move { - let uuid = match Uuid::parse_str(&uuid) { - Ok(uuid) => uuid, - Err(_) => { - return; - } - }; - + ws.on_upgrade(move |socket| async move { let inspector_tx = { - let g = inspector_map__.lock().unwrap(); - let maybe = g.get(&uuid); - if maybe.is_none() { + if let Ok(uuid) = Uuid::parse_str(&uuid) { + let g = inspector_map__.lock().unwrap(); + if let Some(inspector_tx) = g.get(&uuid) { + println!("ws connection {}", uuid); + inspector_tx.clone() + } else { + return; + } + } else { return; } - maybe.unwrap().clone() }; // send a message back so register_worker can return... - println!("ws connection {}", uuid); + let (ws_tx, mut ws_rx) = socket.split(); - let r = inspector_tx.send(InspectorMsg::WsConnection); - assert!(r.is_ok()); + let r = inspector_tx + .send(InspectorMsg::WsConnection { ws_tx }) + .unwrap_or_else(|_| { + panic!("sending message to inspector_tx failed"); + }); - while let Some(Ok(msg)) = socket.next().await { + while let Some(Ok(msg)) = ws_rx.next().await { println!("m: {:?}", msg); } }) @@ -170,10 +171,9 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { .map(move || { let json_values: Vec = inspector_map_.lock().unwrap().iter().map(|(uuid, _)| { let url = websocket_debugger_url(address_, uuid); - let url2 = websocket_debugger_url2(address_, uuid); json!({ "description": "deno", - "devtoolsFrontendUrl": format!("chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws={}", url2), + "devtoolsFrontendUrl": format!("chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws={}", url), "faviconUrl": "https://deno.land/favicon.ico", "id": uuid.to_string(), "title": format!("deno[{}]", std::process::id()), diff --git a/cli/worker.rs b/cli/worker.rs index 0d152fa77f8670..135e97640c5435 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -124,7 +124,7 @@ impl Worker { let context = global_context.get(scope).unwrap(); let inspector = crate::inspector::DenoInspector::new(scope, context); - tokio::spawn(inspector_server.add_inspector(isolate_handle)); + inspector_server.add_inspector(isolate_handle.clone()); Some(inspector) } else { From 8042c1f7dbbb598a792200f6f3d520cb481daf86 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 25 Mar 2020 13:28:53 -0400 Subject: [PATCH 11/41] add InspectorInfo, cleanups --- cli/inspector.rs | 53 +++++++++++++++++++++++++++++++++++++----------- cli/worker.rs | 43 +++++++++++++++++++++------------------ 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 541e8be61c493e..2f7b74b0b7edbf 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -29,7 +29,7 @@ enum ServerMsg { type ServerMsgTx = mpsc::UnboundedSender; type ServerMsgRx = mpsc::UnboundedReceiver; -enum InspectorMsg { +pub enum InspectorMsg { WsConnection { ws_tx: futures::stream::SplitSink< warp::filters::ws::WebSocket, @@ -43,6 +43,13 @@ type InspectorTx = mpsc::UnboundedSender; /// Owned by the isolate/worker type InspectorRx = mpsc::UnboundedReceiver; +// Stored in a UUID hashmap, used by WS server +#[derive(Clone)] +struct InspectorInfo { + inspector_tx: InspectorTx, + isolate_handle: v8::IsolateHandle, +} + /// Owned by GlobalState. pub struct InspectorServer { address: SocketAddrV4, @@ -65,11 +72,15 @@ impl InspectorServer { } /// Each worker/isolate to be debugged should call this exactly one. - pub fn add_inspector(&self, isolate_handle: v8::IsolateHandle) { + /// Called from worker's thread + pub fn add_inspector( + &self, + isolate_handle: v8::IsolateHandle, + ) -> InspectorRx { let server_msg_tx = self.server_msg_tx.as_ref().unwrap().clone(); let address = self.address; - let (inspector_tx, _inspector_rx) = + let (inspector_tx, inspector_rx) = mpsc::unbounded_channel::(); let uuid = Uuid::new_v4(); @@ -87,6 +98,8 @@ impl InspectorServer { .unwrap_or_else(|_| { panic!("sending message to inspector server thread failed"); }); + + inspector_rx } } @@ -108,7 +121,7 @@ fn websocket_debugger_url2(address: SocketAddrV4, uuid: &Uuid) -> String { } async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { - let inspector_map = HashMap::::new(); + let inspector_map = HashMap::::new(); let inspector_map = Arc::new(std::sync::Mutex::new(inspector_map)); let inspector_map_ = inspector_map.clone(); @@ -116,12 +129,20 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { while let Some(msg) = server_msg_rx.next().await { match msg { ServerMsg::AddInspector { - uuid, inspector_tx, .. + uuid, + inspector_tx, + isolate_handle, } => { inspector_map_ .lock() .unwrap() - .insert(uuid, inspector_tx) + .insert( + uuid, + InspectorInfo { + inspector_tx, + isolate_handle, + }, + ) .map(|_| panic!("UUID already in map")); } }; @@ -135,12 +156,12 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { .map(move |uuid: String, ws: warp::ws::Ws| { let inspector_map__ = inspector_map_.clone(); ws.on_upgrade(move |socket| async move { - let inspector_tx = { + let inspector_info = { if let Ok(uuid) = Uuid::parse_str(&uuid) { let g = inspector_map__.lock().unwrap(); - if let Some(inspector_tx) = g.get(&uuid) { + if let Some(inspector_info) = g.get(&uuid) { println!("ws connection {}", uuid); - inspector_tx.clone() + inspector_info.clone() } else { return; } @@ -152,7 +173,8 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { // send a message back so register_worker can return... let (ws_tx, mut ws_rx) = socket.split(); - let r = inspector_tx + inspector_info + .inspector_tx .send(InspectorMsg::WsConnection { ws_tx }) .unwrap_or_else(|_| { panic!("sending message to inspector_tx failed"); @@ -263,16 +285,22 @@ pub struct DenoInspector { terminated: bool, sessions: HashMap>, next_session_id: usize, + /// used to receive messages from ws server + inspector_rx: InspectorRx, } impl DenoInspector { - pub fn new

(scope: &mut P, context: v8::Local) -> Box + pub fn new

( + scope: &mut P, + context: v8::Local, + inspector_rx: InspectorRx, + ) -> Box where P: v8::InIsolate, { let mut deno_inspector = new_box_with(|address| Self { client: v8::inspector::V8InspectorClientBase::new::(), - // Todo(piscisaureus): V8Inspector::create() should require that + // TODO(piscisaureus): V8Inspector::create() should require that // the 'client' argument cannot move. inspector: v8::inspector::V8Inspector::create(scope, unsafe { &mut *address @@ -280,6 +308,7 @@ impl DenoInspector { terminated: false, sessions: HashMap::new(), next_session_id: 1, + inspector_rx, }); let empty_view = v8::inspector::StringView::empty(); diff --git a/cli/worker.rs b/cli/worker.rs index 135e97640c5435..9a71d2df4518d1 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -110,26 +110,29 @@ impl Worker { let global_state_ = state.borrow().global_state.clone(); - let inspector = - if let Some(inspector_server) = global_state_.inspector_server.as_ref() { - use deno_core::v8; - let deno_core::Isolate { - v8_isolate, - global_context, - .. - } = &mut **isolate; - let isolate_handle = v8_isolate.as_mut().unwrap().thread_safe_handle(); - let mut hs = v8::HandleScope::new(v8_isolate.as_mut().unwrap()); - let scope = hs.enter(); - let context = global_context.get(scope).unwrap(); - let inspector = crate::inspector::DenoInspector::new(scope, context); - - inspector_server.add_inspector(isolate_handle.clone()); - - Some(inspector) - } else { - None - }; + let inspector = if let Some(inspector_server) = + global_state_.inspector_server.as_ref() + { + use deno_core::v8; + let deno_core::Isolate { + v8_isolate, + global_context, + .. + } = &mut **isolate; + let isolate_handle = v8_isolate.as_mut().unwrap().thread_safe_handle(); + let mut hs = v8::HandleScope::new(v8_isolate.as_mut().unwrap()); + let scope = hs.enter(); + let context = global_context.get(scope).unwrap(); + + let inspector_rx = inspector_server.add_inspector(isolate_handle.clone()); + + let inspector = + crate::inspector::DenoInspector::new(scope, context, inspector_rx); + + Some(inspector) + } else { + None + }; isolate.set_js_error_create_fn(move |core_js_error| { JSError::create(core_js_error, &global_state_.ts_compiler) From b29a1d181c33a80c381f3e32e343282738d726dd Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 25 Mar 2020 13:41:31 -0400 Subject: [PATCH 12/41] x --- cli/inspector.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 2f7b74b0b7edbf..7a136db046e473 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -285,15 +285,13 @@ pub struct DenoInspector { terminated: bool, sessions: HashMap>, next_session_id: usize, - /// used to receive messages from ws server - inspector_rx: InspectorRx, } impl DenoInspector { pub fn new

( scope: &mut P, context: v8::Local, - inspector_rx: InspectorRx, + mut inspector_rx: InspectorRx, ) -> Box where P: v8::InIsolate, @@ -308,7 +306,6 @@ impl DenoInspector { terminated: false, sessions: HashMap::new(), next_session_id: 1, - inspector_rx, }); let empty_view = v8::inspector::StringView::empty(); @@ -318,6 +315,17 @@ impl DenoInspector { &empty_view, ); + tokio::spawn(async move { + while let Some(msg) = inspector_rx.next().await { + match msg { + InspectorMsg::WsConnection { .. } => { + println!("Got new ws connection in DenoInspector"); + // TODO need to create a new session. + } + } + } + }); + deno_inspector } From 0d5e1b822ba4eac6777390cfa2627929d956635a Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 25 Mar 2020 15:13:12 -0400 Subject: [PATCH 13/41] cleanup --- cli/inspector.rs | 132 ++++++++++++++++++++++++++--------------------- cli/worker.rs | 30 +++-------- 2 files changed, 79 insertions(+), 83 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 7a136db046e473..989770f1a6a4c2 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -75,15 +75,27 @@ impl InspectorServer { /// Called from worker's thread pub fn add_inspector( &self, - isolate_handle: v8::IsolateHandle, - ) -> InspectorRx { + isolate: &mut deno_core::Isolate, + ) -> Box { + let deno_core::Isolate { + v8_isolate, + global_context, + .. + } = isolate; + let isolate_handle = v8_isolate.as_mut().unwrap().thread_safe_handle(); + let mut hs = v8::HandleScope::new(v8_isolate.as_mut().unwrap()); + let scope = hs.enter(); + let context = global_context.get(scope).unwrap(); + let server_msg_tx = self.server_msg_tx.as_ref().unwrap().clone(); let address = self.address; - let (inspector_tx, inspector_rx) = mpsc::unbounded_channel::(); let uuid = Uuid::new_v4(); + let inspector = + crate::inspector::DenoInspector::new(scope, context, inspector_rx); + eprintln!( "Debugger listening on {}", websocket_debugger_url(address, &uuid) @@ -99,7 +111,7 @@ impl InspectorServer { panic!("sending message to inspector server thread failed"); }); - inspector_rx + inspector } } @@ -222,62 +234,6 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { futures::future::join(msg_handler, web_handler).await; } -/// sub-class of v8::inspector::Channel -pub struct DenoInspectorSession { - channel: v8::inspector::ChannelBase, - session: v8::UniqueRef, -} - -impl DenoInspectorSession { - pub fn new(inspector: &mut v8::inspector::V8Inspector) -> Box { - new_box_with(|address| { - let empty_view = v8::inspector::StringView::empty(); - Self { - channel: v8::inspector::ChannelBase::new::(), - session: inspector.connect( - CONTEXT_GROUP_ID, - // Todo(piscisaureus): V8Inspector::connect() should require that - // the 'channel' argument cannot move. - unsafe { &mut *address }, - &empty_view, - ), - } - }) - } -} - -impl v8::inspector::ChannelImpl for DenoInspectorSession { - fn base(&self) -> &v8::inspector::ChannelBase { - &self.channel - } - - fn base_mut(&mut self) -> &mut v8::inspector::ChannelBase { - &mut self.channel - } - - fn send_response( - &mut self, - _call_id: i32, - _message: v8::UniquePtr, - ) { - // deno_isolate.inspector_message_cb(message) - todo!() - } - - fn send_notification( - &mut self, - _message: v8::UniquePtr, - ) { - // deno_isolate.inspector_message_cb(message) - todo!() - } - - fn flush_protocol_notifications(&mut self) { - // pass - todo!() - } -} - #[repr(C)] pub struct DenoInspector { client: v8::inspector::V8InspectorClientBase, @@ -362,6 +318,62 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspector { } } +/// sub-class of v8::inspector::Channel +pub struct DenoInspectorSession { + channel: v8::inspector::ChannelBase, + session: v8::UniqueRef, +} + +impl DenoInspectorSession { + pub fn new(inspector: &mut v8::inspector::V8Inspector) -> Box { + new_box_with(|address| { + let empty_view = v8::inspector::StringView::empty(); + Self { + channel: v8::inspector::ChannelBase::new::(), + session: inspector.connect( + CONTEXT_GROUP_ID, + // Todo(piscisaureus): V8Inspector::connect() should require that + // the 'channel' argument cannot move. + unsafe { &mut *address }, + &empty_view, + ), + } + }) + } +} + +impl v8::inspector::ChannelImpl for DenoInspectorSession { + fn base(&self) -> &v8::inspector::ChannelBase { + &self.channel + } + + fn base_mut(&mut self) -> &mut v8::inspector::ChannelBase { + &mut self.channel + } + + fn send_response( + &mut self, + _call_id: i32, + _message: v8::UniquePtr, + ) { + // deno_isolate.inspector_message_cb(message) + todo!() + } + + fn send_notification( + &mut self, + _message: v8::UniquePtr, + ) { + // deno_isolate.inspector_message_cb(message) + todo!() + } + + fn flush_protocol_notifications(&mut self) { + // pass + todo!() + } +} + fn new_box_with(new_fn: impl FnOnce(*mut T) -> T) -> Box { let b = Box::new(MaybeUninit::::uninit()); let p = Box::into_raw(b) as *mut T; diff --git a/cli/worker.rs b/cli/worker.rs index 9a71d2df4518d1..a3e7798ee39a95 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -110,29 +110,13 @@ impl Worker { let global_state_ = state.borrow().global_state.clone(); - let inspector = if let Some(inspector_server) = - global_state_.inspector_server.as_ref() - { - use deno_core::v8; - let deno_core::Isolate { - v8_isolate, - global_context, - .. - } = &mut **isolate; - let isolate_handle = v8_isolate.as_mut().unwrap().thread_safe_handle(); - let mut hs = v8::HandleScope::new(v8_isolate.as_mut().unwrap()); - let scope = hs.enter(); - let context = global_context.get(scope).unwrap(); - - let inspector_rx = inspector_server.add_inspector(isolate_handle.clone()); - - let inspector = - crate::inspector::DenoInspector::new(scope, context, inspector_rx); - - Some(inspector) - } else { - None - }; + let inspector = + if let Some(inspector_server) = global_state_.inspector_server.as_ref() { + let inspector = inspector_server.add_inspector(&mut *isolate); + Some(inspector) + } else { + None + }; isolate.set_js_error_create_fn(move |core_js_error| { JSError::create(core_js_error, &global_state_.ts_compiler) From 05e091fb0c85f701434651a1284967bd4772ab94 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 25 Mar 2020 15:16:26 -0400 Subject: [PATCH 14/41] remove websocket_debugger_url2 --- cli/inspector.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 989770f1a6a4c2..c1f73c20f45fb1 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -124,12 +124,7 @@ impl Drop for InspectorServer { } fn websocket_debugger_url(address: SocketAddrV4, uuid: &Uuid) -> String { - format!("ws://{}", websocket_debugger_url2(address, uuid)) -} - -/// Same thing but without "ws://" prefix -fn websocket_debugger_url2(address: SocketAddrV4, uuid: &Uuid) -> String { - format!("{}:{}/ws/{}", address.ip(), address.port(), uuid) + format!("ws://{}:{}/ws/{}", address.ip(), address.port(), uuid) } async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { From 88b7f858abdd2cdfda68ac6935c3eb94a1b35c6c Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 25 Mar 2020 16:07:02 -0400 Subject: [PATCH 15/41] session_id creation --- cli/inspector.rs | 74 ++++++++++++++++++++++++++++++++---------------- cli/worker.rs | 6 +++- 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index c1f73c20f45fb1..87c0b1546052eb 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -4,10 +4,14 @@ use deno_core::v8; use futures; use futures::StreamExt; use std::collections::HashMap; +use std::future::Future; use std::mem::MaybeUninit; use std::net::SocketAddrV4; +use std::pin::Pin; use std::ptr; use std::sync::Arc; +use std::task::Context; +use std::task::Poll; use tokio; use tokio::sync::mpsc; @@ -76,7 +80,7 @@ impl InspectorServer { pub fn add_inspector( &self, isolate: &mut deno_core::Isolate, - ) -> Box { + ) -> DenoInspector { let deno_core::Isolate { v8_isolate, global_context, @@ -229,26 +233,57 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { futures::future::join(msg_handler, web_handler).await; } +pub struct DenoInspector(Box); + #[repr(C)] -pub struct DenoInspector { +struct DenoInspectorInner { client: v8::inspector::V8InspectorClientBase, inspector: v8::UniqueRef, terminated: bool, sessions: HashMap>, next_session_id: usize, + // recv_future: Option>>>, + inspector_rx: InspectorRx, +} + +impl Future for DenoInspector { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let deno_inspector = self.get_mut(); + //inner.get_messages().boxed_local(); + // + // let recv_future = inner.recv_future.as_mut().unwrap().as_mut(); + // recv_future.poll(cx) + + match deno_inspector.0.inspector_rx.poll_recv(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Some(msg)) => { + match msg { + InspectorMsg::WsConnection { .. } => { + println!("Got new ws connection in DenoInspector"); + let session_id = deno_inspector.connect(); + println!("session_id {:?}", session_id); + } + }; + Poll::Ready(()) + } + _ => todo!("fixme"), + } + } } impl DenoInspector { pub fn new

( scope: &mut P, context: v8::Local, - mut inspector_rx: InspectorRx, - ) -> Box + inspector_rx: InspectorRx, + ) -> Self where P: v8::InIsolate, { - let mut deno_inspector = new_box_with(|address| Self { - client: v8::inspector::V8InspectorClientBase::new::(), + let mut deno_inspector = new_box_with(|address| DenoInspectorInner { + client: v8::inspector::V8InspectorClientBase::new::(), // TODO(piscisaureus): V8Inspector::create() should require that // the 'client' argument cannot move. inspector: v8::inspector::V8Inspector::create(scope, unsafe { @@ -257,6 +292,7 @@ impl DenoInspector { terminated: false, sessions: HashMap::new(), next_session_id: 1, + inspector_rx, }); let empty_view = v8::inspector::StringView::empty(); @@ -266,29 +302,19 @@ impl DenoInspector { &empty_view, ); - tokio::spawn(async move { - while let Some(msg) = inspector_rx.next().await { - match msg { - InspectorMsg::WsConnection { .. } => { - println!("Got new ws connection in DenoInspector"); - // TODO need to create a new session. - } - } - } - }); - - deno_inspector + DenoInspector(deno_inspector) } - pub fn connect(&mut self) { - let id = self.next_session_id; - self.next_session_id += 1; - let session = DenoInspectorSession::new(&mut self.inspector); - self.sessions.insert(id, session); + pub fn connect(&mut self) -> usize { + let id = self.0.next_session_id; + self.0.next_session_id += 1; + let session = DenoInspectorSession::new(&mut self.0.inspector); + self.0.sessions.insert(id, session); + id } } -impl v8::inspector::V8InspectorClientImpl for DenoInspector { +impl v8::inspector::V8InspectorClientImpl for DenoInspectorInner { fn base(&self) -> &v8::inspector::V8InspectorClientBase { &self.client } diff --git a/cli/worker.rs b/cli/worker.rs index a3e7798ee39a95..774f5140c74bd1 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -100,7 +100,7 @@ pub struct Worker { pub waker: AtomicWaker, pub(crate) internal_channels: WorkerChannelsInternal, external_channels: WorkerHandle, - inspector: Option>, + inspector: Option, } impl Worker { @@ -202,6 +202,10 @@ impl Future for Worker { fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let inner = self.get_mut(); + if let Some(deno_inspector) = inner.inspector.as_mut() { + // We always poll the inspector if it exists. + let _ = deno_inspector.poll_unpin(cx); + } inner.waker.register(cx.waker()); inner.isolate.poll_unpin(cx) } From be5a33f325b45bdb545b50e336c1d0af8eff0532 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 25 Mar 2020 16:59:14 -0400 Subject: [PATCH 16/41] Messages are being sent back and forth --- cli/inspector.rs | 103 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 27 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 87c0b1546052eb..9536950a82fd0c 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] +use crate::futures::SinkExt; use deno_core::v8; use futures; use futures::StreamExt; @@ -17,6 +18,7 @@ use tokio; use tokio::sync::mpsc; use uuid::Uuid; use warp; +use warp::filters::ws; use warp::Filter; const CONTEXT_GROUP_ID: i32 = 1; @@ -33,12 +35,16 @@ enum ServerMsg { type ServerMsgTx = mpsc::UnboundedSender; type ServerMsgRx = mpsc::UnboundedReceiver; +type WsTx = futures::stream::SplitSink; + pub enum InspectorMsg { WsConnection { - ws_tx: futures::stream::SplitSink< - warp::filters::ws::WebSocket, - warp::filters::ws::Message, - >, + session_uuid: Uuid, + ws_tx: WsTx, + }, + WsIncoming { + session_uuid: Uuid, + msg: ws::Message, }, } @@ -184,15 +190,26 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { // send a message back so register_worker can return... let (ws_tx, mut ws_rx) = socket.split(); + // Not to be confused with the WS's uuid... + let session_uuid = Uuid::new_v4(); + inspector_info .inspector_tx - .send(InspectorMsg::WsConnection { ws_tx }) + .send(InspectorMsg::WsConnection { + ws_tx, + session_uuid, + }) .unwrap_or_else(|_| { panic!("sending message to inspector_tx failed"); }); while let Some(Ok(msg)) = ws_rx.next().await { - println!("m: {:?}", msg); + inspector_info + .inspector_tx + .send(InspectorMsg::WsIncoming { msg, session_uuid }) + .unwrap_or_else(|_| { + panic!("sending message to inspector_tx failed"); + }); } }) }); @@ -240,12 +257,14 @@ struct DenoInspectorInner { client: v8::inspector::V8InspectorClientBase, inspector: v8::UniqueRef, terminated: bool, - sessions: HashMap>, - next_session_id: usize, + sessions: HashMap>, // recv_future: Option>>>, inspector_rx: InspectorRx, } +/// DenoInspector implements a Future so that it can poll for incoming messages +/// from the WebSocket server. Since a Worker ownes a DenoInspector, and because +/// a Worker is a Future too, Worker::poll will call this. impl Future for DenoInspector { type Output = (); @@ -260,10 +279,22 @@ impl Future for DenoInspector { Poll::Pending => Poll::Pending, Poll::Ready(Some(msg)) => { match msg { - InspectorMsg::WsConnection { .. } => { - println!("Got new ws connection in DenoInspector"); - let session_id = deno_inspector.connect(); - println!("session_id {:?}", session_id); + InspectorMsg::WsConnection { + session_uuid, + ws_tx, + } => { + deno_inspector.connect(session_uuid, ws_tx); + println!("Got new ws connection in DenoInspector {}", session_uuid); + } + InspectorMsg::WsIncoming { session_uuid, msg } => { + println!(">>> {}", msg.to_str().unwrap()); + if let Some(deno_session) = + deno_inspector.0.sessions.get_mut(&session_uuid) + { + deno_session.dispatch_protocol_message(msg) + } else { + eprintln!("Unknown session {}. msg {:?}", session_uuid, msg); + } } }; Poll::Ready(()) @@ -291,7 +322,6 @@ impl DenoInspector { }), terminated: false, sessions: HashMap::new(), - next_session_id: 1, inspector_rx, }); @@ -305,12 +335,9 @@ impl DenoInspector { DenoInspector(deno_inspector) } - pub fn connect(&mut self) -> usize { - let id = self.0.next_session_id; - self.0.next_session_id += 1; - let session = DenoInspectorSession::new(&mut self.0.inspector); - self.0.sessions.insert(id, session); - id + pub fn connect(&mut self, session_uuid: Uuid, ws_tx: WsTx) { + let session = DenoInspectorSession::new(&mut self.0.inspector, ws_tx); + self.0.sessions.insert(session_uuid, session); } } @@ -343,10 +370,14 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspectorInner { pub struct DenoInspectorSession { channel: v8::inspector::ChannelBase, session: v8::UniqueRef, + ws_tx: WsTx, } impl DenoInspectorSession { - pub fn new(inspector: &mut v8::inspector::V8Inspector) -> Box { + pub fn new( + inspector: &mut v8::inspector::V8Inspector, + ws_tx: WsTx, + ) -> Box { new_box_with(|address| { let empty_view = v8::inspector::StringView::empty(); Self { @@ -358,9 +389,16 @@ impl DenoInspectorSession { unsafe { &mut *address }, &empty_view, ), + ws_tx, } }) } + + pub fn dispatch_protocol_message(&mut self, ws_msg: ws::Message) { + let bytes = ws_msg.as_bytes(); + let string_view = v8::inspector::StringView::from(bytes); + self.session.dispatch_protocol_message(&string_view); + } } impl v8::inspector::ChannelImpl for DenoInspectorSession { @@ -375,26 +413,37 @@ impl v8::inspector::ChannelImpl for DenoInspectorSession { fn send_response( &mut self, _call_id: i32, - _message: v8::UniquePtr, + message: v8::UniquePtr, ) { - // deno_isolate.inspector_message_cb(message) - todo!() + let ws_msg = v8_to_ws_msg(message); + let r = futures::executor::block_on(self.ws_tx.send(ws_msg)); + assert!(r.is_ok()); } fn send_notification( &mut self, - _message: v8::UniquePtr, + message: v8::UniquePtr, ) { - // deno_isolate.inspector_message_cb(message) - todo!() + let ws_msg = v8_to_ws_msg(message); + let r = futures::executor::block_on(self.ws_tx.send(ws_msg)); + assert!(r.is_ok()); } fn flush_protocol_notifications(&mut self) { - // pass todo!() } } +// TODO impl From or Into +fn v8_to_ws_msg( + message: v8::UniquePtr, +) -> ws::Message { + let mut x = message.unwrap(); + let s = x.string().to_string(); + eprintln!("<<< {}", s); + ws::Message::text(s) +} + fn new_box_with(new_fn: impl FnOnce(*mut T) -> T) -> Box { let b = Box::new(MaybeUninit::::uninit()); let p = Box::into_raw(b) as *mut T; From f58fa37d5718b3b40e5523fe327041017dda3457 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 25 Mar 2020 17:08:04 -0400 Subject: [PATCH 17/41] Undo DenoInspectorInner --- cli/inspector.rs | 39 ++++++++++++++++++--------------------- cli/worker.rs | 2 +- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 9536950a82fd0c..2d7cbebf9f55a2 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -86,7 +86,7 @@ impl InspectorServer { pub fn add_inspector( &self, isolate: &mut deno_core::Isolate, - ) -> DenoInspector { + ) -> Box { let deno_core::Isolate { v8_isolate, global_context, @@ -250,10 +250,8 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { futures::future::join(msg_handler, web_handler).await; } -pub struct DenoInspector(Box); - #[repr(C)] -struct DenoInspectorInner { +pub struct DenoInspector { client: v8::inspector::V8InspectorClientBase, inspector: v8::UniqueRef, terminated: bool, @@ -275,7 +273,7 @@ impl Future for DenoInspector { // let recv_future = inner.recv_future.as_mut().unwrap().as_mut(); // recv_future.poll(cx) - match deno_inspector.0.inspector_rx.poll_recv(cx) { + match deno_inspector.inspector_rx.poll_recv(cx) { Poll::Pending => Poll::Pending, Poll::Ready(Some(msg)) => { match msg { @@ -289,7 +287,7 @@ impl Future for DenoInspector { InspectorMsg::WsIncoming { session_uuid, msg } => { println!(">>> {}", msg.to_str().unwrap()); if let Some(deno_session) = - deno_inspector.0.sessions.get_mut(&session_uuid) + deno_inspector.sessions.get_mut(&session_uuid) { deno_session.dispatch_protocol_message(msg) } else { @@ -309,12 +307,12 @@ impl DenoInspector { scope: &mut P, context: v8::Local, inspector_rx: InspectorRx, - ) -> Self + ) -> Box where P: v8::InIsolate, { - let mut deno_inspector = new_box_with(|address| DenoInspectorInner { - client: v8::inspector::V8InspectorClientBase::new::(), + let mut deno_inspector = new_box_with(|address| Self { + client: v8::inspector::V8InspectorClientBase::new::(), // TODO(piscisaureus): V8Inspector::create() should require that // the 'client' argument cannot move. inspector: v8::inspector::V8Inspector::create(scope, unsafe { @@ -332,16 +330,16 @@ impl DenoInspector { &empty_view, ); - DenoInspector(deno_inspector) + deno_inspector } pub fn connect(&mut self, session_uuid: Uuid, ws_tx: WsTx) { - let session = DenoInspectorSession::new(&mut self.0.inspector, ws_tx); - self.0.sessions.insert(session_uuid, session); + let session = DenoInspectorSession::new(&mut self.inspector, ws_tx); + self.sessions.insert(session_uuid, session); } } -impl v8::inspector::V8InspectorClientImpl for DenoInspectorInner { +impl v8::inspector::V8InspectorClientImpl for DenoInspector { fn base(&self) -> &v8::inspector::V8InspectorClientBase { &self.client } @@ -350,19 +348,18 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspectorInner { &mut self.client } - fn run_message_loop_on_pause(&mut self, _context_group_id: i32) { - // while !self.terminated { - // self.deno_isolate.inspector_block_recv(); - // } - todo!() + fn run_message_loop_on_pause(&mut self, context_group_id: i32) { + assert_eq!(context_group_id, CONTEXT_GROUP_ID); + eprintln!("TODO run_message_loop_on_pause"); } fn quit_message_loop_on_pause(&mut self) { - todo!() + eprintln!("TODO quit_message_loop_on_pause"); } - fn run_if_waiting_for_debugger(&mut self, _context_group_id: i32) { - todo!() + fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) { + assert_eq!(context_group_id, CONTEXT_GROUP_ID); + eprintln!("TODO run_if_waiting_for_debugger"); } } diff --git a/cli/worker.rs b/cli/worker.rs index 774f5140c74bd1..aa119b10f6eb90 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -100,7 +100,7 @@ pub struct Worker { pub waker: AtomicWaker, pub(crate) internal_channels: WorkerChannelsInternal, external_channels: WorkerHandle, - inspector: Option, + inspector: Option>, } impl Worker { From a139def890c0a39df1dcd64d71c265979201fa8f Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 26 Mar 2020 00:00:13 -0400 Subject: [PATCH 18/41] add some hints from node --- cli/inspector.rs | 108 ++++++++++++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 44 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 2d7cbebf9f55a2..b875c9ab07674b 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -258,48 +258,8 @@ pub struct DenoInspector { sessions: HashMap>, // recv_future: Option>>>, inspector_rx: InspectorRx, -} - -/// DenoInspector implements a Future so that it can poll for incoming messages -/// from the WebSocket server. Since a Worker ownes a DenoInspector, and because -/// a Worker is a Future too, Worker::poll will call this. -impl Future for DenoInspector { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let deno_inspector = self.get_mut(); - //inner.get_messages().boxed_local(); - // - // let recv_future = inner.recv_future.as_mut().unwrap().as_mut(); - // recv_future.poll(cx) - - match deno_inspector.inspector_rx.poll_recv(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(Some(msg)) => { - match msg { - InspectorMsg::WsConnection { - session_uuid, - ws_tx, - } => { - deno_inspector.connect(session_uuid, ws_tx); - println!("Got new ws connection in DenoInspector {}", session_uuid); - } - InspectorMsg::WsIncoming { session_uuid, msg } => { - println!(">>> {}", msg.to_str().unwrap()); - if let Some(deno_session) = - deno_inspector.sessions.get_mut(&session_uuid) - { - deno_session.dispatch_protocol_message(msg) - } else { - eprintln!("Unknown session {}. msg {:?}", session_uuid, msg); - } - } - }; - Poll::Ready(()) - } - _ => todo!("fixme"), - } - } + waiting_for_resume: bool, + waiting_for_frontend: bool, } impl DenoInspector { @@ -321,6 +281,8 @@ impl DenoInspector { terminated: false, sessions: HashMap::new(), inspector_rx, + waiting_for_resume: false, + waiting_for_frontend: false, }); let empty_view = v8::inspector::StringView::empty(); @@ -337,6 +299,20 @@ impl DenoInspector { let session = DenoInspectorSession::new(&mut self.inspector, ws_tx); self.sessions.insert(session_uuid, session); } + + fn has_connected_sessions(&self) -> bool { + !self.sessions.is_empty() + } + + fn should_run_message_loop(&self) -> bool { + if self.waiting_for_frontend { + true + } else if self.waiting_for_resume { + self.has_connected_sessions() + } else { + false + } + } } impl v8::inspector::V8InspectorClientImpl for DenoInspector { @@ -351,15 +327,59 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspector { fn run_message_loop_on_pause(&mut self, context_group_id: i32) { assert_eq!(context_group_id, CONTEXT_GROUP_ID); eprintln!("TODO run_message_loop_on_pause"); + // how to get context? + // TODO self.poll(context); } fn quit_message_loop_on_pause(&mut self) { - eprintln!("TODO quit_message_loop_on_pause"); + self.waiting_for_resume = false; } fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) { assert_eq!(context_group_id, CONTEXT_GROUP_ID); - eprintln!("TODO run_if_waiting_for_debugger"); + self.waiting_for_frontend = true; + } +} + +/// DenoInspector implements a Future so that it can poll for incoming messages +/// from the WebSocket server. Since a Worker ownes a DenoInspector, and because +/// a Worker is a Future too, Worker::poll will call this. +impl Future for DenoInspector { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let deno_inspector = self.get_mut(); + + // if !deno_inspector.should_run_message_loop() { + // return Poll::Ready(()); + // } + + match deno_inspector.inspector_rx.poll_recv(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Some(msg)) => { + match msg { + InspectorMsg::WsConnection { + session_uuid, + ws_tx, + } => { + deno_inspector.connect(session_uuid, ws_tx); + println!("Got new ws connection in DenoInspector {}", session_uuid); + } + InspectorMsg::WsIncoming { session_uuid, msg } => { + println!(">>> {}", msg.to_str().unwrap()); + if let Some(deno_session) = + deno_inspector.sessions.get_mut(&session_uuid) + { + deno_session.dispatch_protocol_message(msg) + } else { + eprintln!("Unknown session {}. msg {:?}", session_uuid, msg); + } + } + }; + Poll::Ready(()) + } + _ => todo!("fixme"), + } } } From 42243e44af414a3bd32e358c9c0d5ec3d96973c6 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 26 Mar 2020 00:03:47 -0400 Subject: [PATCH 19/41] clean up --- cli/inspector.rs | 2 +- cli/lib.rs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index b875c9ab07674b..10f07f8865454a 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -137,7 +137,7 @@ fn websocket_debugger_url(address: SocketAddrV4, uuid: &Uuid) -> String { format!("ws://{}:{}/ws/{}", address.ip(), address.port(), uuid) } -async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) -> () { +async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { let inspector_map = HashMap::::new(); let inspector_map = Arc::new(std::sync::Mutex::new(inspector_map)); diff --git a/cli/lib.rs b/cli/lib.rs index b00d8af946424a..7b5b56ba234c3c 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -373,10 +373,6 @@ async fn run_command(flags: Flags, script: String) -> Result<(), ErrBox> { std::process::exit(11); } } - - // TODO This should probably be done in impl Drop for InspectorServer, but it - // seems GlobalState is being leaked and the drop is never called. - // global_state.inspector_server.take(); Ok(()) } From ee61cdcd0d3117a56dd2db5ae1c8ef58e713b68a Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 26 Mar 2020 00:15:13 -0400 Subject: [PATCH 20/41] clippy --- cli/global_state.rs | 2 +- cli/inspector.rs | 19 +++++++++---------- cli/worker.rs | 18 ++++++------------ 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/cli/global_state.rs b/cli/global_state.rs index 6985adc222d5c2..909f598f9e20bf 100644 --- a/cli/global_state.rs +++ b/cli/global_state.rs @@ -85,7 +85,7 @@ impl GlobalState { }; let inspector_server = if flags.debug { - Some(InspectorServer::new()) + Some(InspectorServer::default()) } else { None }; diff --git a/cli/inspector.rs b/cli/inspector.rs index 10f07f8865454a..cb7a83537a77ed 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -32,11 +32,6 @@ enum ServerMsg { }, } -type ServerMsgTx = mpsc::UnboundedSender; -type ServerMsgRx = mpsc::UnboundedReceiver; - -type WsTx = futures::stream::SplitSink; - pub enum InspectorMsg { WsConnection { session_uuid: Uuid, @@ -48,6 +43,9 @@ pub enum InspectorMsg { }, } +type ServerMsgTx = mpsc::UnboundedSender; +type ServerMsgRx = mpsc::UnboundedReceiver; +type WsTx = futures::stream::SplitSink; /// Owned by the web socket server type InspectorTx = mpsc::UnboundedSender; /// Owned by the isolate/worker @@ -68,7 +66,8 @@ pub struct InspectorServer { } impl InspectorServer { - pub fn new() -> Self { + /// Starts a DevTools Inspector server on port 127.0.0.1:9229 + pub fn default() -> Self { let address = "127.0.0.1:9229".parse::().unwrap(); let (server_msg_tx, server_msg_rx) = mpsc::unbounded_channel::(); let thread_handle = std::thread::spawn(move || { @@ -160,7 +159,7 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { isolate_handle, }, ) - .map(|_| panic!("UUID already in map")); + .or_else(|| panic!("UUID already in map")); } }; } @@ -215,12 +214,12 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { }); let inspector_map_ = inspector_map.clone(); - let address_ = address.clone(); let json_list = warp::path("json") .map(move || { - let json_values: Vec = inspector_map_.lock().unwrap().iter().map(|(uuid, _)| { - let url = websocket_debugger_url(address_, uuid); + let g = inspector_map_.lock().unwrap(); + let json_values: Vec = g.iter().map(|(uuid, _)| { + let url = websocket_debugger_url(address, uuid); json!({ "description": "deno", "devtoolsFrontendUrl": format!("chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws={}", url), diff --git a/cli/worker.rs b/cli/worker.rs index aa119b10f6eb90..994f22f04afc80 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -1,7 +1,4 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -#![allow(dead_code)] - use crate::fmt_errors::JSError; use crate::ops; use crate::state::State; @@ -108,18 +105,15 @@ impl Worker { let loader = Rc::new(state.clone()); let mut isolate = deno_core::EsIsolate::new(loader, startup_data, false); - let global_state_ = state.borrow().global_state.clone(); + let global_state = state.borrow().global_state.clone(); - let inspector = - if let Some(inspector_server) = global_state_.inspector_server.as_ref() { - let inspector = inspector_server.add_inspector(&mut *isolate); - Some(inspector) - } else { - None - }; + let inspector = global_state + .inspector_server + .as_ref() + .map(|s| s.add_inspector(&mut *isolate)); isolate.set_js_error_create_fn(move |core_js_error| { - JSError::create(core_js_error, &global_state_.ts_compiler) + JSError::create(core_js_error, &global_state.ts_compiler) }); let (internal_channels, external_channels) = create_channels(); From 333fca0d0eb743ccad3cf13aa3f77f41cdcd2d48 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 26 Mar 2020 00:26:50 -0400 Subject: [PATCH 21/41] cleanup --- cli/inspector.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index cb7a83537a77ed..1893ceb25c173a 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -51,7 +51,7 @@ type InspectorTx = mpsc::UnboundedSender; /// Owned by the isolate/worker type InspectorRx = mpsc::UnboundedReceiver; -// Stored in a UUID hashmap, used by WS server +/// Stored in a UUID hashmap, used by WS server. Clonable. #[derive(Clone)] struct InspectorInfo { inspector_tx: InspectorTx, @@ -149,17 +149,16 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { inspector_tx, isolate_handle, } => { - inspector_map_ - .lock() - .unwrap() - .insert( - uuid, - InspectorInfo { - inspector_tx, - isolate_handle, - }, - ) - .or_else(|| panic!("UUID already in map")); + let existing = inspector_map_.lock().unwrap().insert( + uuid, + InspectorInfo { + inspector_tx, + isolate_handle, + }, + ); + if existing.is_some() { + panic!("UUID already in map"); + } } }; } @@ -255,7 +254,6 @@ pub struct DenoInspector { inspector: v8::UniqueRef, terminated: bool, sessions: HashMap>, - // recv_future: Option>>>, inspector_rx: InspectorRx, waiting_for_resume: bool, waiting_for_frontend: bool, @@ -377,7 +375,7 @@ impl Future for DenoInspector { }; Poll::Ready(()) } - _ => todo!("fixme"), + Poll::Ready(None) => Poll::Ready(()), } } } From 885f0d4b82548c2cfd933a67058a810600abf51b Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 26 Mar 2020 01:53:39 -0400 Subject: [PATCH 22/41] wip --- cli/inspector.rs | 59 +++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 1893ceb25c173a..8edb7cefcc8ed9 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -324,8 +324,11 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspector { fn run_message_loop_on_pause(&mut self, context_group_id: i32) { assert_eq!(context_group_id, CONTEXT_GROUP_ID); eprintln!("TODO run_message_loop_on_pause"); - // how to get context? - // TODO self.poll(context); + // TODO need to synchronously run self.poll until quit_message_loop_on_pause + // is called. + // Can we do something like this? + self.waiting_for_resume = true; + let _ = futures::executor::block_on(self); } fn quit_message_loop_on_pause(&mut self) { @@ -351,31 +354,35 @@ impl Future for DenoInspector { // return Poll::Ready(()); // } - match deno_inspector.inspector_rx.poll_recv(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(Some(msg)) => { - match msg { - InspectorMsg::WsConnection { - session_uuid, - ws_tx, - } => { - deno_inspector.connect(session_uuid, ws_tx); - println!("Got new ws connection in DenoInspector {}", session_uuid); - } - InspectorMsg::WsIncoming { session_uuid, msg } => { - println!(">>> {}", msg.to_str().unwrap()); - if let Some(deno_session) = - deno_inspector.sessions.get_mut(&session_uuid) - { - deno_session.dispatch_protocol_message(msg) - } else { - eprintln!("Unknown session {}. msg {:?}", session_uuid, msg); + loop { + match deno_inspector.inspector_rx.poll_recv(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(None) => return Poll::Ready(()), + Poll::Ready(Some(msg)) => { + match msg { + InspectorMsg::WsConnection { + session_uuid, + ws_tx, + } => { + deno_inspector.connect(session_uuid, ws_tx); + println!( + "Got new ws connection in DenoInspector {}", + session_uuid + ); } - } - }; - Poll::Ready(()) + InspectorMsg::WsIncoming { session_uuid, msg } => { + println!(">>> {}", msg.to_str().unwrap()); + if let Some(deno_session) = + deno_inspector.sessions.get_mut(&session_uuid) + { + deno_session.dispatch_protocol_message(msg) + } else { + eprintln!("Unknown session {}. msg {:?}", session_uuid, msg); + } + } + }; + } } - Poll::Ready(None) => Poll::Ready(()), } } } @@ -444,7 +451,7 @@ impl v8::inspector::ChannelImpl for DenoInspectorSession { } fn flush_protocol_notifications(&mut self) { - todo!() + eprintln!("TODO flush_protocol_notifications"); } } From 877b6d1218f0734b665ef904a0875c787fdd58ad Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Thu, 26 Mar 2020 18:05:05 +0100 Subject: [PATCH 23/41] fix pause button --- cli/inspector.rs | 141 +++++++++++++++++++++++++++++++---------------- 1 file changed, 92 insertions(+), 49 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 8edb7cefcc8ed9..b91974eb47c325 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -23,19 +23,32 @@ use warp::Filter; const CONTEXT_GROUP_ID: i32 = 1; +/// Owned by GloalState, this channel end can be used by any isolate thread +/// to register it's inspector with the inspector server. +type ServerMsgTx = mpsc::UnboundedSender; +// Owned by the inspector server thread, used to to receive information about +// new isolates. +type ServerMsgRx = mpsc::UnboundedReceiver; // These messages can be sent from any thread to the server thread. enum ServerMsg { AddInspector { uuid: Uuid, - inspector_tx: InspectorTx, + frontend_to_inspector_tx: FrontendToInspectorTx, isolate_handle: v8::IsolateHandle, }, } -pub enum InspectorMsg { +/// Owned by the web socket server. Relays incoming websocket connections and +/// messages to the isolate/inspector thread. +type FrontendToInspectorTx = mpsc::UnboundedSender; +/// Owned by the isolate/worker. Receives incoming websocket connections and +/// messages from the inspector server thread. +type FrontendToInspectorRx = mpsc::UnboundedReceiver; +/// Messages sent over the FrontendToInspectorTx/FrontendToInspectorRx channel. +pub enum FrontendToInspectorMsg { WsConnection { session_uuid: Uuid, - ws_tx: WsTx, + session_to_frontend_tx: SessionToFrontendTx, }, WsIncoming { session_uuid: Uuid, @@ -43,18 +56,19 @@ pub enum InspectorMsg { }, } -type ServerMsgTx = mpsc::UnboundedSender; -type ServerMsgRx = mpsc::UnboundedReceiver; -type WsTx = futures::stream::SplitSink; -/// Owned by the web socket server -type InspectorTx = mpsc::UnboundedSender; -/// Owned by the isolate/worker -type InspectorRx = mpsc::UnboundedReceiver; +/// Owned by the deno inspector session, used to forward messages from the +/// inspector channel on the isolate thread to the websocket that is owned by +/// the inspector server. +type SessionToFrontendTx = mpsc::UnboundedSender; +/// Owned by the inspector server. Messages arriving on this channel, coming +/// from the inspector session on the isolate thread are forwarded over the +/// websocket to the devtools frontend. +type SessionToFrontendRx = mpsc::UnboundedReceiver; /// Stored in a UUID hashmap, used by WS server. Clonable. #[derive(Clone)] struct InspectorInfo { - inspector_tx: InspectorTx, + frontend_to_inspector_tx: FrontendToInspectorTx, isolate_handle: v8::IsolateHandle, } @@ -98,12 +112,15 @@ impl InspectorServer { let server_msg_tx = self.server_msg_tx.as_ref().unwrap().clone(); let address = self.address; - let (inspector_tx, inspector_rx) = - mpsc::unbounded_channel::(); + let (frontend_to_inspector_tx, frontend_to_inspector_rx) = + mpsc::unbounded_channel::(); let uuid = Uuid::new_v4(); - let inspector = - crate::inspector::DenoInspector::new(scope, context, inspector_rx); + let inspector = crate::inspector::DenoInspector::new( + scope, + context, + frontend_to_inspector_rx, + ); eprintln!( "Debugger listening on {}", @@ -113,7 +130,7 @@ impl InspectorServer { server_msg_tx .send(ServerMsg::AddInspector { uuid, - inspector_tx, + frontend_to_inspector_tx, isolate_handle, }) .unwrap_or_else(|_| { @@ -146,13 +163,13 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { match msg { ServerMsg::AddInspector { uuid, - inspector_tx, + frontend_to_inspector_tx, isolate_handle, } => { let existing = inspector_map_.lock().unwrap().insert( uuid, InspectorInfo { - inspector_tx, + frontend_to_inspector_tx, isolate_handle, }, ); @@ -186,29 +203,42 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { }; // send a message back so register_worker can return... - let (ws_tx, mut ws_rx) = socket.split(); + let (mut ws_tx, mut ws_rx) = socket.split(); + + let (session_to_frontend_tx, mut session_to_frontend_rx) = + mpsc::unbounded_channel::(); // Not to be confused with the WS's uuid... let session_uuid = Uuid::new_v4(); inspector_info - .inspector_tx - .send(InspectorMsg::WsConnection { - ws_tx, + .frontend_to_inspector_tx + .send(FrontendToInspectorMsg::WsConnection { + session_to_frontend_tx, session_uuid, }) .unwrap_or_else(|_| { - panic!("sending message to inspector_tx failed"); + panic!("sending message to frontend_to_inspector_tx failed"); }); - while let Some(Ok(msg)) = ws_rx.next().await { - inspector_info - .inspector_tx - .send(InspectorMsg::WsIncoming { msg, session_uuid }) - .unwrap_or_else(|_| { - panic!("sending message to inspector_tx failed"); - }); - } + let pump_to_inspector = async { + while let Some(Ok(msg)) = ws_rx.next().await { + inspector_info + .frontend_to_inspector_tx + .send(FrontendToInspectorMsg::WsIncoming { msg, session_uuid }) + .unwrap_or_else(|_| { + panic!("sending message to frontend_to_inspector_tx failed"); + }); + } + }; + + let pump_from_session = async { + while let Some(msg) = session_to_frontend_rx.next().await { + ws_tx.send(msg).await.ok(); + } + }; + + futures::future::join(pump_to_inspector, pump_from_session).await; }) }); @@ -254,16 +284,17 @@ pub struct DenoInspector { inspector: v8::UniqueRef, terminated: bool, sessions: HashMap>, - inspector_rx: InspectorRx, + frontend_to_inspector_rx: FrontendToInspectorRx, waiting_for_resume: bool, waiting_for_frontend: bool, + resuming: bool, } impl DenoInspector { pub fn new

( scope: &mut P, context: v8::Local, - inspector_rx: InspectorRx, + frontend_to_inspector_rx: FrontendToInspectorRx, ) -> Box where P: v8::InIsolate, @@ -277,9 +308,10 @@ impl DenoInspector { }), terminated: false, sessions: HashMap::new(), - inspector_rx, + frontend_to_inspector_rx, waiting_for_resume: false, waiting_for_frontend: false, + resuming: false, }); let empty_view = v8::inspector::StringView::empty(); @@ -292,8 +324,13 @@ impl DenoInspector { deno_inspector } - pub fn connect(&mut self, session_uuid: Uuid, ws_tx: WsTx) { - let session = DenoInspectorSession::new(&mut self.inspector, ws_tx); + pub fn connect( + &mut self, + session_uuid: Uuid, + session_to_frontend_tx: SessionToFrontendTx, + ) { + let session = + DenoInspectorSession::new(&mut self.inspector, session_to_frontend_tx); self.sessions.insert(session_uuid, session); } @@ -323,19 +360,23 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspector { fn run_message_loop_on_pause(&mut self, context_group_id: i32) { assert_eq!(context_group_id, CONTEXT_GROUP_ID); - eprintln!("TODO run_message_loop_on_pause"); + eprintln!("!!! run_message_loop_on_pause START"); // TODO need to synchronously run self.poll until quit_message_loop_on_pause // is called. // Can we do something like this? self.waiting_for_resume = true; let _ = futures::executor::block_on(self); + eprintln!("!!! run_message_loop_on_pause END"); } fn quit_message_loop_on_pause(&mut self) { + eprintln!("!!! quit_message_loop_on_pause"); self.waiting_for_resume = false; + self.resuming = true; } fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) { + eprintln!("!!! run_if_waiting_for_debugger"); assert_eq!(context_group_id, CONTEXT_GROUP_ID); self.waiting_for_frontend = true; } @@ -355,22 +396,26 @@ impl Future for DenoInspector { // } loop { - match deno_inspector.inspector_rx.poll_recv(cx) { + if deno_inspector.resuming { + deno_inspector.resuming = false; + return Poll::Ready(()); + } + match deno_inspector.frontend_to_inspector_rx.poll_recv(cx) { Poll::Pending => return Poll::Pending, Poll::Ready(None) => return Poll::Ready(()), Poll::Ready(Some(msg)) => { match msg { - InspectorMsg::WsConnection { + FrontendToInspectorMsg::WsConnection { session_uuid, - ws_tx, + session_to_frontend_tx, } => { - deno_inspector.connect(session_uuid, ws_tx); + deno_inspector.connect(session_uuid, session_to_frontend_tx); println!( "Got new ws connection in DenoInspector {}", session_uuid ); } - InspectorMsg::WsIncoming { session_uuid, msg } => { + FrontendToInspectorMsg::WsIncoming { session_uuid, msg } => { println!(">>> {}", msg.to_str().unwrap()); if let Some(deno_session) = deno_inspector.sessions.get_mut(&session_uuid) @@ -391,13 +436,13 @@ impl Future for DenoInspector { pub struct DenoInspectorSession { channel: v8::inspector::ChannelBase, session: v8::UniqueRef, - ws_tx: WsTx, + session_to_frontend_tx: SessionToFrontendTx, } impl DenoInspectorSession { pub fn new( inspector: &mut v8::inspector::V8Inspector, - ws_tx: WsTx, + session_to_frontend_tx: SessionToFrontendTx, ) -> Box { new_box_with(|address| { let empty_view = v8::inspector::StringView::empty(); @@ -410,7 +455,7 @@ impl DenoInspectorSession { unsafe { &mut *address }, &empty_view, ), - ws_tx, + session_to_frontend_tx, } }) } @@ -437,8 +482,7 @@ impl v8::inspector::ChannelImpl for DenoInspectorSession { message: v8::UniquePtr, ) { let ws_msg = v8_to_ws_msg(message); - let r = futures::executor::block_on(self.ws_tx.send(ws_msg)); - assert!(r.is_ok()); + self.session_to_frontend_tx.send(ws_msg).unwrap(); } fn send_notification( @@ -446,8 +490,7 @@ impl v8::inspector::ChannelImpl for DenoInspectorSession { message: v8::UniquePtr, ) { let ws_msg = v8_to_ws_msg(message); - let r = futures::executor::block_on(self.ws_tx.send(ws_msg)); - assert!(r.is_ok()); + self.session_to_frontend_tx.send(ws_msg).unwrap(); } fn flush_protocol_notifications(&mut self) { From 1cea957746dd5f305a819b526fab566ec63b4c4b Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Thu, 26 Mar 2020 19:43:42 +0100 Subject: [PATCH 24/41] Make interrupts work --- cli/inspector.rs | 194 +++++++++++++++++++++++++++-------------------- 1 file changed, 113 insertions(+), 81 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index b91974eb47c325..3ea9850613e6b9 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -31,11 +31,7 @@ type ServerMsgTx = mpsc::UnboundedSender; type ServerMsgRx = mpsc::UnboundedReceiver; // These messages can be sent from any thread to the server thread. enum ServerMsg { - AddInspector { - uuid: Uuid, - frontend_to_inspector_tx: FrontendToInspectorTx, - isolate_handle: v8::IsolateHandle, - }, + AddInspector(InspectorInfo), } /// Owned by the web socket server. Relays incoming websocket connections and @@ -65,11 +61,37 @@ type SessionToFrontendTx = mpsc::UnboundedSender; /// websocket to the devtools frontend. type SessionToFrontendRx = mpsc::UnboundedReceiver; +#[derive(Copy, Clone)] +struct DenoInspectorHandle(*mut DenoInspector); + +impl DenoInspectorHandle { + pub fn new(inspector: &mut DenoInspector) -> Self { + Self(inspector) + } + + pub unsafe fn get(&mut self) -> &mut DenoInspector { + &mut *self.0 + } + + pub fn as_raw(&self) -> *mut std::ffi::c_void { + self.0 as *mut std::ffi::c_void + } + + pub fn from_raw(ptr: *mut std::ffi::c_void) -> Self { + Self(ptr as *mut DenoInspector) + } +} + +unsafe impl Send for DenoInspectorHandle {} +unsafe impl Sync for DenoInspectorHandle {} + /// Stored in a UUID hashmap, used by WS server. Clonable. #[derive(Clone)] struct InspectorInfo { + uuid: Uuid, frontend_to_inspector_tx: FrontendToInspectorTx, isolate_handle: v8::IsolateHandle, + inspector_handle: DenoInspectorHandle, } /// Owned by GlobalState. @@ -116,7 +138,7 @@ impl InspectorServer { mpsc::unbounded_channel::(); let uuid = Uuid::new_v4(); - let inspector = crate::inspector::DenoInspector::new( + let mut inspector = crate::inspector::DenoInspector::new( scope, context, frontend_to_inspector_rx, @@ -128,11 +150,12 @@ impl InspectorServer { ); server_msg_tx - .send(ServerMsg::AddInspector { + .send(ServerMsg::AddInspector(InspectorInfo { uuid, frontend_to_inspector_tx, isolate_handle, - }) + inspector_handle: DenoInspectorHandle::new(&mut *inspector), + })) .unwrap_or_else(|_| { panic!("sending message to inspector server thread failed"); }); @@ -161,18 +184,11 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { let msg_handler = async move { while let Some(msg) = server_msg_rx.next().await { match msg { - ServerMsg::AddInspector { - uuid, - frontend_to_inspector_tx, - isolate_handle, - } => { - let existing = inspector_map_.lock().unwrap().insert( - uuid, - InspectorInfo { - frontend_to_inspector_tx, - isolate_handle, - }, - ); + ServerMsg::AddInspector(inspector_info) => { + let existing = inspector_map_ + .lock() + .unwrap() + .insert(inspector_info.uuid, inspector_info); if existing.is_some() { panic!("UUID already in map"); } @@ -221,6 +237,11 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { panic!("sending message to frontend_to_inspector_tx failed"); }); + inspector_info.isolate_handle.request_interrupt( + DenoInspector::interrupt_callback, + inspector_info.inspector_handle.as_raw(), + ); + let pump_to_inspector = async { while let Some(Ok(msg)) = ws_rx.next().await { inspector_info @@ -229,6 +250,11 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { .unwrap_or_else(|_| { panic!("sending message to frontend_to_inspector_tx failed"); }); + + inspector_info.isolate_handle.request_interrupt( + DenoInspector::interrupt_callback, + inspector_info.inspector_handle.as_raw(), + ); } }; @@ -283,7 +309,7 @@ pub struct DenoInspector { client: v8::inspector::V8InspectorClientBase, inspector: v8::UniqueRef, terminated: bool, - sessions: HashMap>, + pub sessions: HashMap>, frontend_to_inspector_rx: FrontendToInspectorRx, waiting_for_resume: bool, waiting_for_frontend: bool, @@ -334,17 +360,72 @@ impl DenoInspector { self.sessions.insert(session_uuid, session); } - fn has_connected_sessions(&self) -> bool { - !self.sessions.is_empty() + fn dispatch_frontend_to_inspector_msg( + &mut self, + msg: FrontendToInspectorMsg, + ) { + match msg { + FrontendToInspectorMsg::WsConnection { + session_uuid, + session_to_frontend_tx, + } => { + self.connect(session_uuid, session_to_frontend_tx); + println!("Got new ws connection in DenoInspector {}", session_uuid); + } + FrontendToInspectorMsg::WsIncoming { session_uuid, msg } => { + // println!(">>> {}", msg.to_str().unwrap()); + eprint!(">"); + if let Some(deno_session) = self.sessions.get_mut(&session_uuid) { + deno_session.dispatch_protocol_message(msg) + } else { + eprintln!("Unknown session {}. msg {:?}", session_uuid, msg); + } + } + }; } - fn should_run_message_loop(&self) -> bool { - if self.waiting_for_frontend { - true - } else if self.waiting_for_resume { - self.has_connected_sessions() - } else { - false + extern "C" fn interrupt_callback( + _isolate: &mut v8::Isolate, + deno_inspector_ptr: *mut std::ffi::c_void, + ) { + eprintln!("!!! isolate_interrupt_callback START"); + let mut deno_inspector_handle = + DenoInspectorHandle::from_raw(deno_inspector_ptr); + let deno_inspector = unsafe { deno_inspector_handle.get() }; + let drain_future = futures::future::poll_fn(|cx| { + while let Poll::Ready(Some(msg)) = + deno_inspector.frontend_to_inspector_rx.poll_recv(cx) + { + deno_inspector.dispatch_frontend_to_inspector_msg(msg); + } + Poll::Ready(()) + }); + futures::executor::block_on(drain_future); + eprintln!("!!! isolate_interrupt_callback END"); + } +} + +/// DenoInspector implements a Future so that it can poll for incoming messages +/// from the WebSocket server. Since a Worker ownes a DenoInspector, and because +/// a Worker is a Future too, Worker::poll will call this. +impl Future for DenoInspector { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let deno_inspector = self.get_mut(); + + loop { + if deno_inspector.resuming { + deno_inspector.resuming = false; + return Poll::Ready(()); + } + match deno_inspector.frontend_to_inspector_rx.poll_recv(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(None) => return Poll::Ready(()), + Poll::Ready(Some(msg)) => { + deno_inspector.dispatch_frontend_to_inspector_msg(msg) + } + } } } } @@ -382,56 +463,6 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspector { } } -/// DenoInspector implements a Future so that it can poll for incoming messages -/// from the WebSocket server. Since a Worker ownes a DenoInspector, and because -/// a Worker is a Future too, Worker::poll will call this. -impl Future for DenoInspector { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let deno_inspector = self.get_mut(); - - // if !deno_inspector.should_run_message_loop() { - // return Poll::Ready(()); - // } - - loop { - if deno_inspector.resuming { - deno_inspector.resuming = false; - return Poll::Ready(()); - } - match deno_inspector.frontend_to_inspector_rx.poll_recv(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(None) => return Poll::Ready(()), - Poll::Ready(Some(msg)) => { - match msg { - FrontendToInspectorMsg::WsConnection { - session_uuid, - session_to_frontend_tx, - } => { - deno_inspector.connect(session_uuid, session_to_frontend_tx); - println!( - "Got new ws connection in DenoInspector {}", - session_uuid - ); - } - FrontendToInspectorMsg::WsIncoming { session_uuid, msg } => { - println!(">>> {}", msg.to_str().unwrap()); - if let Some(deno_session) = - deno_inspector.sessions.get_mut(&session_uuid) - { - deno_session.dispatch_protocol_message(msg) - } else { - eprintln!("Unknown session {}. msg {:?}", session_uuid, msg); - } - } - }; - } - } - } - } -} - /// sub-class of v8::inspector::Channel pub struct DenoInspectorSession { channel: v8::inspector::ChannelBase, @@ -504,7 +535,8 @@ fn v8_to_ws_msg( ) -> ws::Message { let mut x = message.unwrap(); let s = x.string().to_string(); - eprintln!("<<< {}", s); + //eprintln!("<<< {}", s); + print!("<"); ws::Message::text(s) } From 50500f0a217eebf27f5888436d9768c7356ae45c Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Fri, 27 Mar 2020 01:55:27 +0100 Subject: [PATCH 25/41] WIP --- cli/inspector.rs | 122 +++++++++++++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 47 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 3ea9850613e6b9..68222fa64987ac 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -13,9 +13,8 @@ use std::ptr; use std::sync::Arc; use std::task::Context; use std::task::Poll; - -use tokio; use tokio::sync::mpsc; +use tokio::sync::mpsc::error::TryRecvError; use uuid::Uuid; use warp; use warp::filters::ws; @@ -293,7 +292,7 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { warp::reply::json(&json!({ "Browser": format!("Deno/{}", crate::version::DENO), // TODO upgrade to 1.3? https://chromedevtools.github.io/devtools-protocol/ - "Protocol-Version": "1.1", + "Protocol-Version": "1.3", "V8-Version": crate::version::v8(), })) }); @@ -304,16 +303,20 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { futures::future::join(msg_handler, web_handler).await; } +enum ExecutionState { + WaitingForDebugger, + Paused, + Running, +} + #[repr(C)] pub struct DenoInspector { client: v8::inspector::V8InspectorClientBase, inspector: v8::UniqueRef, - terminated: bool, pub sessions: HashMap>, frontend_to_inspector_rx: FrontendToInspectorRx, - waiting_for_resume: bool, - waiting_for_frontend: bool, - resuming: bool, + frontend_to_inspector_pump_active: bool, + executing_state: ExecutionState, } impl DenoInspector { @@ -332,12 +335,10 @@ impl DenoInspector { inspector: v8::inspector::V8Inspector::create(scope, unsafe { &mut *address }), - terminated: false, sessions: HashMap::new(), frontend_to_inspector_rx, - waiting_for_resume: false, - waiting_for_frontend: false, - resuming: false, + frontend_to_inspector_pump_active: false, + executing_state: ExecutionState::Running, }); let empty_view = v8::inspector::StringView::empty(); @@ -373,8 +374,8 @@ impl DenoInspector { println!("Got new ws connection in DenoInspector {}", session_uuid); } FrontendToInspectorMsg::WsIncoming { session_uuid, msg } => { - // println!(">>> {}", msg.to_str().unwrap()); - eprint!(">"); + eprintln!(">>> {}", msg.to_str().unwrap()); + //eprint!(">"); if let Some(deno_session) = self.sessions.get_mut(&session_uuid) { deno_session.dispatch_protocol_message(msg) } else { @@ -384,6 +385,60 @@ impl DenoInspector { }; } + fn pump_frontend_to_inspector_messages( + &mut self, + mut maybe_cx: Option<&mut Context>, + ) -> Poll<()> { + use ExecutionState::*; + use Poll::*; + use TryRecvError::*; + + if self.frontend_to_inspector_pump_active { + eprintln!("Not recursively entering pump."); + return Poll::Pending; // TODO: makes little sense. + } + self.frontend_to_inspector_pump_active = true; + + eprintln!("Enter pump."); + + let result = loop { + let msg = match self.executing_state { + Running => match maybe_cx { + Some(ref mut cx) => match self.frontend_to_inspector_rx.poll_recv(cx) + { + Ready(Some(msg)) => msg, + Ready(None) => break Ready(()), + Pending => break Pending, + }, + None => match self.frontend_to_inspector_rx.try_recv() { + Ok(msg) => msg, + Err(Closed) => break Ready(()), + Err(Empty) => break Pending, + }, + }, + WaitingForDebugger | Paused => { + // Note: this should theoretically not be possible because executors + // cannot be nested. It seems to work because the 'outer' executor + // is from tokio while the executor created here comes from the + // futures create. Nonetheless it would be nice to have a find a + // clean solution for it. + match futures::executor::block_on(futures::future::poll_fn(|cx| { + self.frontend_to_inspector_rx.poll_recv(cx) + })) { + Some(msg) => msg, + None => break Ready(()), + } + } + }; + self.dispatch_frontend_to_inspector_msg(msg); + }; + + eprintln!("Exit pump."); + + self.frontend_to_inspector_pump_active = false; + result + } + extern "C" fn interrupt_callback( _isolate: &mut v8::Isolate, deno_inspector_ptr: *mut std::ffi::c_void, @@ -392,15 +447,7 @@ impl DenoInspector { let mut deno_inspector_handle = DenoInspectorHandle::from_raw(deno_inspector_ptr); let deno_inspector = unsafe { deno_inspector_handle.get() }; - let drain_future = futures::future::poll_fn(|cx| { - while let Poll::Ready(Some(msg)) = - deno_inspector.frontend_to_inspector_rx.poll_recv(cx) - { - deno_inspector.dispatch_frontend_to_inspector_msg(msg); - } - Poll::Ready(()) - }); - futures::executor::block_on(drain_future); + let _ = deno_inspector.pump_frontend_to_inspector_messages(None); eprintln!("!!! isolate_interrupt_callback END"); } } @@ -413,20 +460,7 @@ impl Future for DenoInspector { fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let deno_inspector = self.get_mut(); - - loop { - if deno_inspector.resuming { - deno_inspector.resuming = false; - return Poll::Ready(()); - } - match deno_inspector.frontend_to_inspector_rx.poll_recv(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(None) => return Poll::Ready(()), - Poll::Ready(Some(msg)) => { - deno_inspector.dispatch_frontend_to_inspector_msg(msg) - } - } - } + deno_inspector.pump_frontend_to_inspector_messages(Some(cx)) } } @@ -442,24 +476,18 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspector { fn run_message_loop_on_pause(&mut self, context_group_id: i32) { assert_eq!(context_group_id, CONTEXT_GROUP_ID); eprintln!("!!! run_message_loop_on_pause START"); - // TODO need to synchronously run self.poll until quit_message_loop_on_pause - // is called. - // Can we do something like this? - self.waiting_for_resume = true; - let _ = futures::executor::block_on(self); - eprintln!("!!! run_message_loop_on_pause END"); + self.executing_state = ExecutionState::Paused; } fn quit_message_loop_on_pause(&mut self) { eprintln!("!!! quit_message_loop_on_pause"); - self.waiting_for_resume = false; - self.resuming = true; + self.executing_state = ExecutionState::Running; } fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) { eprintln!("!!! run_if_waiting_for_debugger"); assert_eq!(context_group_id, CONTEXT_GROUP_ID); - self.waiting_for_frontend = true; + self.executing_state = ExecutionState::Running; } } @@ -535,8 +563,8 @@ fn v8_to_ws_msg( ) -> ws::Message { let mut x = message.unwrap(); let s = x.string().to_string(); - //eprintln!("<<< {}", s); - print!("<"); + eprintln!("<<< {}", s); + //eprint!("<"); ws::Message::text(s) } From ed4c7d3984ab4bad9a3836313a3e9ba488e3aa7c Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 26 Mar 2020 20:18:47 -0400 Subject: [PATCH 26/41] inspector1 test working --- Cargo.lock | 1 + cli/Cargo.toml | 3 +++ cli/inspector.rs | 2 ++ cli/tests/inspector1.js | 3 +++ cli/tests/integration_tests.rs | 38 ++++++++++++++++++++++++++++++++++ 5 files changed, 47 insertions(+) create mode 100644 cli/tests/inspector1.js diff --git a/Cargo.lock b/Cargo.lock index b801e113483d0e..bbd8f89a0fe25e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -498,6 +498,7 @@ dependencies = [ "termcolor", "tokio", "tokio-rustls", + "tokio-tungstenite", "url 2.1.1", "utime", "uuid", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 8c9fe95edc78d5..a390338554d801 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -75,6 +75,9 @@ nix = "0.14" # rustyline depends on 0.14, to avoid duplicates we do too. [dev-dependencies] os_pipe = "0.9.1" +# Used for testing. Keep in-sync with warp. +tokio-tungstenite = { version = "0.10", features = ["connect"] } + [target.'cfg(unix)'.dev-dependencies] pty = "0.2.2" diff --git a/cli/inspector.rs b/cli/inspector.rs index 68222fa64987ac..d6e14405196e70 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -1,3 +1,5 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + #![allow(dead_code)] use crate::futures::SinkExt; diff --git a/cli/tests/inspector1.js b/cli/tests/inspector1.js new file mode 100644 index 00000000000000..8d5472b2b7aac8 --- /dev/null +++ b/cli/tests/inspector1.js @@ -0,0 +1,3 @@ +setInterval(() => { + console.log("hello"); +}, 1000) diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index ed516056524fca..0f8256ff12bb9f 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1941,6 +1941,44 @@ fn test_permissions_net_listen_allow_localhost() { assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); } +fn extract_ws_url_from_stderr( + stderr: &mut std::process::ChildStderr, +) -> url::Url { + use std::io::BufRead; + let mut stderr = std::io::BufReader::new(stderr); + let mut stderr_first_line = String::from(""); + let _ = stderr.read_line(&mut stderr_first_line).unwrap(); + assert!(stderr_first_line.starts_with("Debugger listening on ")); + let v: Vec<_> = stderr_first_line.match_indices("ws:").collect(); + assert_eq!(v.len(), 1); + let ws_url_index = v[0].0; + let ws_url = &stderr_first_line[ws_url_index..]; + url::Url::parse(ws_url).unwrap() +} + +#[tokio::test] +async fn inspector1() { + let script = deno::test_util::root_path() + .join("cli") + .join("tests") + .join("inspector1.js"); + let mut child = util::deno_cmd() + .arg("run") + .arg("--debug") + .arg(script) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let ws_url = extract_ws_url_from_stderr(child.stderr.as_mut().unwrap()); + // We use tokio_tungstenite as a websocket client because warp (which is + // a dependency of Deno) uses it. + let (_socket, response) = tokio_tungstenite::connect_async(ws_url) + .await + .expect("Can't connect"); + assert_eq!("101 Switching Protocols", response.status().to_string()); + child.kill().unwrap(); +} + mod util { use deno::colors::strip_ansi_codes; pub use deno::test_util::*; From 5af0c644d154bc04fcc7ce6b76704618cb351587 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 26 Mar 2020 22:59:06 -0400 Subject: [PATCH 27/41] clippy --- cli/inspector.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index d6e14405196e70..0bd258b3bc45b9 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -74,7 +74,7 @@ impl DenoInspectorHandle { &mut *self.0 } - pub fn as_raw(&self) -> *mut std::ffi::c_void { + pub fn as_raw(self) -> *mut std::ffi::c_void { self.0 as *mut std::ffi::c_void } From 6bd8c5930e8b3526430e3efc4045744ebab49d2a Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 26 Mar 2020 23:56:16 -0400 Subject: [PATCH 28/41] use --inspect flag instead of --debug --- cli/flags.rs | 98 ++++++++++++++++++++++++++++++---- cli/global_state.rs | 6 ++- cli/inspector.rs | 8 +-- cli/tests/inspector1.js | 2 +- cli/tests/integration_tests.rs | 2 +- 5 files changed, 98 insertions(+), 18 deletions(-) diff --git a/cli/flags.rs b/cli/flags.rs index 1963f6def2a5b0..4f55d69efa1840 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -101,7 +101,8 @@ pub struct Flags { pub no_prompts: bool, pub no_remote: bool, pub cached_only: bool, - pub debug: bool, + pub inspect: Option, + pub inspect_brk: Option, pub seed: Option, pub v8_flags: Option>, @@ -475,15 +476,12 @@ fn run_test_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { no_remote_arg_parse(flags, matches); permission_args_parse(flags, matches); ca_file_arg_parse(flags, matches); + inspect_arg_parse(flags, matches); if matches.is_present("cached-only") { flags.cached_only = true; } - if matches.is_present("debug") { - flags.debug = true; - } - if matches.is_present("seed") { let seed_string = matches.value_of("seed").unwrap(); let seed = seed_string.parse::().unwrap(); @@ -830,7 +828,7 @@ fn permission_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { } fn run_test_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { - permission_args(app) + permission_args(inspect_args(app)) .arg(importmap_arg()) .arg(reload_arg()) .arg(config_arg()) @@ -844,11 +842,6 @@ fn run_test_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { .long("cached-only") .help("Require that remote dependencies are already cached"), ) - .arg( - Arg::with_name("debug") - .long("debug") - .help("Enable debugger and pause on first statement"), - ) .arg( Arg::with_name("seed") .long("seed") @@ -966,6 +959,54 @@ fn ca_file_arg_parse(flags: &mut Flags, matches: &clap::ArgMatches) { flags.ca_file = matches.value_of("cert").map(ToOwned::to_owned); } +fn inspect_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { + app + .arg( + Arg::with_name("inspect") + .long("inspect") + .value_name("HOST:PORT") + .help("activate inspector on host:port (default: 127.0.0.1:9229)") + .min_values(0) + .max_values(1) + .require_equals(true) + .takes_value(true), + ) + .arg( + Arg::with_name("inspect-brk") + .long("inspect-brk") + .value_name("HOST:PORT") + .help( + "activate inspector on host:port and break at start of user script", + ) + .min_values(0) + .max_values(1) + .require_equals(true) + .takes_value(true), + ) +} + +fn inspect_arg_parse(flags: &mut Flags, matches: &clap::ArgMatches) { + const DEFAULT: &str = "127.0.0.1:9229"; + flags.inspect = if matches.is_present("inspect") { + if let Some(host) = matches.value_of("inspect") { + Some(host.to_string()) + } else { + Some(DEFAULT.to_string()) + } + } else { + None + }; + flags.inspect_brk = if matches.is_present("inspect-brk") { + if let Some(host) = matches.value_of("inspect-brk") { + Some(host.to_string()) + } else { + Some(DEFAULT.to_string()) + } + } else { + None + }; +} + fn reload_arg<'a, 'b>() -> Arg<'a, 'b> { Arg::with_name("reload") .short("r") @@ -2337,3 +2378,38 @@ fn repl_with_cafile() { } ); } + +#[test] +fn inspect_default_host() { + let r = flags_from_vec_safe(svec!["deno", "run", "--inspect", "foo.js"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Run { + script: "foo.js".to_string(), + }, + inspect: Some("127.0.0.1:9229".to_string()), + ..Flags::default() + } + ); +} + +#[test] +fn inspect_custom_host() { + let r = flags_from_vec_safe(svec![ + "deno", + "run", + "--inspect=deno.land:80", + "foo.js" + ]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Run { + script: "foo.js".to_string(), + }, + inspect: Some("deno.land:80".to_string()), + ..Flags::default() + } + ); +} diff --git a/cli/global_state.rs b/cli/global_state.rs index 909f598f9e20bf..001c3f55fb96e6 100644 --- a/cli/global_state.rs +++ b/cli/global_state.rs @@ -84,8 +84,10 @@ impl GlobalState { None }; - let inspector_server = if flags.debug { - Some(InspectorServer::default()) + let inspector_server = if let Some(ref host) = flags.inspect { + Some(InspectorServer::new(host, false)) + } else if let Some(ref host) = flags.inspect_brk { + Some(InspectorServer::new(host, true)) } else { None }; diff --git a/cli/inspector.rs b/cli/inspector.rs index 0bd258b3bc45b9..1680345975c0f8 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -103,9 +103,11 @@ pub struct InspectorServer { } impl InspectorServer { - /// Starts a DevTools Inspector server on port 127.0.0.1:9229 - pub fn default() -> Self { - let address = "127.0.0.1:9229".parse::().unwrap(); + pub fn new(host: &str, brk: bool) -> Self { + if brk { + todo!("--inspect-brk not yet supported"); + } + let address = host.parse::().unwrap(); let (server_msg_tx, server_msg_rx) = mpsc::unbounded_channel::(); let thread_handle = std::thread::spawn(move || { crate::tokio_util::run_basic(server(address, server_msg_rx)); diff --git a/cli/tests/inspector1.js b/cli/tests/inspector1.js index 8d5472b2b7aac8..5cb059def636d6 100644 --- a/cli/tests/inspector1.js +++ b/cli/tests/inspector1.js @@ -1,3 +1,3 @@ setInterval(() => { console.log("hello"); -}, 1000) +}, 1000); diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 0f8256ff12bb9f..db0bb1d142ed27 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1964,7 +1964,7 @@ async fn inspector1() { .join("inspector1.js"); let mut child = util::deno_cmd() .arg("run") - .arg("--debug") + .arg("--inspect") .arg(script) .stderr(std::process::Stdio::piped()) .spawn() From 3251003c07f7ea9f34c29ee390183a601c2da833 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 27 Mar 2020 00:33:36 -0400 Subject: [PATCH 29/41] debug --- cli/tests/integration_tests.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index db0bb1d142ed27..f886cd036e44ad 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1957,7 +1957,7 @@ fn extract_ws_url_from_stderr( } #[tokio::test] -async fn inspector1() { +async fn inspector_connect() { let script = deno::test_util::root_path() .join("cli") .join("tests") @@ -1970,6 +1970,7 @@ async fn inspector1() { .spawn() .unwrap(); let ws_url = extract_ws_url_from_stderr(child.stderr.as_mut().unwrap()); + println!("ws_url {}", ws_url); // We use tokio_tungstenite as a websocket client because warp (which is // a dependency of Deno) uses it. let (_socket, response) = tokio_tungstenite::connect_async(ws_url) From ff21fa773463af863b24d5819008ab04a207a67a Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 27 Mar 2020 00:41:49 -0400 Subject: [PATCH 30/41] remove debug output --- cli/inspector.rs | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 1680345975c0f8..9eb58d3df6b165 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -211,7 +211,6 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { if let Ok(uuid) = Uuid::parse_str(&uuid) { let g = inspector_map__.lock().unwrap(); if let Some(inspector_info) = g.get(&uuid) { - println!("ws connection {}", uuid); inspector_info.clone() } else { return; @@ -375,15 +374,15 @@ impl DenoInspector { session_to_frontend_tx, } => { self.connect(session_uuid, session_to_frontend_tx); - println!("Got new ws connection in DenoInspector {}", session_uuid); } FrontendToInspectorMsg::WsIncoming { session_uuid, msg } => { - eprintln!(">>> {}", msg.to_str().unwrap()); - //eprint!(">"); if let Some(deno_session) = self.sessions.get_mut(&session_uuid) { deno_session.dispatch_protocol_message(msg) } else { - eprintln!("Unknown session {}. msg {:?}", session_uuid, msg); + eprintln!( + "Unknown inspector session {}. msg {:?}", + session_uuid, msg + ); } } }; @@ -398,13 +397,10 @@ impl DenoInspector { use TryRecvError::*; if self.frontend_to_inspector_pump_active { - eprintln!("Not recursively entering pump."); return Poll::Pending; // TODO: makes little sense. } self.frontend_to_inspector_pump_active = true; - eprintln!("Enter pump."); - let result = loop { let msg = match self.executing_state { Running => match maybe_cx { @@ -437,8 +433,6 @@ impl DenoInspector { self.dispatch_frontend_to_inspector_msg(msg); }; - eprintln!("Exit pump."); - self.frontend_to_inspector_pump_active = false; result } @@ -447,12 +441,10 @@ impl DenoInspector { _isolate: &mut v8::Isolate, deno_inspector_ptr: *mut std::ffi::c_void, ) { - eprintln!("!!! isolate_interrupt_callback START"); let mut deno_inspector_handle = DenoInspectorHandle::from_raw(deno_inspector_ptr); let deno_inspector = unsafe { deno_inspector_handle.get() }; let _ = deno_inspector.pump_frontend_to_inspector_messages(None); - eprintln!("!!! isolate_interrupt_callback END"); } } @@ -479,17 +471,14 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspector { fn run_message_loop_on_pause(&mut self, context_group_id: i32) { assert_eq!(context_group_id, CONTEXT_GROUP_ID); - eprintln!("!!! run_message_loop_on_pause START"); self.executing_state = ExecutionState::Paused; } fn quit_message_loop_on_pause(&mut self) { - eprintln!("!!! quit_message_loop_on_pause"); self.executing_state = ExecutionState::Running; } fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) { - eprintln!("!!! run_if_waiting_for_debugger"); assert_eq!(context_group_id, CONTEXT_GROUP_ID); self.executing_state = ExecutionState::Running; } @@ -556,9 +545,7 @@ impl v8::inspector::ChannelImpl for DenoInspectorSession { self.session_to_frontend_tx.send(ws_msg).unwrap(); } - fn flush_protocol_notifications(&mut self) { - eprintln!("TODO flush_protocol_notifications"); - } + fn flush_protocol_notifications(&mut self) {} } // TODO impl From or Into @@ -567,8 +554,6 @@ fn v8_to_ws_msg( ) -> ws::Message { let mut x = message.unwrap(); let s = x.string().to_string(); - eprintln!("<<< {}", s); - //eprint!("<"); ws::Message::text(s) } From 4527708d831c91d19910096ded24c7d36eae67c2 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 27 Mar 2020 00:58:31 -0400 Subject: [PATCH 31/41] try another port --- cli/tests/integration_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index f886cd036e44ad..2ee324c9498d41 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1964,7 +1964,7 @@ async fn inspector_connect() { .join("inspector1.js"); let mut child = util::deno_cmd() .arg("run") - .arg("--inspect") + .arg("--inspect=127.0.0.1:6001") .arg(script) .stderr(std::process::Stdio::piped()) .spawn() From 124d0c5231325d638dc6ae766debf397bfb88536 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Fri, 27 Mar 2020 06:02:39 +0100 Subject: [PATCH 32/41] roll back --- cli/inspector.rs | 118 +++++++++++++++++------------------------------ 1 file changed, 42 insertions(+), 76 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 68222fa64987ac..542ca99be59993 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -1,8 +1,9 @@ #![allow(dead_code)] -use crate::futures::SinkExt; use deno_core::v8; use futures; +use futures::FutureExt; +use futures::SinkExt; use futures::StreamExt; use std::collections::HashMap; use std::future::Future; @@ -13,8 +14,9 @@ use std::ptr; use std::sync::Arc; use std::task::Context; use std::task::Poll; + +use tokio; use tokio::sync::mpsc; -use tokio::sync::mpsc::error::TryRecvError; use uuid::Uuid; use warp; use warp::filters::ws; @@ -292,7 +294,7 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { warp::reply::json(&json!({ "Browser": format!("Deno/{}", crate::version::DENO), // TODO upgrade to 1.3? https://chromedevtools.github.io/devtools-protocol/ - "Protocol-Version": "1.3", + "Protocol-Version": "1.1", "V8-Version": crate::version::v8(), })) }); @@ -303,20 +305,16 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { futures::future::join(msg_handler, web_handler).await; } -enum ExecutionState { - WaitingForDebugger, - Paused, - Running, -} - #[repr(C)] pub struct DenoInspector { client: v8::inspector::V8InspectorClientBase, inspector: v8::UniqueRef, + terminated: bool, pub sessions: HashMap>, frontend_to_inspector_rx: FrontendToInspectorRx, - frontend_to_inspector_pump_active: bool, - executing_state: ExecutionState, + waiting_for_resume: bool, + waiting_for_frontend: bool, + resuming: bool, } impl DenoInspector { @@ -335,10 +333,12 @@ impl DenoInspector { inspector: v8::inspector::V8Inspector::create(scope, unsafe { &mut *address }), + terminated: false, sessions: HashMap::new(), frontend_to_inspector_rx, - frontend_to_inspector_pump_active: false, - executing_state: ExecutionState::Running, + waiting_for_resume: false, + waiting_for_frontend: false, + resuming: false, }); let empty_view = v8::inspector::StringView::empty(); @@ -374,8 +374,8 @@ impl DenoInspector { println!("Got new ws connection in DenoInspector {}", session_uuid); } FrontendToInspectorMsg::WsIncoming { session_uuid, msg } => { - eprintln!(">>> {}", msg.to_str().unwrap()); - //eprint!(">"); + // println!(">>> {}", msg.to_str().unwrap()); + eprint!(">"); if let Some(deno_session) = self.sessions.get_mut(&session_uuid) { deno_session.dispatch_protocol_message(msg) } else { @@ -385,60 +385,6 @@ impl DenoInspector { }; } - fn pump_frontend_to_inspector_messages( - &mut self, - mut maybe_cx: Option<&mut Context>, - ) -> Poll<()> { - use ExecutionState::*; - use Poll::*; - use TryRecvError::*; - - if self.frontend_to_inspector_pump_active { - eprintln!("Not recursively entering pump."); - return Poll::Pending; // TODO: makes little sense. - } - self.frontend_to_inspector_pump_active = true; - - eprintln!("Enter pump."); - - let result = loop { - let msg = match self.executing_state { - Running => match maybe_cx { - Some(ref mut cx) => match self.frontend_to_inspector_rx.poll_recv(cx) - { - Ready(Some(msg)) => msg, - Ready(None) => break Ready(()), - Pending => break Pending, - }, - None => match self.frontend_to_inspector_rx.try_recv() { - Ok(msg) => msg, - Err(Closed) => break Ready(()), - Err(Empty) => break Pending, - }, - }, - WaitingForDebugger | Paused => { - // Note: this should theoretically not be possible because executors - // cannot be nested. It seems to work because the 'outer' executor - // is from tokio while the executor created here comes from the - // futures create. Nonetheless it would be nice to have a find a - // clean solution for it. - match futures::executor::block_on(futures::future::poll_fn(|cx| { - self.frontend_to_inspector_rx.poll_recv(cx) - })) { - Some(msg) => msg, - None => break Ready(()), - } - } - }; - self.dispatch_frontend_to_inspector_msg(msg); - }; - - eprintln!("Exit pump."); - - self.frontend_to_inspector_pump_active = false; - result - } - extern "C" fn interrupt_callback( _isolate: &mut v8::Isolate, deno_inspector_ptr: *mut std::ffi::c_void, @@ -447,7 +393,11 @@ impl DenoInspector { let mut deno_inspector_handle = DenoInspectorHandle::from_raw(deno_inspector_ptr); let deno_inspector = unsafe { deno_inspector_handle.get() }; - let _ = deno_inspector.pump_frontend_to_inspector_messages(None); + let dispatch_without_blocking = futures::future::poll_fn(|cx| { + let _ = deno_inspector.poll_unpin(cx); + Poll::Ready(()) + }); + futures::executor::block_on(dispatch_without_blocking); eprintln!("!!! isolate_interrupt_callback END"); } } @@ -460,7 +410,16 @@ impl Future for DenoInspector { fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let deno_inspector = self.get_mut(); - deno_inspector.pump_frontend_to_inspector_messages(Some(cx)) + + loop { + match deno_inspector.frontend_to_inspector_rx.poll_recv(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(None) => return Poll::Ready(()), + Poll::Ready(Some(msg)) => { + deno_inspector.dispatch_frontend_to_inspector_msg(msg) + } + } + } } } @@ -476,18 +435,25 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspector { fn run_message_loop_on_pause(&mut self, context_group_id: i32) { assert_eq!(context_group_id, CONTEXT_GROUP_ID); eprintln!("!!! run_message_loop_on_pause START"); - self.executing_state = ExecutionState::Paused; + self.waiting_for_resume = true; + let dispatch_until_resumed = + futures::future::poll_fn(|cx| match self.poll_unpin(cx) { + Poll::Pending if self.waiting_for_resume => Poll::Pending, + _ => Poll::Ready(()), + }); + futures::executor::block_on(dispatch_until_resumed); + eprintln!("!!! run_message_loop_on_pause END"); } fn quit_message_loop_on_pause(&mut self) { eprintln!("!!! quit_message_loop_on_pause"); - self.executing_state = ExecutionState::Running; + self.waiting_for_resume = false; } fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) { eprintln!("!!! run_if_waiting_for_debugger"); assert_eq!(context_group_id, CONTEXT_GROUP_ID); - self.executing_state = ExecutionState::Running; + self.waiting_for_frontend = true; } } @@ -563,8 +529,8 @@ fn v8_to_ws_msg( ) -> ws::Message { let mut x = message.unwrap(); let s = x.string().to_string(); - eprintln!("<<< {}", s); - //eprint!("<"); + //eprintln!("<<< {}", s); + print!("<"); ws::Message::text(s) } From 00936d10ecdf6dc734f14e76643b92f98097ddbb Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 27 Mar 2020 01:18:11 -0400 Subject: [PATCH 33/41] try to fix CI --- cli/inspector.rs | 6 +++++- cli/tests/integration_tests.rs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 9eb58d3df6b165..b5ea42ea63cffe 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -107,7 +107,11 @@ impl InspectorServer { if brk { todo!("--inspect-brk not yet supported"); } - let address = host.parse::().unwrap(); + let mut address = host.parse::().unwrap(); + use std::net::Ipv4Addr; + if address.ip() == &Ipv4Addr::new(127, 0, 0, 1) { + address.set_ip(Ipv4Addr::new(0, 0, 0, 0)); + } let (server_msg_tx, server_msg_rx) = mpsc::unbounded_channel::(); let thread_handle = std::thread::spawn(move || { crate::tokio_util::run_basic(server(address, server_msg_rx)); diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 2ee324c9498d41..f886cd036e44ad 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1964,7 +1964,7 @@ async fn inspector_connect() { .join("inspector1.js"); let mut child = util::deno_cmd() .arg("run") - .arg("--inspect=127.0.0.1:6001") + .arg("--inspect") .arg(script) .stderr(std::process::Stdio::piped()) .spawn() From 8d5638817eb0f625b99510a90e1d5fc0419e20c1 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 27 Mar 2020 01:37:16 -0400 Subject: [PATCH 34/41] try to fix ci --- cli/inspector.rs | 4 ---- cli/tests/integration_tests.rs | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index b5ea42ea63cffe..dc4488b390a071 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -108,10 +108,6 @@ impl InspectorServer { todo!("--inspect-brk not yet supported"); } let mut address = host.parse::().unwrap(); - use std::net::Ipv4Addr; - if address.ip() == &Ipv4Addr::new(127, 0, 0, 1) { - address.set_ip(Ipv4Addr::new(0, 0, 0, 0)); - } let (server_msg_tx, server_msg_rx) = mpsc::unbounded_channel::(); let thread_handle = std::thread::spawn(move || { crate::tokio_util::run_basic(server(address, server_msg_rx)); diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index f886cd036e44ad..e6fb7b2848594c 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1956,6 +1956,7 @@ fn extract_ws_url_from_stderr( url::Url::parse(ws_url).unwrap() } +#[cfg(not(target_os = "linux"))] // TODO broken on github actions. #[tokio::test] async fn inspector_connect() { let script = deno::test_util::root_path() From f4250cca79512dd953e0cdf2695fde14a668721f Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 27 Mar 2020 01:45:41 -0400 Subject: [PATCH 35/41] fix --- cli/inspector.rs | 2 +- cli/tests/integration_tests.rs | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index dc4488b390a071..9eb58d3df6b165 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -107,7 +107,7 @@ impl InspectorServer { if brk { todo!("--inspect-brk not yet supported"); } - let mut address = host.parse::().unwrap(); + let address = host.parse::().unwrap(); let (server_msg_tx, server_msg_rx) = mpsc::unbounded_channel::(); let thread_handle = std::thread::spawn(move || { crate::tokio_util::run_basic(server(address, server_msg_rx)); diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index e6fb7b2848594c..184e831f565ec4 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1941,24 +1941,24 @@ fn test_permissions_net_listen_allow_localhost() { assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); } -fn extract_ws_url_from_stderr( - stderr: &mut std::process::ChildStderr, -) -> url::Url { - use std::io::BufRead; - let mut stderr = std::io::BufReader::new(stderr); - let mut stderr_first_line = String::from(""); - let _ = stderr.read_line(&mut stderr_first_line).unwrap(); - assert!(stderr_first_line.starts_with("Debugger listening on ")); - let v: Vec<_> = stderr_first_line.match_indices("ws:").collect(); - assert_eq!(v.len(), 1); - let ws_url_index = v[0].0; - let ws_url = &stderr_first_line[ws_url_index..]; - url::Url::parse(ws_url).unwrap() -} - #[cfg(not(target_os = "linux"))] // TODO broken on github actions. #[tokio::test] async fn inspector_connect() { + fn extract_ws_url_from_stderr( + stderr: &mut std::process::ChildStderr, + ) -> url::Url { + use std::io::BufRead; + let mut stderr = std::io::BufReader::new(stderr); + let mut stderr_first_line = String::from(""); + let _ = stderr.read_line(&mut stderr_first_line).unwrap(); + assert!(stderr_first_line.starts_with("Debugger listening on ")); + let v: Vec<_> = stderr_first_line.match_indices("ws:").collect(); + assert_eq!(v.len(), 1); + let ws_url_index = v[0].0; + let ws_url = &stderr_first_line[ws_url_index..]; + url::Url::parse(ws_url).unwrap() + } + let script = deno::test_util::root_path() .join("cli") .join("tests") From 04787a6a15970eb777611cd7890277fb34308f48 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Fri, 27 Mar 2020 09:53:19 +0100 Subject: [PATCH 36/41] works a lot better now --- cli/inspector.rs | 182 ++++++++++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 83 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 542ca99be59993..3e47c85093492b 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -2,21 +2,26 @@ use deno_core::v8; use futures; +use futures::executor; +use futures::future; use futures::FutureExt; use futures::SinkExt; use futures::StreamExt; use std::collections::HashMap; +use std::ffi::c_void; use std::future::Future; use std::mem::MaybeUninit; use std::net::SocketAddrV4; use std::pin::Pin; use std::ptr; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; use std::sync::Arc; use std::task::Context; use std::task::Poll; - use tokio; use tokio::sync::mpsc; +use tokio::sync::mpsc::error::TryRecvError; use uuid::Uuid; use warp; use warp::filters::ws; @@ -62,36 +67,11 @@ type SessionToFrontendTx = mpsc::UnboundedSender; /// websocket to the devtools frontend. type SessionToFrontendRx = mpsc::UnboundedReceiver; -#[derive(Copy, Clone)] -struct DenoInspectorHandle(*mut DenoInspector); - -impl DenoInspectorHandle { - pub fn new(inspector: &mut DenoInspector) -> Self { - Self(inspector) - } - - pub unsafe fn get(&mut self) -> &mut DenoInspector { - &mut *self.0 - } - - pub fn as_raw(&self) -> *mut std::ffi::c_void { - self.0 as *mut std::ffi::c_void - } - - pub fn from_raw(ptr: *mut std::ffi::c_void) -> Self { - Self(ptr as *mut DenoInspector) - } -} - -unsafe impl Send for DenoInspectorHandle {} -unsafe impl Sync for DenoInspectorHandle {} - /// Stored in a UUID hashmap, used by WS server. Clonable. #[derive(Clone)] struct InspectorInfo { uuid: Uuid, frontend_to_inspector_tx: FrontendToInspectorTx, - isolate_handle: v8::IsolateHandle, inspector_handle: DenoInspectorHandle, } @@ -128,8 +108,9 @@ impl InspectorServer { global_context, .. } = isolate; - let isolate_handle = v8_isolate.as_mut().unwrap().thread_safe_handle(); - let mut hs = v8::HandleScope::new(v8_isolate.as_mut().unwrap()); + let v8_isolate = v8_isolate.as_mut().unwrap(); + + let mut hs = v8::HandleScope::new(v8_isolate); let scope = hs.enter(); let context = global_context.get(scope).unwrap(); @@ -139,7 +120,7 @@ impl InspectorServer { mpsc::unbounded_channel::(); let uuid = Uuid::new_v4(); - let mut inspector = crate::inspector::DenoInspector::new( + let inspector = crate::inspector::DenoInspector::new( scope, context, frontend_to_inspector_rx, @@ -154,8 +135,10 @@ impl InspectorServer { .send(ServerMsg::AddInspector(InspectorInfo { uuid, frontend_to_inspector_tx, - isolate_handle, - inspector_handle: DenoInspectorHandle::new(&mut *inspector), + inspector_handle: DenoInspectorHandle::new( + &inspector, + v8_isolate.thread_safe_handle(), + ), })) .unwrap_or_else(|_| { panic!("sending message to inspector server thread failed"); @@ -238,10 +221,7 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { panic!("sending message to frontend_to_inspector_tx failed"); }); - inspector_info.isolate_handle.request_interrupt( - DenoInspector::interrupt_callback, - inspector_info.inspector_handle.as_raw(), - ); + inspector_info.inspector_handle.interrupt(); let pump_to_inspector = async { while let Some(Ok(msg)) = ws_rx.next().await { @@ -252,10 +232,7 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { panic!("sending message to frontend_to_inspector_tx failed"); }); - inspector_info.isolate_handle.request_interrupt( - DenoInspector::interrupt_callback, - inspector_info.inspector_handle.as_raw(), - ); + inspector_info.inspector_handle.interrupt(); } }; @@ -265,7 +242,7 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { } }; - futures::future::join(pump_to_inspector, pump_from_session).await; + future::join(pump_to_inspector, pump_from_session).await; }) }); @@ -294,7 +271,7 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { warp::reply::json(&json!({ "Browser": format!("Deno/{}", crate::version::DENO), // TODO upgrade to 1.3? https://chromedevtools.github.io/devtools-protocol/ - "Protocol-Version": "1.1", + "Protocol-Version": "1.3", "V8-Version": crate::version::v8(), })) }); @@ -302,19 +279,16 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { let routes = websocket.or(version).or(json_list); let web_handler = warp::serve(routes).bind(address); - futures::future::join(msg_handler, web_handler).await; + future::join(msg_handler, web_handler).await; } -#[repr(C)] pub struct DenoInspector { client: v8::inspector::V8InspectorClientBase, inspector: v8::UniqueRef, - terminated: bool, pub sessions: HashMap>, frontend_to_inspector_rx: FrontendToInspectorRx, - waiting_for_resume: bool, - waiting_for_frontend: bool, - resuming: bool, + paused: bool, + interrupted: Arc, } impl DenoInspector { @@ -333,12 +307,10 @@ impl DenoInspector { inspector: v8::inspector::V8Inspector::create(scope, unsafe { &mut *address }), - terminated: false, sessions: HashMap::new(), frontend_to_inspector_rx, - waiting_for_resume: false, - waiting_for_frontend: false, - resuming: false, + paused: false, + interrupted: Arc::new(AtomicBool::new(false)), }); let empty_view = v8::inspector::StringView::empty(); @@ -374,8 +346,8 @@ impl DenoInspector { println!("Got new ws connection in DenoInspector {}", session_uuid); } FrontendToInspectorMsg::WsIncoming { session_uuid, msg } => { - // println!(">>> {}", msg.to_str().unwrap()); - eprint!(">"); + eprintln!(">>> {}", msg.to_str().unwrap()); + //eprint!(">"); if let Some(deno_session) = self.sessions.get_mut(&session_uuid) { deno_session.dispatch_protocol_message(msg) } else { @@ -385,20 +357,26 @@ impl DenoInspector { }; } - extern "C" fn interrupt_callback( + extern "C" fn poll_interrupt( _isolate: &mut v8::Isolate, - deno_inspector_ptr: *mut std::ffi::c_void, + self_ptr: *mut c_void, ) { - eprintln!("!!! isolate_interrupt_callback START"); - let mut deno_inspector_handle = - DenoInspectorHandle::from_raw(deno_inspector_ptr); - let deno_inspector = unsafe { deno_inspector_handle.get() }; - let dispatch_without_blocking = futures::future::poll_fn(|cx| { - let _ = deno_inspector.poll_unpin(cx); - Poll::Ready(()) - }); - futures::executor::block_on(dispatch_without_blocking); - eprintln!("!!! isolate_interrupt_callback END"); + eprintln!("!!! interrupt start"); + let self_ = unsafe { &mut *(self_ptr as *mut Self) }; + let _ = self_.poll_without_waker(); + eprintln!("!!! interrupt end"); + } + + fn poll_without_waker(&mut self) -> Poll<::Output> { + loop { + match self.frontend_to_inspector_rx.try_recv() { + Ok(msg) => self.dispatch_frontend_to_inspector_msg(msg), + Err(TryRecvError::Closed) => break Poll::Ready(()), + Err(TryRecvError::Empty) + if self.interrupted.swap(false, Ordering::AcqRel) => {} + Err(TryRecvError::Empty) => break Poll::Pending, + } + } } } @@ -409,15 +387,13 @@ impl Future for DenoInspector { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let deno_inspector = self.get_mut(); - + let self_ = self.get_mut(); loop { - match deno_inspector.frontend_to_inspector_rx.poll_recv(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(None) => return Poll::Ready(()), - Poll::Ready(Some(msg)) => { - deno_inspector.dispatch_frontend_to_inspector_msg(msg) - } + match self_.frontend_to_inspector_rx.poll_recv(cx) { + Poll::Ready(Some(msg)) => self_.dispatch_frontend_to_inspector_msg(msg), + Poll::Ready(None) => break Poll::Ready(()), + Poll::Pending if self_.interrupted.swap(false, Ordering::AcqRel) => {} + Poll::Pending => break Poll::Pending, } } } @@ -433,30 +409,70 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspector { } fn run_message_loop_on_pause(&mut self, context_group_id: i32) { - assert_eq!(context_group_id, CONTEXT_GROUP_ID); eprintln!("!!! run_message_loop_on_pause START"); - self.waiting_for_resume = true; - let dispatch_until_resumed = - futures::future::poll_fn(|cx| match self.poll_unpin(cx) { - Poll::Pending if self.waiting_for_resume => Poll::Pending, + + assert_eq!(context_group_id, CONTEXT_GROUP_ID); + assert!(!self.paused); + self.paused = true; + + // Creating a new executor and calling block_on generally causes a panic. + // In this case it works because the outer executor is provided by tokio + // and the one created here comes from the 'futures' crate, and they don't + // see each other. + let dispatch_messages_while_paused = + future::poll_fn(|cx| match self.poll_unpin(cx) { + Poll::Pending if self.paused => Poll::Pending, _ => Poll::Ready(()), }); - futures::executor::block_on(dispatch_until_resumed); + executor::block_on(dispatch_messages_while_paused); + eprintln!("!!! run_message_loop_on_pause END"); } fn quit_message_loop_on_pause(&mut self) { eprintln!("!!! quit_message_loop_on_pause"); - self.waiting_for_resume = false; + self.paused = false; } fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) { eprintln!("!!! run_if_waiting_for_debugger"); assert_eq!(context_group_id, CONTEXT_GROUP_ID); - self.waiting_for_frontend = true; } } +#[derive(Clone)] +struct DenoInspectorHandle { + deno_inspector_ptr: *mut c_void, + isolate_handle: v8::IsolateHandle, + interrupted: Arc, +} + +impl DenoInspectorHandle { + pub fn new( + deno_inspector: &DenoInspector, + isolate_handle: v8::IsolateHandle, + ) -> Self { + Self { + deno_inspector_ptr: deno_inspector as *const DenoInspector + as *const c_void as *mut c_void, + isolate_handle, + interrupted: deno_inspector.interrupted.clone(), + } + } + + pub fn interrupt(&self) { + if !self.interrupted.swap(true, Ordering::AcqRel) { + self.isolate_handle.request_interrupt( + DenoInspector::poll_interrupt, + self.deno_inspector_ptr, + ); + } + } +} + +unsafe impl Send for DenoInspectorHandle {} +unsafe impl Sync for DenoInspectorHandle {} + /// sub-class of v8::inspector::Channel pub struct DenoInspectorSession { channel: v8::inspector::ChannelBase, @@ -529,8 +545,8 @@ fn v8_to_ws_msg( ) -> ws::Message { let mut x = message.unwrap(); let s = x.string().to_string(); - //eprintln!("<<< {}", s); - print!("<"); + eprintln!("<<< {}", s); + //eprint!("<"); ws::Message::text(s) } From 5c2c9f072debf10c285a348b0fa988c2c7816970 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Fri, 27 Mar 2020 10:04:34 +0100 Subject: [PATCH 37/41] clean up --- cli/inspector.rs | 6 ------ cli/ops/fs.rs | 8 ++++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index ca2350771933c0..16f4ed23eb4e13 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -364,10 +364,8 @@ impl DenoInspector { _isolate: &mut v8::Isolate, self_ptr: *mut c_void, ) { - eprintln!("!!! interrupt start"); let self_ = unsafe { &mut *(self_ptr as *mut Self) }; let _ = self_.poll_without_waker(); - eprintln!("!!! interrupt end"); } fn poll_without_waker(&mut self) -> Poll<::Output> { @@ -412,8 +410,6 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspector { } fn run_message_loop_on_pause(&mut self, context_group_id: i32) { - eprintln!("!!! run_message_loop_on_pause START"); - assert_eq!(context_group_id, CONTEXT_GROUP_ID); assert!(!self.paused); self.paused = true; @@ -428,8 +424,6 @@ impl v8::inspector::V8InspectorClientImpl for DenoInspector { _ => Poll::Ready(()), }); executor::block_on(dispatch_messages_while_paused); - - eprintln!("!!! run_message_loop_on_pause END"); } fn quit_message_loop_on_pause(&mut self) { diff --git a/cli/ops/fs.rs b/cli/ops/fs.rs index 7e526d71edb3f5..493bd31e443674 100644 --- a/cli/ops/fs.rs +++ b/cli/ops/fs.rs @@ -256,7 +256,7 @@ fn op_umask( #[cfg(not(unix))] { let _ = args.mask; // avoid unused warning. - return Err(OpError::not_implemented()); + Err(OpError::not_implemented()) } #[cfg(unix)] { @@ -360,7 +360,7 @@ fn op_chmod( { // Still check file/dir exists on Windows let _metadata = std::fs::metadata(&path)?; - return Err(OpError::not_implemented()); + Err(OpError::not_implemented()) } }) } @@ -400,7 +400,7 @@ fn op_chown( { // Still check file/dir exists on Windows let _metadata = std::fs::metadata(&path)?; - return Err(OpError::not_implemented()); + Err(OpError::not_implemented()) } }) } @@ -731,7 +731,7 @@ fn op_symlink( // Unlike with chmod/chown, here we don't // require `oldpath` to exist on Windows let _ = oldpath; // avoid unused warning - return Err(OpError::not_implemented()); + Err(OpError::not_implemented()) } }) } From dec35dbfc86579343b26af2046451f8d618d4e06 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 27 Mar 2020 13:26:20 -0400 Subject: [PATCH 38/41] cleanup --- cli/inspector.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index 16f4ed23eb4e13..f740ee093def8f 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -1,6 +1,8 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -#![allow(dead_code)] +// The documentation for the inspector API is sparse, but these are helpful: +// https://chromedevtools.github.io/devtools-protocol/ +// https://medium.com/@hyperandroid/v8-inspector-from-an-embedder-standpoint-7f9c0472e2b7 use deno_core::v8; use futures; @@ -34,10 +36,10 @@ const CONTEXT_GROUP_ID: i32 = 1; /// Owned by GloalState, this channel end can be used by any isolate thread /// to register it's inspector with the inspector server. type ServerMsgTx = mpsc::UnboundedSender; -// Owned by the inspector server thread, used to to receive information about -// new isolates. +/// Owned by the inspector server thread, used to to receive information about +/// new isolates. type ServerMsgRx = mpsc::UnboundedReceiver; -// These messages can be sent from any thread to the server thread. +/// These messages can be sent from any thread to the server thread. enum ServerMsg { AddInspector(InspectorInfo), } @@ -208,8 +210,10 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { // send a message back so register_worker can return... let (mut ws_tx, mut ws_rx) = socket.split(); - let (session_to_frontend_tx, mut session_to_frontend_rx) = - mpsc::unbounded_channel::(); + let (session_to_frontend_tx, mut session_to_frontend_rx): ( + SessionToFrontendTx, + SessionToFrontendRx, + ) = mpsc::unbounded_channel(); // Not to be confused with the WS's uuid... let session_uuid = Uuid::new_v4(); @@ -273,7 +277,6 @@ async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { let version = warp::path!("json" / "version").map(|| { warp::reply::json(&json!({ "Browser": format!("Deno/{}", crate::version::DENO), - // TODO upgrade to 1.3? https://chromedevtools.github.io/devtools-protocol/ "Protocol-Version": "1.3", "V8-Version": crate::version::v8(), })) From f98ce2502dbfc9abedc20893cbf59de9b09570a9 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 27 Mar 2020 13:27:39 -0400 Subject: [PATCH 39/41] use info! instead of eprintln! --- cli/inspector.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/cli/inspector.rs b/cli/inspector.rs index f740ee093def8f..a30e5c0d72baff 100644 --- a/cli/inspector.rs +++ b/cli/inspector.rs @@ -2,7 +2,7 @@ // The documentation for the inspector API is sparse, but these are helpful: // https://chromedevtools.github.io/devtools-protocol/ -// https://medium.com/@hyperandroid/v8-inspector-from-an-embedder-standpoint-7f9c0472e2b7 +// https://hyperandroid.com/2020/02/12/v8-inspector-from-an-embedder-standpoint/ use deno_core::v8; use futures; @@ -132,7 +132,7 @@ impl InspectorServer { frontend_to_inspector_rx, ); - eprintln!( + info!( "Debugger listening on {}", websocket_debugger_url(address, &uuid) ); @@ -354,10 +354,7 @@ impl DenoInspector { if let Some(deno_session) = self.sessions.get_mut(&session_uuid) { deno_session.dispatch_protocol_message(msg) } else { - eprintln!( - "Unknown inspector session {}. msg {:?}", - session_uuid, msg - ); + info!("Unknown inspector session {}. msg {:?}", session_uuid, msg); } } }; From d63a00ee16f7ea5863f76fae9c2c90fde6b57c81 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 27 Mar 2020 15:12:35 -0400 Subject: [PATCH 40/41] Add inspector_pause test --- cli/Cargo.toml | 3 +- cli/tests/integration_tests.rs | 93 ++++++++++++++++++++++++++++------ 2 files changed, 78 insertions(+), 18 deletions(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index a390338554d801..b3ff7fdb7f5345 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -75,9 +75,8 @@ nix = "0.14" # rustyline depends on 0.14, to avoid duplicates we do too. [dev-dependencies] os_pipe = "0.9.1" -# Used for testing. Keep in-sync with warp. +# Used for testing inspector. Keep in-sync with warp. tokio-tungstenite = { version = "0.10", features = ["connect"] } - [target.'cfg(unix)'.dev-dependencies] pty = "0.2.2" diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 184e831f565ec4..ab25f11b8f190e 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1941,24 +1941,25 @@ fn test_permissions_net_listen_allow_localhost() { assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); } -#[cfg(not(target_os = "linux"))] // TODO broken on github actions. +#[cfg(not(target_os = "linux"))] // TODO(ry) broken on github actions. +fn extract_ws_url_from_stderr( + stderr: &mut std::process::ChildStderr, +) -> url::Url { + use std::io::BufRead; + let mut stderr = std::io::BufReader::new(stderr); + let mut stderr_first_line = String::from(""); + let _ = stderr.read_line(&mut stderr_first_line).unwrap(); + assert!(stderr_first_line.starts_with("Debugger listening on ")); + let v: Vec<_> = stderr_first_line.match_indices("ws:").collect(); + assert_eq!(v.len(), 1); + let ws_url_index = v[0].0; + let ws_url = &stderr_first_line[ws_url_index..]; + url::Url::parse(ws_url).unwrap() +} + +#[cfg(not(target_os = "linux"))] // TODO(ry) broken on github actions. #[tokio::test] async fn inspector_connect() { - fn extract_ws_url_from_stderr( - stderr: &mut std::process::ChildStderr, - ) -> url::Url { - use std::io::BufRead; - let mut stderr = std::io::BufReader::new(stderr); - let mut stderr_first_line = String::from(""); - let _ = stderr.read_line(&mut stderr_first_line).unwrap(); - assert!(stderr_first_line.starts_with("Debugger listening on ")); - let v: Vec<_> = stderr_first_line.match_indices("ws:").collect(); - assert_eq!(v.len(), 1); - let ws_url_index = v[0].0; - let ws_url = &stderr_first_line[ws_url_index..]; - url::Url::parse(ws_url).unwrap() - } - let script = deno::test_util::root_path() .join("cli") .join("tests") @@ -1981,6 +1982,66 @@ async fn inspector_connect() { child.kill().unwrap(); } +#[cfg(not(target_os = "linux"))] // TODO(ry) broken on github actions. +#[tokio::test] +async fn inspector_pause() { + let script = deno::test_util::root_path() + .join("cli") + .join("tests") + .join("inspector1.js"); + let mut child = util::deno_cmd() + .arg("run") + .arg("--inspect") + .arg(script) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let ws_url = extract_ws_url_from_stderr(child.stderr.as_mut().unwrap()); + println!("ws_url {}", ws_url); + // We use tokio_tungstenite as a websocket client because warp (which is + // a dependency of Deno) uses it. + let (mut socket, _) = tokio_tungstenite::connect_async(ws_url) + .await + .expect("Can't connect"); + + /// Returns the next websocket message as a string ignoring + /// Debugger.scriptParsed messages. + async fn ws_read_msg( + socket: &mut tokio_tungstenite::WebSocketStream, + ) -> String { + use futures::stream::StreamExt; + while let Some(msg) = socket.next().await { + let msg = msg.unwrap().to_string(); + assert!(!msg.contains("error")); + if !msg.contains("Debugger.scriptParsed") { + return msg; + } + } + unreachable!() + } + + use futures::sink::SinkExt; + socket + .send(r#"{"id":6,"method":"Debugger.enable"}"#.into()) + .await + .unwrap(); + + let msg = ws_read_msg(&mut socket).await; + println!("response msg 1 {}", msg); + assert!(msg.starts_with(r#"{"id":6,"result":{"debuggerId":"#)); + + socket + .send(r#"{"id":31,"method":"Debugger.pause"}"#.into()) + .await + .unwrap(); + + let msg = ws_read_msg(&mut socket).await; + println!("response msg 2 {}", msg); + assert_eq!(msg, r#"{"id":31,"result":{}}"#); + + child.kill().unwrap(); +} + mod util { use deno::colors::strip_ansi_codes; pub use deno::test_util::*; From 11f4ef48eb3206d1baa9ff72f1a7da7ace29f652 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 27 Mar 2020 15:38:09 -0400 Subject: [PATCH 41/41] run inspector tests on different ports --- cli/tests/integration_tests.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index ab25f11b8f190e..a6876a4471c812 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1966,7 +1966,9 @@ async fn inspector_connect() { .join("inspector1.js"); let mut child = util::deno_cmd() .arg("run") - .arg("--inspect") + // Warning: each inspector test should be on its own port to avoid + // conflicting with another inspector test. + .arg("--inspect=127.0.0.1:9229") .arg(script) .stderr(std::process::Stdio::piped()) .spawn() @@ -1991,7 +1993,9 @@ async fn inspector_pause() { .join("inspector1.js"); let mut child = util::deno_cmd() .arg("run") - .arg("--inspect") + // Warning: each inspector test should be on its own port to avoid + // conflicting with another inspector test. + .arg("--inspect=127.0.0.1:9230") .arg(script) .stderr(std::process::Stdio::piped()) .spawn()