Skip to content

Commit 4afde40

Browse files
angelozerrdatho7561
authored andcommitted
perf: optimize Java Language Server startup time
Reduce startup time from ~72s to ~49ms by eliminating unnecessary subprocess calls, system scans, and disk I/O on the critical path: - Read JDK release file instead of spawning java -version subprocess in getMajorVersion() (was ~33s per call) - Skip listJdks() system scan in resolveRequirements when toolingJre is valid - defer JDK discovery after server startup via didChangeConfiguration notification (was ~16s) - Defer addAutoDetectedJdks in getJavaConfig until after server start by adding skipAutoDetection parameter - Use context.extension.packageJSON.version instead of reading and parsing package.json from disk in getVersion() (3 calls at startup) Signed-off-by: azerr <azerr@redhat.com>
1 parent 777cc2c commit 4afde40

5 files changed

Lines changed: 25 additions & 31 deletions

File tree

‎src/extension.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ export async function activate(context: ExtensionContext): Promise<ExtensionAPI>
224224
const requireStandardServer = (serverMode !== ServerMode.lightWeight) && (!isDebugModeByClientPort || !!process.env['JDTLS_CLIENT_PORT']);
225225
let initFailureReported: boolean = false;
226226

227-
const javaConfig = await getJavaConfig(requirementsData.java_home);
227+
const javaConfig = await getJavaConfig(requirementsData.java_home, true);
228228
javaConfigDeferred.resolve(javaConfig);
229229

