mirror of
https://github.com/denoland/deno.git
synced 2024-12-30 11:16:38 -05:00
9845361153
Partially supersedes #19016. This migrates `spawn` and `spawn_blocking` to `deno_core`, and removes the requirement for `spawn` tasks to be `Send` given our single-threaded executor. While we don't need to technically do anything w/`spawn_blocking`, this allows us to have a single `JoinHandle` type that works for both cases, and allows us to more easily experiment with alternative `spawn_blocking` implementations that do not require tokio (ie: rayon). Async ops (+~35%): Before: ``` time 1310 ms rate 763358 time 1267 ms rate 789265 time 1259 ms rate 794281 time 1266 ms rate 789889 ``` After: ``` time 956 ms rate 1046025 time 954 ms rate 1048218 time 924 ms rate 1082251 time 920 ms rate 1086956 ``` HTTP serve (+~4.4%): Before: ``` Running 10s test @ http://localhost:4500 2 threads and 10 connections Thread Stats Avg Stdev Max +/- Stdev Latency 68.78us 19.77us 1.43ms 86.84% Req/Sec 68.78k 5.00k 73.84k 91.58% 1381833 requests in 10.10s, 167.36MB read Requests/sec: 136823.29 Transfer/sec: 16.57MB ``` After: ``` Running 10s test @ http://localhost:4500 2 threads and 10 connections Thread Stats Avg Stdev Max +/- Stdev Latency 63.12us 17.43us 1.11ms 85.13% Req/Sec 71.82k 3.71k 77.02k 79.21% 1443195 requests in 10.10s, 174.79MB read Requests/sec: 142921.99 Transfer/sec: 17.31MB ``` Suggested-By: alice@ryhl.io Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
213 lines
6.8 KiB
Rust
213 lines
6.8 KiB
Rust
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use crate::args::CliOptions;
|
|
use crate::args::Flags;
|
|
use crate::args::ReplFlags;
|
|
use crate::colors;
|
|
use crate::factory::CliFactory;
|
|
use crate::file_fetcher::FileFetcher;
|
|
use deno_core::error::AnyError;
|
|
use deno_core::futures::StreamExt;
|
|
use deno_core::task::spawn_blocking;
|
|
use deno_runtime::permissions::Permissions;
|
|
use deno_runtime::permissions::PermissionsContainer;
|
|
use rustyline::error::ReadlineError;
|
|
|
|
mod cdp;
|
|
mod channel;
|
|
mod editor;
|
|
mod session;
|
|
|
|
use channel::rustyline_channel;
|
|
use channel::RustylineSyncMessage;
|
|
use channel::RustylineSyncMessageHandler;
|
|
use channel::RustylineSyncResponse;
|
|
use editor::EditorHelper;
|
|
use editor::ReplEditor;
|
|
use session::EvaluationOutput;
|
|
use session::ReplSession;
|
|
|
|
async fn read_line_and_poll(
|
|
repl_session: &mut ReplSession,
|
|
message_handler: &mut RustylineSyncMessageHandler,
|
|
editor: ReplEditor,
|
|
) -> Result<String, ReadlineError> {
|
|
#![allow(clippy::await_holding_refcell_ref)]
|
|
let mut line_fut = spawn_blocking(move || editor.readline());
|
|
let mut poll_worker = true;
|
|
let notifications_rc = repl_session.notifications.clone();
|
|
let mut notifications = notifications_rc.borrow_mut();
|
|
|
|
loop {
|
|
tokio::select! {
|
|
result = &mut line_fut => {
|
|
return result.unwrap();
|
|
}
|
|
result = message_handler.recv() => {
|
|
match result {
|
|
Some(RustylineSyncMessage::PostMessage { method, params }) => {
|
|
let result = repl_session
|
|
.post_message_with_event_loop(&method, params)
|
|
.await;
|
|
message_handler.send(RustylineSyncResponse::PostMessage(result)).unwrap();
|
|
},
|
|
Some(RustylineSyncMessage::LspCompletions {
|
|
line_text,
|
|
position,
|
|
}) => {
|
|
let result = repl_session.language_server.completions(&line_text, position).await;
|
|
message_handler.send(RustylineSyncResponse::LspCompletions(result)).unwrap();
|
|
}
|
|
None => {}, // channel closed
|
|
}
|
|
|
|
poll_worker = true;
|
|
}
|
|
message = notifications.next() => {
|
|
if let Some(message) = message {
|
|
let method = message.get("method").unwrap().as_str().unwrap();
|
|
if method == "Runtime.exceptionThrown" {
|
|
let params = message.get("params").unwrap().as_object().unwrap();
|
|
let exception_details = params.get("exceptionDetails").unwrap().as_object().unwrap();
|
|
let text = exception_details.get("text").unwrap().as_str().unwrap();
|
|
let exception = exception_details.get("exception").unwrap().as_object().unwrap();
|
|
let description = exception.get("description").and_then(|d| d.as_str()).unwrap_or("undefined");
|
|
println!("{text} {description}");
|
|
}
|
|
}
|
|
}
|
|
_ = repl_session.run_event_loop(), if poll_worker => {
|
|
poll_worker = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn read_eval_file(
|
|
cli_options: &CliOptions,
|
|
file_fetcher: &FileFetcher,
|
|
eval_file: &str,
|
|
) -> Result<String, AnyError> {
|
|
let specifier =
|
|
deno_core::resolve_url_or_path(eval_file, cli_options.initial_cwd())?;
|
|
|
|
let file = file_fetcher
|
|
.fetch(&specifier, PermissionsContainer::allow_all())
|
|
.await?;
|
|
|
|
Ok((*file.source).to_string())
|
|
}
|
|
|
|
pub async fn run(flags: Flags, repl_flags: ReplFlags) -> Result<i32, AnyError> {
|
|
let factory = CliFactory::from_flags(flags).await?;
|
|
let cli_options = factory.cli_options();
|
|
let main_module = cli_options.resolve_main_module()?;
|
|
let permissions = PermissionsContainer::new(Permissions::from_options(
|
|
&cli_options.permissions_options(),
|
|
)?);
|
|
let npm_resolver = factory.npm_resolver().await?.clone();
|
|
let resolver = factory.resolver().await?.clone();
|
|
let dir = factory.deno_dir()?;
|
|
let file_fetcher = factory.file_fetcher()?;
|
|
let worker_factory = factory.create_cli_main_worker_factory().await?;
|
|
|
|
let mut worker = worker_factory
|
|
.create_main_worker(main_module, permissions)
|
|
.await?;
|
|
worker.setup_repl().await?;
|
|
let worker = worker.into_main_worker();
|
|
let mut repl_session =
|
|
ReplSession::initialize(cli_options, npm_resolver, resolver, worker)
|
|
.await?;
|
|
let mut rustyline_channel = rustyline_channel();
|
|
|
|
let helper = EditorHelper {
|
|
context_id: repl_session.context_id,
|
|
sync_sender: rustyline_channel.0,
|
|
};
|
|
|
|
let history_file_path = dir.repl_history_file_path();
|
|
let editor = ReplEditor::new(helper, history_file_path)?;
|
|
|
|
if let Some(eval_files) = repl_flags.eval_files {
|
|
for eval_file in eval_files {
|
|
match read_eval_file(cli_options, file_fetcher, &eval_file).await {
|
|
Ok(eval_source) => {
|
|
let output = repl_session
|
|
.evaluate_line_and_get_output(&eval_source)
|
|
.await;
|
|
// only output errors
|
|
if let EvaluationOutput::Error(error_text) = output {
|
|
println!("Error in --eval-file file \"{eval_file}\": {error_text}");
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!("Error in --eval-file file \"{eval_file}\": {e}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(eval) = repl_flags.eval {
|
|
let output = repl_session.evaluate_line_and_get_output(&eval).await;
|
|
// only output errors
|
|
if let EvaluationOutput::Error(error_text) = output {
|
|
println!("Error in --eval flag: {error_text}");
|
|
}
|
|
}
|
|
|
|
// Doing this manually, instead of using `log::info!` because these messages
|
|
// are supposed to go to stdout, not stderr.
|
|
if !cli_options.is_quiet() {
|
|
println!("Deno {}", crate::version::deno());
|
|
println!("exit using ctrl+d, ctrl+c, or close()");
|
|
if repl_flags.is_default_command {
|
|
println!(
|
|
"{}",
|
|
colors::yellow("REPL is running with all permissions allowed.")
|
|
);
|
|
println!("To specify permissions, run `deno repl` with allow flags.")
|
|
}
|
|
}
|
|
|
|
loop {
|
|
let line = read_line_and_poll(
|
|
&mut repl_session,
|
|
&mut rustyline_channel.1,
|
|
editor.clone(),
|
|
)
|
|
.await;
|
|
match line {
|
|
Ok(line) => {
|
|
editor.set_should_exit_on_interrupt(false);
|
|
editor.update_history(line.clone());
|
|
let output = repl_session.evaluate_line_and_get_output(&line).await;
|
|
|
|
// We check for close and break here instead of making it a loop condition to get
|
|
// consistent behavior in when the user evaluates a call to close().
|
|
if repl_session.closing().await? {
|
|
break;
|
|
}
|
|
|
|
println!("{output}");
|
|
}
|
|
Err(ReadlineError::Interrupted) => {
|
|
if editor.should_exit_on_interrupt() {
|
|
break;
|
|
}
|
|
editor.set_should_exit_on_interrupt(true);
|
|
println!("press ctrl+c again to exit");
|
|
continue;
|
|
}
|
|
Err(ReadlineError::Eof) => {
|
|
break;
|
|
}
|
|
Err(err) => {
|
|
println!("Error: {err:?}");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(repl_session.worker.exit_code())
|
|
}
|