Skip to content

Commit

Permalink
fix(watcher): watcher doesn't exit when module resolution fails (deno…
Browse files Browse the repository at this point in the history
…land#8521)

This commit makes the file watcher continue to work even if module
resolution fails at the initial attempt, allowing us to execute `run`
or `bundle` subcommand when a script has invalid syntax. In such
cases, the watcher observes a single file that is specified as an
command line argument.
  • Loading branch information
magurotuna committed Nov 28, 2020
1 parent 5588085 commit d9b4182
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 54 deletions.
81 changes: 52 additions & 29 deletions cli/file_watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use tokio::time::{delay_for, Delay};

const DEBOUNCE_INTERVAL_MS: Duration = Duration::from_millis(200);

type FileWatcherFuture<T> = Pin<Box<dyn Future<Output = Result<T, AnyError>>>>;
type FileWatcherFuture<T> = Pin<Box<dyn Future<Output = T>>>;

struct Debounce {
delay: Delay,
Expand Down Expand Up @@ -63,7 +63,7 @@ impl Stream for Debounce {
}
}

async fn error_handler(watch_future: FileWatcherFuture<()>) {
async fn error_handler(watch_future: FileWatcherFuture<Result<(), AnyError>>) {
let result = watch_future.await;
if let Err(err) = result {
let msg = format!("{}: {}", colors::red_bold("error"), err.to_string(),);
Expand Down Expand Up @@ -94,7 +94,7 @@ pub async fn watch_func<F, G>(
) -> Result<(), AnyError>
where
F: Fn() -> Result<Vec<PathBuf>, AnyError>,
G: Fn(Vec<PathBuf>) -> FileWatcherFuture<()>,
G: Fn(Vec<PathBuf>) -> FileWatcherFuture<Result<(), AnyError>>,
{
let mut debounce = Debounce::new();

Expand Down Expand Up @@ -129,6 +129,17 @@ where
}
}

pub enum ModuleResolutionResult<T> {
Success {
paths_to_watch: Vec<PathBuf>,
module_info: T,
},
Fail {
source_path: PathBuf,
error: AnyError,
},
}

/// This function adds watcher functionality to subcommands like `run` or `bundle`.
/// The difference from [`watch_func`] is that this does depend on [`ModuleGraph`].
///
Expand All @@ -154,51 +165,63 @@ pub async fn watch_func_with_module_resolution<F, G, T>(
job_name: &str,
) -> Result<(), AnyError>
where
F: Fn() -> FileWatcherFuture<(Vec<PathBuf>, T)>,
G: Fn(T) -> FileWatcherFuture<()>,
F: Fn() -> FileWatcherFuture<ModuleResolutionResult<T>>,
G: Fn(T) -> FileWatcherFuture<Result<(), AnyError>>,
T: Clone,
{
let mut debounce = Debounce::new();
// Store previous data. If module resolution fails at some point, the watcher will try to
// continue watching files using these data.
let mut paths = None;
let mut paths;
let mut module = None;

loop {
match module_resolver().await {
Ok((next_paths, next_module)) => {
paths = Some(next_paths);
module = Some(next_module);
ModuleResolutionResult::Success {
paths_to_watch,
module_info,
} => {
paths = paths_to_watch;
module = Some(module_info);
}
Err(e) => {
// If at least one of `paths` and `module` is `None`, the watcher cannot decide which files
// should be watched. So return the error immediately without watching anything.
if paths.is_none() || module.is_none() {
return Err(e);
ModuleResolutionResult::Fail { source_path, error } => {
paths = vec![source_path];
if module.is_none() {
eprintln!("{}: {}", colors::red_bold("error"), error);
}
}
}
// These `unwrap`s never cause panic since `None` is already checked above.
let cur_paths = paths.clone().unwrap();
let cur_module = module.clone().unwrap();
let _watcher = new_watcher(&paths, &debounce)?;

let _watcher = new_watcher(&cur_paths, &debounce)?;
let func = error_handler(operation(cur_module));
let mut is_file_changed = false;
select! {
_ = debounce.next() => {
is_file_changed = true;
if let Some(module) = &module {
let func = error_handler(operation(module.clone()));
let mut is_file_changed = false;
select! {
_ = debounce.next() => {
is_file_changed = true;
info!(
"{} File change detected! Restarting!",
colors::intense_blue("Watcher"),
);
},
_ = func => {},
};

if !is_file_changed {
info!(
"{} {} finished! Restarting on file change...",
colors::intense_blue("Watcher"),
job_name,
);
debounce.next().await;
info!(
"{} File change detected! Restarting!",
colors::intense_blue("Watcher"),
);
},
_ = func => {},
};

if !is_file_changed {
}
} else {
info!(
"{} {} finished! Restarting on file change...",
"{} {} failed! Restarting on file change...",
colors::intense_blue("Watcher"),
job_name,
);
Expand Down
36 changes: 31 additions & 5 deletions cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ mod worker;

use crate::file_fetcher::File;
use crate::file_fetcher::FileFetcher;
use crate::file_watcher::ModuleResolutionResult;
use crate::media_type::MediaType;
use crate::permissions::Permissions;
use crate::program_state::ProgramState;
Expand Down Expand Up @@ -307,10 +308,11 @@ async fn bundle_command(

let module_resolver = || {
let flags = flags.clone();
let source_file = source_file.clone();
let source_file1 = source_file.clone();
let source_file2 = source_file.clone();
async move {
let module_specifier =
ModuleSpecifier::resolve_url_or_path(&source_file)?;
ModuleSpecifier::resolve_url_or_path(&source_file1)?;

debug!(">>>>> bundle START");
let program_state = ProgramState::new(flags.clone())?;
Expand Down Expand Up @@ -373,6 +375,16 @@ async fn bundle_command(

Ok((paths_to_watch, module_graph))
}
.map(move |result| match result {
Ok((paths_to_watch, module_graph)) => ModuleResolutionResult::Success {
paths_to_watch,
module_info: module_graph,
},
Err(e) => ModuleResolutionResult::Fail {
source_path: PathBuf::from(source_file2),
error: e,
},
})
.boxed_local()
};

Expand Down Expand Up @@ -423,7 +435,10 @@ async fn bundle_command(
)
.await?;
} else {
let (_, module_graph) = module_resolver().await?;
let module_graph = match module_resolver().await {
ModuleResolutionResult::Fail { error, .. } => return Err(error),
ModuleResolutionResult::Success { module_info, .. } => module_info,
};
operation(module_graph).await?;
}

Expand Down Expand Up @@ -610,10 +625,11 @@ async fn run_from_stdin(flags: Flags) -> Result<(), AnyError> {

async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> {
let module_resolver = || {
let script = script.clone();
let script1 = script.clone();
let script2 = script.clone();
let flags = flags.clone();
async move {
let main_module = ModuleSpecifier::resolve_url_or_path(&script)?;
let main_module = ModuleSpecifier::resolve_url_or_path(&script1)?;
let program_state = ProgramState::new(flags)?;
let handler = Rc::new(RefCell::new(FetchHandler::new(
&program_state,
Expand Down Expand Up @@ -641,6 +657,16 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> {

Ok((paths_to_watch, main_module))
}
.map(move |result| match result {
Ok((paths_to_watch, module_info)) => ModuleResolutionResult::Success {
paths_to_watch,
module_info,
},
Err(e) => ModuleResolutionResult::Fail {
source_path: PathBuf::from(script2),
error: e,
},
})
.boxed_local()
};

Expand Down
Loading

0 comments on commit d9b4182

Please sign in to comment.