230230
// Options to control the language client
@@ -790,6 +790,12 @@ async function doStartStandardServer(context: ExtensionContext, requirements: re
790790
await standardClient.initialize(context, requirements, clientOptions, workspacePath, jdtEventEmitter);
791791
standardClient.start().then(async () => {
792792
standardClient.registerLanguageClientActions(context, await fse.pathExists(path.join(workspacePath, ".metadata", ".plugins")), jdtEventEmitter);
793+
// Send auto-detected JDKs after server startup to avoid blocking initialization
794+
standardClient.getClient().sendNotification(DidChangeConfigurationNotification.type, {
795+
settings: {
796+
java: await getJavaConfig(requirements.java_home),
797+
}
798+
});
793799
});
794800
serverStatusBarProvider.setBusy("Activating...");
795801
return standardClient.getClient();
@@ -1269,7 +1275,7 @@ async function cleanJavaWorkspaceStorage() {
12691275
}
12701276

12711277
async function cleanOldGlobalStorage(context: ExtensionContext) {
1272-
const currentVersion = getVersion(context.extensionPath);
1278+
const currentVersion = getVersion(context);
12731279
const globalStoragePath = context.globalStorageUri?.fsPath; // .../Code/User/globalStorage/redhat.java
12741280

12751281
ensureExists(globalStoragePath);

‎src/javaServerStarter.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ function prepareParams(requirements: RequirementsData, workspacePath, context: E
297297
}
298298

299299
const hasJDWP = params.find((param: string) => param.includes('jdwp')) !== undefined;
300-
const extVersion = getVersion(context.extensionPath);
300+
const extVersion = getVersion(context);
301301
const globalStoragePath = path.resolve(context.globalStorageUri?.fsPath, extVersion); // .../Code/User/globalStorage/redhat.java/1.42.0/
302302
const appCDSMode = workspace.getConfiguration().get('java.jdt.ls.appcds.enabled');
303303
const useAppCDS = (appCDSMode === 'on') || (appCDSMode === 'auto' && (isPrereleaseOrInsiderVersion(context)));
@@ -396,7 +396,7 @@ export function getSharedIndexCache(context: ExtensionContext): string {
396396

397397
function resolveConfiguration(context, configDir) {
398398
ensureExists(context.globalStoragePath);
399-
const version = getVersion(context.extensionPath);
399+
const version = getVersion(context);
400400
let configuration = path.resolve(context.globalStoragePath, version);
401401
ensureExists(configuration);
402402
configuration = path.resolve(configuration, configDir);

‎src/jdkUtils.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { existsSync } from 'fs';
44
import { IJavaRuntime, findRuntimes, getSources } from 'jdk-utils';
55
import { join } from 'path';
6-
import { ExtensionContext, Uri, workspace } from 'vscode';
6+
import { ExtensionContext } from 'vscode';
77

88
let cachedJdks: IJavaRuntime[];
99
let cachedJreNames: string[];
@@ -14,8 +14,7 @@ export async function loadSupportedJreNames(context: ExtensionContext): Promise<
1414
}
1515

1616
async function getContributesConfiguration(context: ExtensionContext, configId: string): Promise<any> {
17-
const buffer = await workspace.fs.readFile(Uri.file(context.asAbsolutePath("package.json")));
18-
const packageJson = JSON.parse(buffer.toString());
17+
const packageJson = context.extension.packageJSON;
1918
/**
2019
* contributes.configuration can either be a single object,
2120
* representing a single category of settings, or an array

‎src/requirements.ts‎

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import expandHomeDir from 'expand-home-dir';
44
import * as fse from 'fs-extra';
5-
import { getRuntime, getSources, JAVAC_FILENAME, JAVA_FILENAME } from 'jdk-utils';
5+
import { getRuntime, JAVAC_FILENAME, JAVA_FILENAME } from 'jdk-utils';
66
import * as path from 'path';
77
import { env, ExtensionContext, Uri, window, workspace } from 'vscode';
88
import { Commands } from './commands';
@@ -74,10 +74,10 @@ export async function resolveRequirements(context: ExtensionContext): Promise<Re
7474
}
7575
}
7676

77-
// search valid JDKs from env.JAVA_HOME, env.PATH, SDKMAN, jEnv, jabba, Common directories
78-
const javaRuntimes = await listJdks();
7977
if (!toolingJre) { // universal version
78+
// search valid JDKs from env.JAVA_HOME, env.PATH, SDKMAN, jEnv, jabba, Common directories
8079
// as latest version as possible.
80+
const javaRuntimes = await listJdks();
8181
sortJdksByVersion(javaRuntimes);
8282
const validJdks = javaRuntimes.filter(r => r.version.major >= REQUIRED_JDK_VERSION);
8383
if (validJdks.length > 0) {
@@ -99,16 +99,12 @@ export async function resolveRequirements(context: ExtensionContext): Promise<Re
9999
*/
100100
if (javaHome) {
101101
logger.info(`Use the JDK from '${preferenceName}' setting as the initial default project JDK.`);
102-
} else if (javaRuntimes.length) {
103-
sortJdksBySource(javaRuntimes);
104-
javaHome = javaRuntimes[0].homedir;
105-
javaVersion = javaRuntimes[0].version?.major;
106-
logger.info(`Use the JDK from '${getSources(javaRuntimes[0])}' as the initial default project JDK.`);
107102
} else if (javaHome = await findDefaultRuntimeFromSettings()) {
108103
javaVersion = await getMajorVersion(javaHome);
109104
logger.info("Use the JDK from 'java.configuration.runtimes' as the initial default project JDK.");
110105
} else {
111-
openJDKDownload(reject, "Please download and install a JDK to compile your project. You can configure your projects with different JDKs by the setting ['java.configuration.runtimes'](https://github.com/redhat-developer/vscode-java/wiki/JDK-Requirements#java.configuration.runtimes)");
106+
javaHome = toolingJre;
107+
javaVersion = toolingJreVersion;
112108
}
113109
}
114110

‎src/utils.ts‎

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ function getDirectoriesByBuildFile(inclusions: string[], exclusions: string[], f
220220

221221
const detectJdksAtStart: boolean = getJavaConfiguration().get<boolean>('configuration.detectJdksAtStart');
222222

223-
export async function getJavaConfig(javaHome: string) {
223+
export async function getJavaConfig(javaHome: string, skipAutoDetection?: boolean) {
224224
const origConfig = getJavaConfiguration();
225225
const javaConfig = JSON.parse(JSON.stringify(origConfig));
226226
javaConfig.home = javaHome;
@@ -274,7 +274,7 @@ export async function getJavaConfig(javaHome: string) {
274274
}
275275

276276
javaConfig.telemetry = { enabled: workspace.getConfiguration('redhat.telemetry').get('enabled', false) };
277-
if (detectJdksAtStart) {
277+
if (detectJdksAtStart && !skipAutoDetection) {
278278
const userConfiguredJREs: any[] = javaConfig.configuration.runtimes;
279279
javaConfig.configuration.runtimes = await addAutoDetectedJdks(userConfiguredJREs);
280280
}
@@ -344,14 +344,8 @@ export function resolveActualCause(callstack: any): any {
344344
return callstack;
345345
}
346346

347-
export function getVersion(extensionPath: string): string {
348-
const packagePath = path.resolve(extensionPath, "package.json");
349-
const packageFile = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
350-
if (packageFile) {
351-
return packageFile.version;
352-
}
353-
354-
return '0.0.0';
347+
export function getVersion(context: ExtensionContext): string {
348+
return context.extension.packageJSON?.version || '0.0.0';
355349
}
356350

357351
export function getVSCodeVariablesMap(): any {
@@ -368,19 +362,18 @@ export function getVSCodeVariablesMap(): any {
368362
* @param context The extension context or extension path
369363
* @returns true if the version is a pre-release version or running an insider editor
370364
*/
371-
export function isPrereleaseOrInsiderVersion(context: ExtensionContext | string): boolean {
365+
export function isPrereleaseOrInsiderVersion(context: ExtensionContext): boolean {
372366
return isInsiderEditor() || isPreReleaseVersion(context);
373367
}
374368

375369
/**
376370
* Check if the extension version is a pre-release version.
377371
* Pre-release versions follow the pattern: major.minor.timestamp (e.g., 1.47.1234567890)
378-
* @param context The extension context or extension path
372+
* @param context The extension context
379373
* @returns true if the version is a pre-release version
380374
*/
381-
export function isPreReleaseVersion(context: ExtensionContext | string): boolean {
382-
const extensionPath = typeof context === 'string' ? context : context.extensionPath;
383-
const extVersion = getVersion(extensionPath);
375+
export function isPreReleaseVersion(context: ExtensionContext): boolean {
376+
const extVersion = getVersion(context);
384377
return /^\d+\.\d+\.\d{10}/.test(extVersion);
385378
}
386379

0 commit comments

Comments
 (0)