Skip to content

Commit

Permalink
Revert "refactor: remove tsc snapshot (#27987)" (#28052)
Browse files Browse the repository at this point in the history
This reverts commit 174e496.

That commit caused panic on startup like
#28047
or #28044.

Reverting for now, until we figure out what's going wrong.
  • Loading branch information
bartlomieju authored Feb 11, 2025
1 parent 0945634 commit acdc7dc
Show file tree
Hide file tree
Showing 8 changed files with 582 additions and 601 deletions.
489 changes: 316 additions & 173 deletions cli/build.rs

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion cli/lsp/language_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ impl Inner {
ts_server.clone(),
diagnostics_state.clone(),
);
let assets = Assets::new();
let assets = Assets::new(ts_server.clone());
let initial_cwd = std::env::current_dir().unwrap_or_else(|_| {
panic!("Could not resolve current working directory")
});
Expand Down Expand Up @@ -913,6 +913,7 @@ impl Inner {
.await?;
self.ts_fixable_diagnostics = fixable_diagnostics;
}
self.assets.initialize(self.snapshot()).await;

self.performance.measure(mark);
Ok(InitializeResult {
Expand Down
127 changes: 99 additions & 28 deletions cli/lsp/tsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1399,7 +1399,7 @@ fn new_assets_map() -> Arc<Mutex<AssetsMap>> {
.map(|(k, v)| {
let url_str = format!("asset:///{k}");
let specifier = resolve_url(&url_str).unwrap();
let asset = AssetDocument::new(specifier.clone(), v.get());
let asset = AssetDocument::new(specifier.clone(), v);
(specifier, asset)
})
.collect::<AssetsMap>();
Expand Down Expand Up @@ -1430,16 +1430,29 @@ impl AssetsSnapshot {
/// multiple threads without needing to worry about race conditions.
#[derive(Debug, Clone)]
pub struct Assets {
ts_server: Arc<TsServer>,
assets: Arc<Mutex<AssetsMap>>,
}

impl Assets {
pub fn new() -> Self {
pub fn new(ts_server: Arc<TsServer>) -> Self {
Self {
ts_server,
assets: new_assets_map(),
}
}

/// Initializes with the assets in the isolate.
pub async fn initialize(&self, state_snapshot: Arc<StateSnapshot>) {
let assets = get_isolate_assets(&self.ts_server, state_snapshot).await;
let mut assets_map = self.assets.lock();
for asset in assets {
if !assets_map.contains_key(asset.specifier()) {
assets_map.insert(asset.specifier().clone(), asset);
}
}
}

pub fn snapshot(&self) -> AssetsSnapshot {
// it's ok to not make a complete copy for snapshotting purposes
// because assets are static
Expand All @@ -1464,6 +1477,39 @@ impl Assets {
}
}

/// Get all the assets stored in the tsc isolate.
async fn get_isolate_assets(
ts_server: &TsServer,
state_snapshot: Arc<StateSnapshot>,
) -> Vec<AssetDocument> {
let req = TscRequest::GetAssets;
let res: Value = ts_server
.request(state_snapshot, req, None, &Default::default())
.await
.unwrap();
let response_assets = match res {
Value::Array(value) => value,
_ => unreachable!(),
};
let mut assets = Vec::with_capacity(response_assets.len());

for asset in response_assets {
let mut obj = match asset {
Value::Object(obj) => obj,
_ => unreachable!(),
};
let specifier_str = obj.get("specifier").unwrap().as_str().unwrap();
let specifier = ModuleSpecifier::parse(specifier_str).unwrap();
let text = match obj.remove("text").unwrap() {
Value::String(text) => text,
_ => unreachable!(),
};
assets.push(AssetDocument::new(specifier, text));
}

assets
}

fn get_tag_body_text(
tag: &JsDocTagInfo,
language_server: &language_server::Inner,
Expand Down Expand Up @@ -4501,21 +4547,6 @@ fn op_is_node_file(state: &mut OpState, #[string] path: String) -> bool {
r
}

#[op2]
#[serde]
fn op_libs() -> Vec<String> {
let mut out =
Vec::with_capacity(crate::tsc::LAZILY_LOADED_STATIC_ASSETS.len());
for key in crate::tsc::LAZILY_LOADED_STATIC_ASSETS.keys() {
let lib = key
.replace("lib.", "")
.replace(".d.ts", "")
.replace("deno_", "deno.");
out.push(lib);
}
out
}

#[derive(Debug, thiserror::Error, deno_error::JsError)]
enum LoadError {
#[error("{0}")]
Expand Down Expand Up @@ -4916,13 +4947,13 @@ fn run_tsc_thread(
// supplied snapshot is an isolate that contains the TypeScript language
// server.
let mut tsc_runtime = JsRuntime::new(RuntimeOptions {
extensions: vec![deno_tsc::init_ops_and_esm(
extensions: vec![deno_tsc::init_ops(
performance,
specifier_map,
request_rx,
)],
create_params: create_isolate_create_params(),
startup_snapshot: None,
startup_snapshot: Some(tsc::compiler_snapshot()),
inspector: has_inspector_server,
..Default::default()
});
Expand Down Expand Up @@ -5004,7 +5035,6 @@ deno_core::extension!(deno_tsc,
op_script_version,
op_project_version,
op_poll_requests,
op_libs,
],
options = {
performance: Arc<Performance>,
Expand All @@ -5019,14 +5049,6 @@ deno_core::extension!(deno_tsc,
options.request_rx,
));
},
customizer = |ext: &mut deno_core::Extension| {
use deno_core::ExtensionFileSource;
ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_tsc/99_main_compiler.js", crate::tsc::MAIN_COMPILER_SOURCE.get().into()));
ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_tsc/97_ts_host.js", crate::tsc::TS_HOST_SOURCE.get().into()));
ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_tsc/98_lsp.js", crate::tsc::LSP_SOURCE.get().into()));
ext.js_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/00_typescript.js", crate::tsc::TYPESCRIPT_SOURCE.get().into()));
ext.esm_entry_point = Some("ext:deno_tsc/99_main_compiler.js");
}
);

#[derive(Debug, Clone, Deserialize_repr, Serialize_repr)]
Expand Down Expand Up @@ -5406,6 +5428,7 @@ pub struct JsNull;
#[derive(Debug, Clone, Serialize)]
pub enum TscRequest {
GetDiagnostics((Vec<String>, usize)),
GetAssets,

CleanupSemanticCache,
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6230
Expand Down Expand Up @@ -5611,6 +5634,7 @@ impl TscRequest {
("provideInlayHints", Some(serde_v8::to_v8(scope, args)?))
}
TscRequest::CleanupSemanticCache => ("cleanupSemanticCache", None),
TscRequest::GetAssets => ("$getAssets", None),
};

Ok(args)
Expand Down Expand Up @@ -5655,6 +5679,7 @@ impl TscRequest {
TscRequest::GetSignatureHelpItems(_) => "getSignatureHelpItems",
TscRequest::GetNavigateToItems(_) => "getNavigateToItems",
TscRequest::ProvideInlayHints(_) => "provideInlayHints",
TscRequest::GetAssets => "$getAssets",
}
}
}
Expand Down Expand Up @@ -6040,6 +6065,52 @@ mod tests {
);
}

#[tokio::test]
async fn test_request_assets() {
let (_, ts_server, snapshot, _) = setup(json!({}), &[]).await;
let assets = get_isolate_assets(&ts_server, snapshot).await;
let mut asset_names = assets
.iter()
.map(|a| {
a.specifier()
.to_string()
.replace("asset:///lib.", "")
.replace(".d.ts", "")
})
.collect::<Vec<_>>();
let mut expected_asset_names: Vec<String> = serde_json::from_str(
include_str!(concat!(env!("OUT_DIR"), "/lib_file_names.json")),
)
.unwrap();
asset_names.sort();

// if this test fails, update build.rs
expected_asset_names.sort();
assert_eq!(asset_names, expected_asset_names);

// get some notification when the size of the assets grows
let mut total_size = 0;
for asset in &assets {
total_size += asset.text().len();
}
assert!(total_size > 0);
#[allow(clippy::print_stderr)]
// currently as of TS 5.7, it's 3MB
if total_size > 3_500_000 {
let mut sizes = Vec::new();
for asset in &assets {
sizes.push((asset.specifier(), asset.text().len()));
}
sizes.sort_by_cached_key(|(_, size)| *size);
sizes.reverse();
for (specifier, size) in &sizes {
eprintln!("{}: {}", specifier, size);
}
eprintln!("Total size: {}", total_size);
panic!("Assets were quite large.");
}
}

#[tokio::test]
async fn test_modify_sources() {
let (temp_dir, ts_server, snapshot, cache) = setup(
Expand Down
14 changes: 14 additions & 0 deletions cli/tsc/97_ts_host.js
Original file line number Diff line number Diff line change
Expand Up @@ -855,3 +855,17 @@ const setTypesNodeIgnorableNames = new Set([
"WritableStreamDefaultWriter",
]);
ts.deno.setTypesNodeIgnorableNames(setTypesNodeIgnorableNames);

export function getAssets() {
/** @type {{ specifier: string; text: string; }[]} */
const assets = [];
for (const sourceFile of SOURCE_FILE_CACHE.values()) {
if (sourceFile.fileName.startsWith(ASSETS_URL_PREFIX)) {
assets.push({
specifier: sourceFile.fileName,
text: sourceFile.text,
});
}
}
return assets;
}
4 changes: 4 additions & 0 deletions cli/tsc/98_lsp.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
error,
filterMapDiagnostic,
fromTypeScriptDiagnostics,
getAssets,
getCreateSourceFileOptions,
host,
IS_NODE_SOURCE_FILE_CACHE,
Expand Down Expand Up @@ -445,6 +446,9 @@ function serverRequest(id, method, args, scope, maybeChange) {
ts.getSupportedCodeFixes(),
);
}
case "$getAssets": {
return respond(id, getAssets());
}
case "$getDiagnostics": {
const projectVersion = args[1];
// there's a possibility that we receive a change notification
Expand Down
32 changes: 23 additions & 9 deletions cli/tsc/99_main_compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
delete Object.prototype.__proto__;

import {
assert,
AssertionError,
ASSETS_URL_PREFIX,
debug,
filterMapDiagnostic,
fromTypeScriptDiagnostics,
getAssets,
host,
setLogDebug,
} from "./97_ts_host.js";
Expand Down Expand Up @@ -211,20 +214,31 @@ function exec({ config, debug: debugFlag, rootNames, localOnly }) {
debug("<<< exec stop");
}

const libs = ops.op_libs();
for (const lib of libs) {
const specifier = `lib.${lib}.d.ts`;
// we are using internal APIs here to "inject" our custom libraries into
// tsc, so things like `"lib": [ "deno.ns" ]` are supported.
if (!ts.libs.includes(lib)) {
ts.libs.push(lib);
ts.libMap.set(lib, specifier);
globalThis.snapshot = function (libs) {
for (const lib of libs) {
const specifier = `lib.${lib}.d.ts`;
// we are using internal APIs here to "inject" our custom libraries into
// tsc, so things like `"lib": [ "deno.ns" ]` are supported.
if (!ts.libs.includes(lib)) {
ts.libs.push(lib);
ts.libMap.set(lib, `lib.${lib}.d.ts`);
}
// we are caching in memory common type libraries that will be re-used by
// tsc on when the snapshot is restored
assert(
!!host.getSourceFile(
`${ASSETS_URL_PREFIX}${specifier}`,
ts.ScriptTarget.ESNext,
),
`failed to load '${ASSETS_URL_PREFIX}${specifier}'`,
);
}
}
};

// exposes the functions that are called by `tsc::exec()` when type
// checking TypeScript.
globalThis.exec = exec;
globalThis.getAssets = getAssets;

// exposes the functions that are called when the compiler is used as a
// language service.
Expand Down
Loading

0 comments on commit acdc7dc

Please sign in to comment.