Improve consistency of issues and diagnostics for napi calls (#60198)

### What?

get value, issues and diagnostics in a single strongly consistent call

### Why?

Before issues and diagnostics where not received strongly consistent,
which could result in stale data.

### How?



Closes PACK-2194
This commit is contained in:
Tobias Koppers 2024-01-04 13:06:45 +01:00 committed by GitHub
parent bc69a382f6
commit 5117cc6ea9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 165 additions and 50 deletions

View file

@ -1,13 +1,16 @@
use std::ops::Deref; use std::{ops::Deref, sync::Arc};
use anyhow::Result;
use napi::{bindgen_prelude::External, JsFunction}; use napi::{bindgen_prelude::External, JsFunction};
use next_api::{ use next_api::{
route::{Endpoint, WrittenEndpoint}, route::{Endpoint, WrittenEndpoint},
server_paths::ServerPath, server_paths::ServerPath,
}; };
use tracing::Instrument; use tracing::Instrument;
use turbo_tasks::Vc; use turbo_tasks::{ReadRef, Vc};
use turbopack_binding::turbopack::core::error::PrettyPrintError; use turbopack_binding::turbopack::core::{
diagnostics::PlainDiagnostic, error::PrettyPrintError, issue::PlainIssue,
};
use super::utils::{ use super::utils::{
get_diagnostics, get_issues, subscribe, NapiDiagnostic, NapiIssue, RootTask, TurbopackResult, get_diagnostics, get_issues, subscribe, NapiDiagnostic, NapiIssue, RootTask, TurbopackResult,
@ -80,7 +83,31 @@ impl Deref for ExternalEndpoint {
} }
} }
#[turbo_tasks::value(serialization = "none")]
struct WrittenEndpointWithIssues {
written: ReadRef<WrittenEndpoint>,
issues: Arc<Vec<ReadRef<PlainIssue>>>,
diagnostics: Arc<Vec<ReadRef<PlainDiagnostic>>>,
}
#[turbo_tasks::function]
async fn get_written_endpoint_with_issues(
endpoint: Vc<Box<dyn Endpoint>>,
) -> Result<Vc<WrittenEndpointWithIssues>> {
let write_to_disk = endpoint.write_to_disk();
let written = write_to_disk.strongly_consistent().await?;
let issues = get_issues(write_to_disk).await?;
let diagnostics = get_diagnostics(write_to_disk).await?;
Ok(WrittenEndpointWithIssues {
written,
issues,
diagnostics,
}
.cell())
}
#[napi] #[napi]
#[tracing::instrument(skip_all)]
pub async fn endpoint_write_to_disk( pub async fn endpoint_write_to_disk(
#[napi(ts_arg_type = "{ __napiType: \"Endpoint\" }")] endpoint: External<ExternalEndpoint>, #[napi(ts_arg_type = "{ __napiType: \"Endpoint\" }")] endpoint: External<ExternalEndpoint>,
) -> napi::Result<TurbopackResult<NapiWrittenEndpoint>> { ) -> napi::Result<TurbopackResult<NapiWrittenEndpoint>> {
@ -88,15 +115,17 @@ pub async fn endpoint_write_to_disk(
let endpoint = ***endpoint; let endpoint = ***endpoint;
let (written, issues, diags) = turbo_tasks let (written, issues, diags) = turbo_tasks
.run_once(async move { .run_once(async move {
let write_to_disk = endpoint.write_to_disk(); let WrittenEndpointWithIssues {
let written = write_to_disk.strongly_consistent().await?; written,
let issues = get_issues(write_to_disk).await?; issues,
let diags = get_diagnostics(write_to_disk).await?; diagnostics,
Ok((written, issues, diags)) } = &*get_written_endpoint_with_issues(endpoint)
.strongly_consistent()
.await?;
Ok((written.clone(), issues.clone(), diagnostics.clone()))
}) })
.await .await
.map_err(|e| napi::Error::from_reason(PrettyPrintError(&e).to_string()))?; .map_err(|e| napi::Error::from_reason(PrettyPrintError(&e).to_string()))?;
// TODO diagnostics
Ok(TurbopackResult { Ok(TurbopackResult {
result: NapiWrittenEndpoint::from(&*written), result: NapiWrittenEndpoint::from(&*written),
issues: issues.iter().map(|i| NapiIssue::from(&**i)).collect(), issues: issues.iter().map(|i| NapiIssue::from(&**i)).collect(),
@ -124,7 +153,7 @@ pub fn endpoint_server_changed_subscribe(
let diags = get_diagnostics(changed).await?; let diags = get_diagnostics(changed).await?;
Ok((issues, diags)) Ok((issues, diags))
} else { } else {
Ok((vec![], vec![])) Ok((Arc::new(vec![]), Arc::new(vec![])))
} }
} }
.instrument(tracing::info_span!("server changes subscription")) .instrument(tracing::info_span!("server changes subscription"))

View file

@ -7,8 +7,9 @@ use napi::{
JsFunction, Status, JsFunction, Status,
}; };
use next_api::{ use next_api::{
entrypoints::Entrypoints,
project::{ project::{
DefineEnv, Instrumentation, Middleware, PartialProjectOptions, ProjectContainer, DefineEnv, Instrumentation, Middleware, PartialProjectOptions, Project, ProjectContainer,
ProjectOptions, ProjectOptions,
}, },
route::{Endpoint, Route}, route::{Endpoint, Route},
@ -21,7 +22,7 @@ use tracing::Instrument;
use tracing_subscriber::{ use tracing_subscriber::{
prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry,
}; };
use turbo_tasks::{TransientInstance, TurboTasks, UpdateInfo, Vc}; use turbo_tasks::{ReadRef, TransientInstance, TurboTasks, UpdateInfo, Vc};
use turbopack_binding::{ use turbopack_binding::{
turbo::{ turbo::{
tasks_fs::{FileContent, FileSystem}, tasks_fs::{FileContent, FileSystem},
@ -29,9 +30,11 @@ use turbopack_binding::{
}, },
turbopack::{ turbopack::{
core::{ core::{
diagnostics::PlainDiagnostic,
error::PrettyPrintError, error::PrettyPrintError,
issue::PlainIssue,
source_map::{GenerateSourceMap, Token}, source_map::{GenerateSourceMap, Token},
version::{PartialUpdate, TotalUpdate, Update}, version::{PartialUpdate, TotalUpdate, Update, VersionState},
}, },
ecmascript_hmr_protocol::{ClientUpdateInstruction, ResourceIdentifier}, ecmascript_hmr_protocol::{ClientUpdateInstruction, ResourceIdentifier},
trace_utils::{ trace_utils::{
@ -424,6 +427,29 @@ struct NapiEntrypoints {
pub pages_error_endpoint: External<ExternalEndpoint>, pub pages_error_endpoint: External<ExternalEndpoint>,
} }
#[turbo_tasks::value(serialization = "none")]
struct EntrypointsWithIssues {
entrypoints: ReadRef<Entrypoints>,
issues: Arc<Vec<ReadRef<PlainIssue>>>,
diagnostics: Arc<Vec<ReadRef<PlainDiagnostic>>>,
}
#[turbo_tasks::function]
async fn get_entrypoints_with_issues(
container: Vc<ProjectContainer>,
) -> Result<Vc<EntrypointsWithIssues>> {
let entrypoints_operation = container.entrypoints();
let entrypoints = entrypoints_operation.strongly_consistent().await?;
let issues = get_issues(entrypoints_operation).await?;
let diagnostics = get_diagnostics(entrypoints_operation).await?;
Ok(EntrypointsWithIssues {
entrypoints,
issues,
diagnostics,
}
.cell())
}
#[napi(ts_return_type = "{ __napiType: \"RootTask\" }")] #[napi(ts_return_type = "{ __napiType: \"RootTask\" }")]
pub fn project_entrypoints_subscribe( pub fn project_entrypoints_subscribe(
#[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>, #[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>,
@ -436,13 +462,14 @@ pub fn project_entrypoints_subscribe(
func, func,
move || { move || {
async move { async move {
let entrypoints_operation = container.entrypoints(); let EntrypointsWithIssues {
let entrypoints = entrypoints_operation.strongly_consistent().await?; entrypoints,
issues,
let issues = get_issues(entrypoints_operation).await?; diagnostics,
let diags = get_diagnostics(entrypoints_operation).await?; } = &*get_entrypoints_with_issues(container)
.strongly_consistent()
Ok((entrypoints, issues, diags)) .await?;
Ok((entrypoints.clone(), issues.clone(), diagnostics.clone()))
} }
.instrument(tracing::info_span!("entrypoints subscription")) .instrument(tracing::info_span!("entrypoints subscription"))
}, },
@ -491,6 +518,31 @@ pub fn project_entrypoints_subscribe(
) )
} }
#[turbo_tasks::value(serialization = "none")]
struct HmrUpdateWithIssues {
update: ReadRef<Update>,
issues: Arc<Vec<ReadRef<PlainIssue>>>,
diagnostics: Arc<Vec<ReadRef<PlainDiagnostic>>>,
}
#[turbo_tasks::function]
async fn hmr_update(
project: Vc<Project>,
identifier: String,
state: Vc<VersionState>,
) -> Result<Vc<HmrUpdateWithIssues>> {
let update_operation = project.hmr_update(identifier, state);
let update = update_operation.strongly_consistent().await?;
let issues = get_issues(update_operation).await?;
let diagnostics = get_diagnostics(update_operation).await?;
Ok(HmrUpdateWithIssues {
update,
issues,
diagnostics,
}
.cell())
}
#[napi(ts_return_type = "{ __napiType: \"RootTask\" }")] #[napi(ts_return_type = "{ __napiType: \"RootTask\" }")]
pub fn project_hmr_events( pub fn project_hmr_events(
#[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>, #[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>,
@ -510,14 +562,17 @@ pub fn project_hmr_events(
let identifier = outer_identifier.clone(); let identifier = outer_identifier.clone();
let session = session.clone(); let session = session.clone();
async move { async move {
let state = project let project = project.project().resolve().await?;
.project() let state = project.hmr_version_state(identifier.clone(), session);
.hmr_version_state(identifier.clone(), session); let update = hmr_update(project, identifier, state)
let update_operation = project.project().hmr_update(identifier, state); .strongly_consistent()
let update = update_operation.strongly_consistent().await?; .await?;
let issues = get_issues(update_operation).await?; let HmrUpdateWithIssues {
let diags = get_diagnostics(update_operation).await?; update,
match &*update { issues,
diagnostics,
} = &*update;
match &**update {
Update::None => {} Update::None => {}
Update::Total(TotalUpdate { to }) => { Update::Total(TotalUpdate { to }) => {
state.set(to.clone()).await?; state.set(to.clone()).await?;
@ -526,7 +581,7 @@ pub fn project_hmr_events(
state.set(to.clone()).await?; state.set(to.clone()).await?;
} }
} }
Ok((update, issues, diags)) Ok((update.clone(), issues.clone(), diagnostics.clone()))
} }
.instrument(tracing::info_span!( .instrument(tracing::info_span!(
"HMR subscription", "HMR subscription",
@ -574,6 +629,29 @@ struct HmrIdentifiers {
pub identifiers: Vec<String>, pub identifiers: Vec<String>,
} }
#[turbo_tasks::value(serialization = "none")]
struct HmrIdentifiersWithIssues {
identifiers: ReadRef<Vec<String>>,
issues: Arc<Vec<ReadRef<PlainIssue>>>,
diagnostics: Arc<Vec<ReadRef<PlainDiagnostic>>>,
}
#[turbo_tasks::function]
async fn get_hmr_identifiers_with_issues(
container: Vc<ProjectContainer>,
) -> Result<Vc<HmrIdentifiersWithIssues>> {
let hmr_identifiers_operation = container.hmr_identifiers();
let hmr_identifiers = hmr_identifiers_operation.strongly_consistent().await?;
let issues = get_issues(hmr_identifiers_operation).await?;
let diagnostics = get_diagnostics(hmr_identifiers_operation).await?;
Ok(HmrIdentifiersWithIssues {
identifiers: hmr_identifiers,
issues,
diagnostics,
}
.cell())
}
#[napi(ts_return_type = "{ __napiType: \"RootTask\" }")] #[napi(ts_return_type = "{ __napiType: \"RootTask\" }")]
pub fn project_hmr_identifiers_subscribe( pub fn project_hmr_identifiers_subscribe(
#[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>, #[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>,
@ -585,20 +663,22 @@ pub fn project_hmr_identifiers_subscribe(
turbo_tasks.clone(), turbo_tasks.clone(),
func, func,
move || async move { move || async move {
let hmr_identifiers_operation = container.hmr_identifiers(); let HmrIdentifiersWithIssues {
let hmr_identifiers = hmr_identifiers_operation.strongly_consistent().await?; identifiers,
issues,
diagnostics,
} = &*get_hmr_identifiers_with_issues(container)
.strongly_consistent()
.await?;
let issues = get_issues(hmr_identifiers_operation).await?; Ok((identifiers.clone(), issues.clone(), diagnostics.clone()))
let diags = get_diagnostics(hmr_identifiers_operation).await?;
Ok((hmr_identifiers, issues, diags))
}, },
move |ctx| { move |ctx| {
let (hmr_identifiers, issues, diags) = ctx.value; let (identifiers, issues, diagnostics) = ctx.value;
Ok(vec![TurbopackResult { Ok(vec![TurbopackResult {
result: HmrIdentifiers { result: HmrIdentifiers {
identifiers: hmr_identifiers identifiers: identifiers
.iter() .iter()
.map(|ident| ident.to_string()) .map(|ident| ident.to_string())
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
@ -607,7 +687,10 @@ pub fn project_hmr_identifiers_subscribe(
.iter() .iter()
.map(|issue| NapiIssue::from(&**issue)) .map(|issue| NapiIssue::from(&**issue))
.collect(), .collect(),
diagnostics: diags.iter().map(|d| NapiDiagnostic::from(d)).collect(), diagnostics: diagnostics
.iter()
.map(|d| NapiDiagnostic::from(d))
.collect(),
}]) }])
}, },
) )

View file

@ -77,22 +77,24 @@ pub fn root_task_dispose(
Ok(()) Ok(())
} }
pub async fn get_issues<T: Send>(source: Vc<T>) -> Result<Vec<ReadRef<PlainIssue>>> { pub async fn get_issues<T: Send>(source: Vc<T>) -> Result<Arc<Vec<ReadRef<PlainIssue>>>> {
let issues = source.peek_issues_with_path().await?; let issues = source.peek_issues_with_path().await?;
issues.get_plain_issues().await Ok(Arc::new(issues.get_plain_issues().await?))
} }
/// Collect [turbopack::core::diagnostics::Diagnostic] from given source, /// Collect [turbopack::core::diagnostics::Diagnostic] from given source,
/// returns [turbopack::core::diagnostics::PlainDiagnostic] /// returns [turbopack::core::diagnostics::PlainDiagnostic]
pub async fn get_diagnostics<T: Send>(source: Vc<T>) -> Result<Vec<ReadRef<PlainDiagnostic>>> { pub async fn get_diagnostics<T: Send>(source: Vc<T>) -> Result<Arc<Vec<ReadRef<PlainDiagnostic>>>> {
let captured_diags = source.peek_diagnostics().await?; let captured_diags = source.peek_diagnostics().await?;
captured_diags Ok(Arc::new(
.diagnostics captured_diags
.iter() .diagnostics
.map(|d| d.into_plain()) .iter()
.try_join() .map(|d| d.into_plain())
.await .try_join()
.await?,
))
} }
#[napi(object)] #[napi(object)]

View file

@ -3,7 +3,7 @@
mod app; mod app;
mod dynamic_imports; mod dynamic_imports;
mod entrypoints; pub mod entrypoints;
mod instrumentation; mod instrumentation;
mod middleware; mod middleware;
mod pages; mod pages;

View file

@ -56,7 +56,7 @@ pub(crate) async fn create_server_actions_manifest(
) -> Result<(Vc<Box<dyn EvaluatableAsset>>, Vc<Box<dyn OutputAsset>>)> { ) -> Result<(Vc<Box<dyn EvaluatableAsset>>, Vc<Box<dyn OutputAsset>>)> {
let actions = get_actions(rsc_entry, server_reference_modules, asset_context); let actions = get_actions(rsc_entry, server_reference_modules, asset_context);
let loader = let loader =
build_server_actions_loader(project_path, page_name, actions, asset_context).await?; build_server_actions_loader(project_path, page_name.to_string(), actions, asset_context);
let evaluable = Vc::try_resolve_sidecast::<Box<dyn EvaluatableAsset>>(loader) let evaluable = Vc::try_resolve_sidecast::<Box<dyn EvaluatableAsset>>(loader)
.await? .await?
.context("loader module must be evaluatable")?; .context("loader module must be evaluatable")?;
@ -76,9 +76,10 @@ pub(crate) async fn create_server_actions_manifest(
/// The actions are reexported under a hashed name (comprised of the exporting /// The actions are reexported under a hashed name (comprised of the exporting
/// file's name and the action name). This hash matches the id sent to the /// file's name and the action name). This hash matches the id sent to the
/// client and present inside the paired manifest. /// client and present inside the paired manifest.
#[turbo_tasks::function]
async fn build_server_actions_loader( async fn build_server_actions_loader(
project_path: Vc<FileSystemPath>, project_path: Vc<FileSystemPath>,
page_name: &str, page_name: String,
actions: Vc<AllActions>, actions: Vc<AllActions>,
asset_context: Vc<Box<dyn AssetContext>>, asset_context: Vc<Box<dyn AssetContext>>,
) -> Result<Vc<Box<dyn EcmascriptChunkPlaceable>>> { ) -> Result<Vc<Box<dyn EcmascriptChunkPlaceable>>> {