mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 21:13:37 +00:00
Auto merge of #18281 - darichey:async-subprocess, r=lnicola
Run subprocesses async in vscode extension Extensions should not block the vscode extension host. Replace uses of `spawnSync` with `spawnAsync`, a convenience wrapper around `spawn`. These `spawnSync`s are unlikely to cause a real issue in practice, because they spawn very short-lived processes, so we aren't blocking for very long. That said, blocking the extension host is poor practice, and if they _do_ block for too long for whatever reason, vscode becomes useless.
This commit is contained in:
commit
d7628c0a8b
3 changed files with 83 additions and 32 deletions
|
@ -1,9 +1,9 @@
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
import * as os from "os";
|
import * as os from "os";
|
||||||
import type { Config } from "./config";
|
import type { Config } from "./config";
|
||||||
import { type Env, log } from "./util";
|
import { type Env, log, spawnAsync } from "./util";
|
||||||
import type { PersistentState } from "./persistent_state";
|
import type { PersistentState } from "./persistent_state";
|
||||||
import { exec, spawnSync } from "child_process";
|
import { exec } from "child_process";
|
||||||
import { TextDecoder } from "node:util";
|
import { TextDecoder } from "node:util";
|
||||||
|
|
||||||
export async function bootstrap(
|
export async function bootstrap(
|
||||||
|
@ -61,13 +61,12 @@ async function getServer(
|
||||||
// if so, use the rust-analyzer component
|
// if so, use the rust-analyzer component
|
||||||
const toolchainUri = vscode.Uri.joinPath(workspaceFolder.uri, "rust-toolchain.toml");
|
const toolchainUri = vscode.Uri.joinPath(workspaceFolder.uri, "rust-toolchain.toml");
|
||||||
if (await hasToolchainFileWithRaDeclared(toolchainUri)) {
|
if (await hasToolchainFileWithRaDeclared(toolchainUri)) {
|
||||||
const res = spawnSync("rustup", ["which", "rust-analyzer"], {
|
const res = await spawnAsync("rustup", ["which", "rust-analyzer"], {
|
||||||
encoding: "utf8",
|
|
||||||
env: { ...process.env },
|
env: { ...process.env },
|
||||||
cwd: workspaceFolder.uri.fsPath,
|
cwd: workspaceFolder.uri.fsPath,
|
||||||
});
|
});
|
||||||
if (!res.error && res.status === 0) {
|
if (!res.error && res.status === 0) {
|
||||||
toolchainServerPath = earliestToolchainPath(
|
toolchainServerPath = await earliestToolchainPath(
|
||||||
toolchainServerPath,
|
toolchainServerPath,
|
||||||
res.stdout.trim(),
|
res.stdout.trim(),
|
||||||
raVersionResolver,
|
raVersionResolver,
|
||||||
|
@ -114,10 +113,8 @@ async function getServer(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given a path to a rust-analyzer executable, resolve its version and return it.
|
// Given a path to a rust-analyzer executable, resolve its version and return it.
|
||||||
function raVersionResolver(path: string): string | undefined {
|
async function raVersionResolver(path: string): Promise<string | undefined> {
|
||||||
const res = spawnSync(path, ["--version"], {
|
const res = await spawnAsync(path, ["--version"]);
|
||||||
encoding: "utf8",
|
|
||||||
});
|
|
||||||
if (!res.error && res.status === 0) {
|
if (!res.error && res.status === 0) {
|
||||||
return res.stdout;
|
return res.stdout;
|
||||||
} else {
|
} else {
|
||||||
|
@ -126,13 +123,16 @@ function raVersionResolver(path: string): string | undefined {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given a path to two rust-analyzer executables, return the earliest one by date.
|
// Given a path to two rust-analyzer executables, return the earliest one by date.
|
||||||
function earliestToolchainPath(
|
async function earliestToolchainPath(
|
||||||
path0: string | undefined,
|
path0: string | undefined,
|
||||||
path1: string,
|
path1: string,
|
||||||
raVersionResolver: (path: string) => string | undefined,
|
raVersionResolver: (path: string) => Promise<string | undefined>,
|
||||||
): string {
|
): Promise<string> {
|
||||||
if (path0) {
|
if (path0) {
|
||||||
if (orderFromPath(path0, raVersionResolver) < orderFromPath(path1, raVersionResolver)) {
|
if (
|
||||||
|
(await orderFromPath(path0, raVersionResolver)) <
|
||||||
|
(await orderFromPath(path1, raVersionResolver))
|
||||||
|
) {
|
||||||
return path0;
|
return path0;
|
||||||
} else {
|
} else {
|
||||||
return path1;
|
return path1;
|
||||||
|
@ -150,11 +150,11 @@ function earliestToolchainPath(
|
||||||
// nightly - /Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer
|
// nightly - /Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer
|
||||||
// versioned - /Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer
|
// versioned - /Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer
|
||||||
// stable - /Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer
|
// stable - /Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer
|
||||||
function orderFromPath(
|
async function orderFromPath(
|
||||||
path: string,
|
path: string,
|
||||||
raVersionResolver: (path: string) => string | undefined,
|
raVersionResolver: (path: string) => Promise<string | undefined>,
|
||||||
): string {
|
): Promise<string> {
|
||||||
const raVersion = raVersionResolver(path);
|
const raVersion = await raVersionResolver(path);
|
||||||
const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/);
|
const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/);
|
||||||
if (raDate?.length === 2) {
|
if (raDate?.length === 2) {
|
||||||
const precedence = path.includes("nightly-") ? "0" : "1";
|
const precedence = path.includes("nightly-") ? "0" : "1";
|
||||||
|
@ -184,11 +184,10 @@ async function hasToolchainFileWithRaDeclared(uri: vscode.Uri): Promise<boolean>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidExecutable(path: string, extraEnv: Env): boolean {
|
export async function isValidExecutable(path: string, extraEnv: Env): Promise<boolean> {
|
||||||
log.debug("Checking availability of a binary at", path);
|
log.debug("Checking availability of a binary at", path);
|
||||||
|
|
||||||
const res = spawnSync(path, ["--version"], {
|
const res = await spawnAsync(path, ["--version"], {
|
||||||
encoding: "utf8",
|
|
||||||
env: { ...process.env, ...extraEnv },
|
env: { ...process.env, ...extraEnv },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
import { strict as nativeAssert } from "assert";
|
import { strict as nativeAssert } from "assert";
|
||||||
import { exec, type ExecOptions } from "child_process";
|
import { exec, spawn, type SpawnOptionsWithoutStdio, type ExecOptions } from "child_process";
|
||||||
import { inspect } from "util";
|
import { inspect } from "util";
|
||||||
import type { CargoRunnableArgs, ShellRunnableArgs } from "./lsp_ext";
|
import type { CargoRunnableArgs, ShellRunnableArgs } from "./lsp_ext";
|
||||||
|
|
||||||
|
@ -233,3 +233,55 @@ export function expectNotUndefined<T>(input: Undefinable<T>, msg: string): NotUn
|
||||||
export function unwrapUndefinable<T>(input: Undefinable<T>): NotUndefined<T> {
|
export function unwrapUndefinable<T>(input: Undefinable<T>): NotUndefined<T> {
|
||||||
return expectNotUndefined(input, `unwrapping \`undefined\``);
|
return expectNotUndefined(input, `unwrapping \`undefined\``);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SpawnAsyncReturns {
|
||||||
|
stdout: string;
|
||||||
|
stderr: string;
|
||||||
|
status: number | null;
|
||||||
|
error?: Error | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function spawnAsync(
|
||||||
|
path: string,
|
||||||
|
args?: ReadonlyArray<string>,
|
||||||
|
options?: SpawnOptionsWithoutStdio,
|
||||||
|
): Promise<SpawnAsyncReturns> {
|
||||||
|
const child = spawn(path, args, options);
|
||||||
|
const stdout: Array<Buffer> = [];
|
||||||
|
const stderr: Array<Buffer> = [];
|
||||||
|
try {
|
||||||
|
const res = await new Promise<{ stdout: string; stderr: string; status: number | null }>(
|
||||||
|
(resolve, reject) => {
|
||||||
|
child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
|
||||||
|
child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk)));
|
||||||
|
child.on("error", (error) =>
|
||||||
|
reject({
|
||||||
|
stdout: Buffer.concat(stdout).toString("utf8"),
|
||||||
|
stderr: Buffer.concat(stderr).toString("utf8"),
|
||||||
|
error,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
child.on("close", (status) =>
|
||||||
|
resolve({
|
||||||
|
stdout: Buffer.concat(stdout).toString("utf8"),
|
||||||
|
stderr: Buffer.concat(stderr).toString("utf8"),
|
||||||
|
status,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
stdout: res.stdout,
|
||||||
|
stderr: res.stderr,
|
||||||
|
status: res.status,
|
||||||
|
};
|
||||||
|
} catch (e: any) {
|
||||||
|
return {
|
||||||
|
stdout: e.stdout,
|
||||||
|
stderr: e.stderr,
|
||||||
|
status: e.status,
|
||||||
|
error: e.error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,9 +6,9 @@ export async function getTests(ctx: Context) {
|
||||||
await ctx.suite("Bootstrap/Select toolchain RA", (suite) => {
|
await ctx.suite("Bootstrap/Select toolchain RA", (suite) => {
|
||||||
suite.addTest("Order of nightly RA", async () => {
|
suite.addTest("Order of nightly RA", async () => {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
_private.orderFromPath(
|
await _private.orderFromPath(
|
||||||
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
|
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
|
||||||
function (path: string) {
|
async function (path: string) {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
path,
|
path,
|
||||||
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
|
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
|
||||||
|
@ -22,9 +22,9 @@ export async function getTests(ctx: Context) {
|
||||||
|
|
||||||
suite.addTest("Order of versioned RA", async () => {
|
suite.addTest("Order of versioned RA", async () => {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
_private.orderFromPath(
|
await _private.orderFromPath(
|
||||||
"/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer",
|
"/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer",
|
||||||
function (path: string) {
|
async function (path: string) {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
path,
|
path,
|
||||||
"/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer",
|
"/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer",
|
||||||
|
@ -38,9 +38,9 @@ export async function getTests(ctx: Context) {
|
||||||
|
|
||||||
suite.addTest("Order of versioned RA when unable to obtain version date", async () => {
|
suite.addTest("Order of versioned RA when unable to obtain version date", async () => {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
_private.orderFromPath(
|
await _private.orderFromPath(
|
||||||
"/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer",
|
"/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer",
|
||||||
function () {
|
async function () {
|
||||||
return "rust-analyzer 1.72.1";
|
return "rust-analyzer 1.72.1";
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -50,9 +50,9 @@ export async function getTests(ctx: Context) {
|
||||||
|
|
||||||
suite.addTest("Order of stable RA", async () => {
|
suite.addTest("Order of stable RA", async () => {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
_private.orderFromPath(
|
await _private.orderFromPath(
|
||||||
"/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer",
|
"/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer",
|
||||||
function (path: string) {
|
async function (path: string) {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
path,
|
path,
|
||||||
"/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer",
|
"/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer",
|
||||||
|
@ -66,7 +66,7 @@ export async function getTests(ctx: Context) {
|
||||||
|
|
||||||
suite.addTest("Order with invalid path to RA", async () => {
|
suite.addTest("Order with invalid path to RA", async () => {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
_private.orderFromPath("some-weird-path", function () {
|
await _private.orderFromPath("some-weird-path", async function () {
|
||||||
return undefined;
|
return undefined;
|
||||||
}),
|
}),
|
||||||
"2",
|
"2",
|
||||||
|
@ -75,10 +75,10 @@ export async function getTests(ctx: Context) {
|
||||||
|
|
||||||
suite.addTest("Earliest RA between nightly and stable", async () => {
|
suite.addTest("Earliest RA between nightly and stable", async () => {
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
_private.earliestToolchainPath(
|
await _private.earliestToolchainPath(
|
||||||
"/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer",
|
"/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer",
|
||||||
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
|
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
|
||||||
function (path: string) {
|
async function (path: string) {
|
||||||
if (
|
if (
|
||||||
path ===
|
path ===
|
||||||
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer"
|
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer"
|
||||||
|
|
Loading…
Reference in a new issue