1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-25 16:49:18 -05:00

fix(watch): preserve ProcState::file_fetcher between restarts (#15466)

This commit changes "ProcState" to store "file_fetcher" field in an "Arc",
allowing it to be preserved between restarts and thus keeping the state
alive between the restarts. File watchers for "deno test" and "deno bench"
now reset "ProcState" between restarts.
This commit is contained in:
Nayeem Rahman 2023-01-10 15:28:10 +00:00 committed by David Sherret
parent aa79603674
commit 3545bff678
12 changed files with 126 additions and 42 deletions

4
cli/cache/mod.rs vendored
View file

@ -50,12 +50,10 @@ pub struct FetchCacher {
impl FetchCacher { impl FetchCacher {
pub fn new( pub fn new(
emit_cache: EmitCache, emit_cache: EmitCache,
file_fetcher: FileFetcher, file_fetcher: Arc<FileFetcher>,
root_permissions: PermissionsContainer, root_permissions: PermissionsContainer,
dynamic_permissions: PermissionsContainer, dynamic_permissions: PermissionsContainer,
) -> Self { ) -> Self {
let file_fetcher = Arc::new(file_fetcher);
Self { Self {
emit_cache, emit_cache,
dynamic_permissions, dynamic_permissions,

1
cli/cache/node.rs vendored
View file

@ -24,6 +24,7 @@ struct CjsAnalysisData {
pub reexports: Vec<String>, pub reexports: Vec<String>,
} }
#[derive(Clone)]
pub struct NodeAnalysisCache { pub struct NodeAnalysisCache {
db_file_path: Option<PathBuf>, db_file_path: Option<PathBuf>,
inner: Arc<Mutex<Option<Option<NodeAnalysisCacheInner>>>>, inner: Arc<Mutex<Option<Option<NodeAnalysisCacheInner>>>>,

View file

@ -65,6 +65,14 @@ impl ParsedSourceCache {
} }
} }
pub fn reset_for_file_watcher(&self) -> Self {
Self {
db_cache_path: self.db_cache_path.clone(),
cli_version: self.cli_version.clone(),
sources: Default::default(),
}
}
pub fn get_parsed_source_from_module( pub fn get_parsed_source_from_module(
&self, &self,
module: &deno_graph::Module, module: &deno_graph::Module,

View file

@ -72,7 +72,7 @@ pub struct GraphData {
impl GraphData { impl GraphData {
/// Store data from `graph` into `self`. /// Store data from `graph` into `self`.
pub fn add_graph(&mut self, graph: &ModuleGraph, reload: bool) { pub fn add_graph(&mut self, graph: &ModuleGraph) {
for graph_import in &graph.imports { for graph_import in &graph.imports {
for dep in graph_import.dependencies.values() { for dep in graph_import.dependencies.values() {
for resolved in [&dep.maybe_code, &dep.maybe_type] { for resolved in [&dep.maybe_code, &dep.maybe_type] {
@ -96,7 +96,7 @@ impl GraphData {
continue; continue;
} }
if !reload && self.modules.contains_key(specifier) { if self.modules.contains_key(specifier) {
continue; continue;
} }
@ -470,7 +470,7 @@ impl GraphData {
impl From<&ModuleGraph> for GraphData { impl From<&ModuleGraph> for GraphData {
fn from(graph: &ModuleGraph) -> Self { fn from(graph: &ModuleGraph) -> Self {
let mut graph_data = GraphData::default(); let mut graph_data = GraphData::default();
graph_data.add_graph(graph, false); graph_data.add_graph(graph);
graph_data graph_data
} }
} }
@ -542,7 +542,7 @@ pub async fn create_graph_and_maybe_check(
let check_js = ps.options.check_js(); let check_js = ps.options.check_js();
let mut graph_data = GraphData::default(); let mut graph_data = GraphData::default();
graph_data.add_graph(&graph, false); graph_data.add_graph(&graph);
graph_data graph_data
.check( .check(
&graph.roots, &graph.roots,

View file

@ -277,7 +277,6 @@ impl ModuleLoader for CliModuleLoader {
lib, lib,
root_permissions, root_permissions,
dynamic_permissions, dynamic_permissions,
false,
) )
.await .await
} }

View file

@ -77,7 +77,7 @@ pub struct ProcState(Arc<Inner>);
pub struct Inner { pub struct Inner {
pub dir: DenoDir, pub dir: DenoDir,
pub file_fetcher: FileFetcher, pub file_fetcher: Arc<FileFetcher>,
pub http_client: HttpClient, pub http_client: HttpClient,
pub options: Arc<CliOptions>, pub options: Arc<CliOptions>,
pub emit_cache: EmitCache, pub emit_cache: EmitCache,
@ -147,6 +147,38 @@ impl ProcState {
Ok(ps) Ok(ps)
} }
/// Reset all runtime state to its default. This should be used on file
/// watcher restarts.
pub fn reset_for_file_watcher(&mut self) {
self.0 = Arc::new(Inner {
dir: self.dir.clone(),
options: self.options.clone(),
emit_cache: self.emit_cache.clone(),
emit_options_hash: self.emit_options_hash,
emit_options: self.emit_options.clone(),
file_fetcher: self.file_fetcher.clone(),
http_client: self.http_client.clone(),
graph_data: Default::default(),
lockfile: self.lockfile.clone(),
maybe_import_map: self.maybe_import_map.clone(),
maybe_inspector_server: self.maybe_inspector_server.clone(),
root_cert_store: self.root_cert_store.clone(),
blob_store: Default::default(),
broadcast_channel: Default::default(),
shared_array_buffer_store: Default::default(),
compiled_wasm_module_store: Default::default(),
parsed_source_cache: self.parsed_source_cache.reset_for_file_watcher(),
maybe_resolver: self.maybe_resolver.clone(),
maybe_file_watcher_reporter: self.maybe_file_watcher_reporter.clone(),
node_analysis_cache: self.node_analysis_cache.clone(),
npm_cache: self.npm_cache.clone(),
npm_resolver: self.npm_resolver.clone(),
cjs_resolutions: Default::default(),
progress_bar: self.progress_bar.clone(),
node_std_graph_prepared: AtomicBool::new(false),
});
}
async fn build_with_sender( async fn build_with_sender(
cli_options: Arc<CliOptions>, cli_options: Arc<CliOptions>,
maybe_sender: Option<tokio::sync::mpsc::UnboundedSender<Vec<PathBuf>>>, maybe_sender: Option<tokio::sync::mpsc::UnboundedSender<Vec<PathBuf>>>,
@ -256,7 +288,7 @@ impl ProcState {
.write_hashable(&emit_options) .write_hashable(&emit_options)
.finish(), .finish(),
emit_options, emit_options,
file_fetcher, file_fetcher: Arc::new(file_fetcher),
http_client, http_client,
graph_data: Default::default(), graph_data: Default::default(),
lockfile, lockfile,
@ -291,7 +323,6 @@ impl ProcState {
lib: TsTypeLib, lib: TsTypeLib,
root_permissions: PermissionsContainer, root_permissions: PermissionsContainer,
dynamic_permissions: PermissionsContainer, dynamic_permissions: PermissionsContainer,
reload_on_watch: bool,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
log::debug!("Preparing module load."); log::debug!("Preparing module load.");
let _pb_clear_guard = self.progress_bar.clear_guard(); let _pb_clear_guard = self.progress_bar.clear_guard();
@ -304,7 +335,7 @@ impl ProcState {
.map(|s| (s, ModuleKind::Esm)) .map(|s| (s, ModuleKind::Esm))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !reload_on_watch && !has_root_npm_specifier { if !has_root_npm_specifier {
let graph_data = self.graph_data.read(); let graph_data = self.graph_data.read();
if self.options.type_check_mode() == TypeCheckMode::None if self.options.type_check_mode() == TypeCheckMode::None
|| graph_data.is_type_checked(&roots, &lib) || graph_data.is_type_checked(&roots, &lib)
@ -338,7 +369,6 @@ impl ProcState {
struct ProcStateLoader<'a> { struct ProcStateLoader<'a> {
inner: &'a mut cache::FetchCacher, inner: &'a mut cache::FetchCacher,
graph_data: Arc<RwLock<GraphData>>, graph_data: Arc<RwLock<GraphData>>,
reload: bool,
} }
impl Loader for ProcStateLoader<'_> { impl Loader for ProcStateLoader<'_> {
fn get_cache_info( fn get_cache_info(
@ -355,9 +385,7 @@ impl ProcState {
let graph_data = self.graph_data.read(); let graph_data = self.graph_data.read();
let found_specifier = graph_data.follow_redirect(specifier); let found_specifier = graph_data.follow_redirect(specifier);
match graph_data.get(&found_specifier) { match graph_data.get(&found_specifier) {
Some(_) if !self.reload => { Some(_) => Box::pin(futures::future::ready(Err(anyhow!("")))),
Box::pin(futures::future::ready(Err(anyhow!(""))))
}
_ => self.inner.load(specifier, is_dynamic), _ => self.inner.load(specifier, is_dynamic),
} }
} }
@ -365,7 +393,6 @@ impl ProcState {
let mut loader = ProcStateLoader { let mut loader = ProcStateLoader {
inner: &mut cache, inner: &mut cache,
graph_data: self.graph_data.clone(), graph_data: self.graph_data.clone(),
reload: reload_on_watch,
}; };
let maybe_file_watcher_reporter: Option<&dyn deno_graph::source::Reporter> = let maybe_file_watcher_reporter: Option<&dyn deno_graph::source::Reporter> =
@ -404,7 +431,7 @@ impl ProcState {
let npm_package_reqs = { let npm_package_reqs = {
let mut graph_data = self.graph_data.write(); let mut graph_data = self.graph_data.write();
graph_data.add_graph(&graph, reload_on_watch); graph_data.add_graph(&graph);
let check_js = self.options.check_js(); let check_js = self.options.check_js();
graph_data graph_data
.check( .check(
@ -492,7 +519,6 @@ impl ProcState {
lib, lib,
PermissionsContainer::allow_all(), PermissionsContainer::allow_all(),
PermissionsContainer::allow_all(), PermissionsContainer::allow_all(),
false,
) )
.await .await
} }
@ -506,7 +532,7 @@ impl ProcState {
let node_std_graph = self let node_std_graph = self
.create_graph(vec![(node::MODULE_ALL_URL.clone(), ModuleKind::Esm)]) .create_graph(vec![(node::MODULE_ALL_URL.clone(), ModuleKind::Esm)])
.await?; .await?;
self.graph_data.write().add_graph(&node_std_graph, false); self.graph_data.write().add_graph(&node_std_graph);
self.node_std_graph_prepared.store(true, Ordering::Relaxed); self.node_std_graph_prepared.store(true, Ordering::Relaxed);
Ok(()) Ok(())
} }
@ -747,7 +773,7 @@ pub fn import_map_from_text(
Ok(result.import_map) Ok(result.import_map)
} }
#[derive(Debug)] #[derive(Clone, Debug)]
struct FileWatcherReporter { struct FileWatcherReporter {
sender: tokio::sync::mpsc::UnboundedSender<Vec<PathBuf>>, sender: tokio::sync::mpsc::UnboundedSender<Vec<PathBuf>>,
file_paths: Arc<Mutex<Vec<PathBuf>>>, file_paths: Arc<Mutex<Vec<PathBuf>>>,

View file

@ -1098,6 +1098,44 @@ mod watcher {
check_alive_then_kill(child); check_alive_then_kill(child);
} }
// Regression test for https://github.com/denoland/deno/issues/15465.
#[test]
fn run_watch_reload_once() {
let _g = util::http_server();
let t = TempDir::new();
let file_to_watch = t.path().join("file_to_watch.js");
let file_content = r#"
import { time } from "http://localhost:4545/dynamic_module.ts";
console.log(time);
"#;
write(&file_to_watch, file_content).unwrap();
let mut child = util::deno_cmd()
.current_dir(util::testdata_path())
.arg("run")
.arg("--watch")
.arg("--reload")
.arg(&file_to_watch)
.env("NO_COLOR", "1")
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.unwrap();
let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child);
wait_contains("finished", &mut stderr_lines);
let first_output = stdout_lines.next().unwrap();
write(&file_to_watch, file_content).unwrap();
// The remote dynamic module should not have been reloaded again.
wait_contains("finished", &mut stderr_lines);
let second_output = stdout_lines.next().unwrap();
assert_eq!(second_output, first_output);
check_alive_then_kill(child);
}
#[test] #[test]
fn run_watch_dynamic_imports() { fn run_watch_dynamic_imports() {
let t = TempDir::new(); let t = TempDir::new();
@ -1159,11 +1197,11 @@ mod watcher {
&mut stdout_lines, &mut stdout_lines,
); );
wait_contains("finished", &mut stderr_lines);
wait_for( wait_for(
|m| m.contains("Watching paths") && m.contains("imported2.js"), |m| m.contains("Watching paths") && m.contains("imported2.js"),
&mut stderr_lines, &mut stderr_lines,
); );
wait_contains("finished", &mut stderr_lines);
write( write(
&file_to_watch3, &file_to_watch3,

View file

@ -32,6 +32,7 @@ use indexmap::IndexMap;
use log::Level; use log::Level;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use std::cell::RefCell;
use std::collections::HashSet; use std::collections::HashSet;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@ -339,7 +340,6 @@ async fn check_specifiers(
lib, lib,
PermissionsContainer::allow_all(), PermissionsContainer::allow_all(),
PermissionsContainer::new(permissions), PermissionsContainer::new(permissions),
true,
) )
.await?; .await?;
@ -538,13 +538,15 @@ pub async fn run_benchmarks_with_watch(
.collect(); .collect();
let no_check = ps.options.type_check_mode() == TypeCheckMode::None; let no_check = ps.options.type_check_mode() == TypeCheckMode::None;
let ps = RefCell::new(ps);
let resolver = |changed: Option<Vec<PathBuf>>| { let resolver = |changed: Option<Vec<PathBuf>>| {
let paths_to_watch = paths_to_watch.clone(); let paths_to_watch = paths_to_watch.clone();
let paths_to_watch_clone = paths_to_watch.clone(); let paths_to_watch_clone = paths_to_watch.clone();
let files_changed = changed.is_some(); let files_changed = changed.is_some();
let files = bench_options.files.clone(); let files = bench_options.files.clone();
let ps = ps.clone(); let ps = ps.borrow().clone();
async move { async move {
let bench_modules = collect_specifiers(&files, is_supported_bench_path)?; let bench_modules = collect_specifiers(&files, is_supported_bench_path)?;
@ -656,7 +658,8 @@ pub async fn run_benchmarks_with_watch(
let operation = |modules_to_reload: Vec<(ModuleSpecifier, ModuleKind)>| { let operation = |modules_to_reload: Vec<(ModuleSpecifier, ModuleKind)>| {
let permissions = permissions.clone(); let permissions = permissions.clone();
let ps = ps.clone(); ps.borrow_mut().reset_for_file_watcher();
let ps = ps.borrow().clone();
let filter = bench_options.filter.clone(); let filter = bench_options.filter.clone();
let files = bench_options.files.clone(); let files = bench_options.files.clone();
@ -681,12 +684,13 @@ pub async fn run_benchmarks_with_watch(
} }
}; };
let clear_screen = !ps.borrow().options.no_clear_screen();
file_watcher::watch_func( file_watcher::watch_func(
resolver, resolver,
operation, operation,
file_watcher::PrintConfig { file_watcher::PrintConfig {
job_name: "Bench".to_string(), job_name: "Bench".to_string(),
clear_screen: !ps.options.no_clear_screen(), clear_screen,
}, },
) )
.await?; .await?;

View file

@ -1,7 +1,6 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::io::Read; use std::io::Read;
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use deno_ast::MediaType; use deno_ast::MediaType;
@ -104,16 +103,13 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<i32, AnyError> {
let flags = Arc::new(flags); let flags = Arc::new(flags);
let main_module = resolve_url_or_path(&script)?; let main_module = resolve_url_or_path(&script)?;
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
let mut ps =
ProcState::build_for_file_watcher((*flags).clone(), sender.clone()).await?;
let operation = |(sender, main_module): ( let operation = |main_module: ModuleSpecifier| {
tokio::sync::mpsc::UnboundedSender<Vec<PathBuf>>, ps.reset_for_file_watcher();
ModuleSpecifier, let ps = ps.clone();
)| {
let flags = flags.clone();
Ok(async move { Ok(async move {
let ps =
ProcState::build_for_file_watcher((*flags).clone(), sender.clone())
.await?;
let permissions = PermissionsContainer::new(Permissions::from_options( let permissions = PermissionsContainer::new(Permissions::from_options(
&ps.options.permissions_options(), &ps.options.permissions_options(),
)?); )?);
@ -128,7 +124,7 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<i32, AnyError> {
util::file_watcher::watch_func2( util::file_watcher::watch_func2(
receiver, receiver,
operation, operation,
(sender, main_module), main_module,
util::file_watcher::PrintConfig { util::file_watcher::PrintConfig {
job_name: "Process".to_string(), job_name: "Process".to_string(),
clear_screen: !flags.no_clear_screen, clear_screen: !flags.no_clear_screen,

View file

@ -46,6 +46,7 @@ use rand::seq::SliceRandom;
use rand::SeedableRng; use rand::SeedableRng;
use regex::Regex; use regex::Regex;
use serde::Deserialize; use serde::Deserialize;
use std::cell::RefCell;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt::Write as _; use std::fmt::Write as _;
@ -957,7 +958,6 @@ pub async fn check_specifiers(
lib, lib,
PermissionsContainer::new(Permissions::allow_all()), PermissionsContainer::new(Permissions::allow_all()),
PermissionsContainer::new(permissions.clone()), PermissionsContainer::new(permissions.clone()),
false,
) )
.await?; .await?;
} }
@ -979,7 +979,6 @@ pub async fn check_specifiers(
lib, lib,
PermissionsContainer::allow_all(), PermissionsContainer::allow_all(),
PermissionsContainer::new(permissions), PermissionsContainer::new(permissions),
true,
) )
.await?; .await?;
@ -1355,12 +1354,14 @@ pub async fn run_tests_with_watch(
let no_check = ps.options.type_check_mode() == TypeCheckMode::None; let no_check = ps.options.type_check_mode() == TypeCheckMode::None;
let test_options = &test_options; let test_options = &test_options;
let ps = RefCell::new(ps);
let resolver = |changed: Option<Vec<PathBuf>>| { let resolver = |changed: Option<Vec<PathBuf>>| {
let paths_to_watch = paths_to_watch.clone(); let paths_to_watch = paths_to_watch.clone();
let paths_to_watch_clone = paths_to_watch.clone(); let paths_to_watch_clone = paths_to_watch.clone();
let files_changed = changed.is_some(); let files_changed = changed.is_some();
let ps = ps.clone(); let ps = ps.borrow().clone();
async move { async move {
let test_modules = if test_options.doc { let test_modules = if test_options.doc {
@ -1477,7 +1478,8 @@ pub async fn run_tests_with_watch(
let operation = |modules_to_reload: Vec<(ModuleSpecifier, ModuleKind)>| { let operation = |modules_to_reload: Vec<(ModuleSpecifier, ModuleKind)>| {
let permissions = permissions.clone(); let permissions = permissions.clone();
let ps = ps.clone(); ps.borrow_mut().reset_for_file_watcher();
let ps = ps.borrow().clone();
let test_options = test_options.clone(); let test_options = test_options.clone();
async move { async move {
@ -1517,12 +1519,13 @@ pub async fn run_tests_with_watch(
} }
}; };
let clear_screen = !ps.borrow().options.no_clear_screen();
file_watcher::watch_func( file_watcher::watch_func(
resolver, resolver,
operation, operation,
file_watcher::PrintConfig { file_watcher::PrintConfig {
job_name: "Test".to_string(), job_name: "Test".to_string(),
clear_screen: !ps.options.no_clear_screen(), clear_screen,
}, },
) )
.await?; .await?;

View file

@ -322,13 +322,13 @@ where
continue; continue;
}, },
_ = operation_future => { _ = operation_future => {
consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver);
// TODO(bartlomieju): print exit code here? // TODO(bartlomieju): print exit code here?
info!( info!(
"{} {} finished. Restarting on file change...", "{} {} finished. Restarting on file change...",
colors::intense_blue("Watcher"), colors::intense_blue("Watcher"),
job_name, job_name,
); );
consume_paths_to_watch(&mut watcher, &mut paths_to_watch_receiver);
}, },
}; };

View file

@ -971,6 +971,17 @@ async fn main_server(
); );
Ok(res) Ok(res)
} }
(_, "/dynamic_module.ts") => {
let mut res = Response::new(Body::from(format!(
r#"export const time = {};"#,
std::time::SystemTime::now().elapsed().unwrap().as_nanos()
)));
res.headers_mut().insert(
"Content-type",
HeaderValue::from_static("application/typescript"),
);
Ok(res)
}
(_, "/echo_accept") => { (_, "/echo_accept") => {
let accept = req.headers().get("accept").map(|v| v.to_str().unwrap()); let accept = req.headers().get("accept").map(|v| v.to_str().unwrap());
let res = Response::new(Body::from( let res = Response::new(Body::from(