Skip to content

Commit 6f53a34

Browse files
authored
[VSCode] CDS Training run (#1828)
* [VSCode] Adjustments for CDS with Java 25+ Signed-off-by: BoykoAlex <alex.boyko@broadcom.com> * [VSCode] Allow use to finish training run Signed-off-by: BoykoAlex <alex.boyko@broadcom.com> * [VSCode] Cleanup and finalize Signed-off-by: BoykoAlex <alex.boyko@broadcom.com> * [VSCode] ADjust timeout Signed-off-by: BoykoAlex <alex.boyko@broadcom.com> --------- Signed-off-by: BoykoAlex <alex.boyko@broadcom.com>
1 parent 2485c7c commit 6f53a34

File tree

2 files changed

+147
-52
lines changed

2 files changed

+147
-52
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
'use strict';
2+
3+
import {
4+
ExtensionContext,
5+
ProgressLocation,
6+
Uri,
7+
commands,
8+
window,
9+
workspace
10+
} from 'vscode';
11+
import * as Path from 'path';
12+
import * as Crypto from 'crypto';
13+
import { LanguageClient, State } from 'vscode-languageclient/node';
14+
import { JVM } from '@pivotal-tools/jvm-launch-utils';
15+
import { ActivatorOptions } from './launch-util';
16+
17+
const LOG_AOT_VM_ARG_PREFIX = '-Xlog:aot';
18+
const AOT_CACHE_OUTPUT_PREFIX = '-XX:AOTCacheOutput=';
19+
const AOT_CACHE_PREFIX = '-XX:AOTCache=';
20+
const MIN_JAVA_VERSION = 25;
21+
22+
export class CdsResult {
23+
private readonly _trainingRun: boolean;
24+
25+
constructor(readonly cdsArgs: string[]) {
26+
this._trainingRun = cdsArgs.some(a => a.startsWith(AOT_CACHE_OUTPUT_PREFIX));
27+
}
28+
29+
get isTrainingRun(): boolean {
30+
return this._trainingRun;
31+
}
32+
}
33+
34+
export class CdsSupport {
35+
private readonly aotCachePath: string;
36+
37+
constructor(
38+
private readonly options: ActivatorOptions,
39+
private readonly context: ExtensionContext,
40+
private readonly jvm: JVM,
41+
private readonly useSocket: boolean
42+
) {
43+
this.aotCachePath = CdsSupport.computeAotCachePath(context.extensionPath, jvm, options.extensionId);
44+
}
45+
46+
async prepareArgs(): Promise<CdsResult> {
47+
if (!this.options.workspaceOptions.get('cds.enabled')) {
48+
return new CdsResult([]);
49+
}
50+
51+
if (this.jvm.getMajorVersion() < MIN_JAVA_VERSION) {
52+
window.showInformationMessage(
53+
`${this.options.extensionId}: CDS is enabled but requires Java 25+. ` +
54+
`Current Java version is ${this.jvm.getMajorVersion()}. Starting without CDS.`
55+
);
56+
return new CdsResult([]);
57+
}
58+
59+
const cdsArgs: string[] = [];
60+
61+
if (await this.cacheExists()) {
62+
this.options.clientOptions.outputChannel.appendLine(`CDS: Using existing AOT cache: ${this.aotCachePath}`);
63+
cdsArgs.push(`${AOT_CACHE_PREFIX}${this.aotCachePath}`);
64+
} else {
65+
this.options.clientOptions.outputChannel.appendLine(`CDS: No AOT cache found, will record cache on this run: ${this.aotCachePath}`);
66+
cdsArgs.push(`${AOT_CACHE_OUTPUT_PREFIX}${this.aotCachePath}`);
67+
}
68+
69+
if (!this.useSocket) {
70+
cdsArgs.push(`${LOG_AOT_VM_ARG_PREFIX}*=off`);
71+
}
72+
73+
return new CdsResult(cdsArgs);
74+
}
75+
76+
handleTrainingRun(client: LanguageClient): void {
77+
const disposable = client.onDidChangeState(e => {
78+
if (e.newState === State.Running) {
79+
disposable.dispose();
80+
setTimeout(() => {
81+
window.showInformationMessage(
82+
`${this.options.extensionId}: CDS training run in progress. Click "Finish Training Run" when done to reload the window with the AOT cache applied.`,
83+
'Finish Training Run'
84+
).then(selection => {
85+
if (selection === 'Finish Training Run') {
86+
window.withProgress({
87+
location: ProgressLocation.Notification,
88+
title: `${this.options.extensionId}: CDS`,
89+
cancellable: false
90+
}, async progress => {
91+
progress.report({ message: 'Stopping Language Server...' });
92+
await client.stop();
93+
await this.waitForAotCache(progress);
94+
}).then(() => commands.executeCommand('workbench.action.reloadWindow'));
95+
}
96+
});
97+
}, 600000); // 10 mins timeout in ms
98+
}
99+
});
100+
this.context.subscriptions.push(disposable);
101+
}
102+
103+
private async cacheExists(): Promise<boolean> {
104+
try {
105+
await workspace.fs.stat(Uri.file(this.aotCachePath));
106+
return true;
107+
} catch {
108+
return false;
109+
}
110+
}
111+
112+
private async waitForAotCache(progress: { report(value: { message?: string }): void }): Promise<void> {
113+
const maxWaitMs = 30000;
114+
const intervalMs = 1000;
115+
let waited = 0;
116+
while (waited < maxWaitMs) {
117+
if (await this.cacheExists()) {
118+
progress.report({ message: 'AOT cache ready, reloading window...' });
119+
return;
120+
}
121+
waited += intervalMs;
122+
progress.report({ message: `Waiting for AOT cache... (${waited / 1000}s)` });
123+
await new Promise(resolve => setTimeout(resolve, intervalMs));
124+
}
125+
progress.report({ message: 'Timed out waiting for AOT cache.' });
126+
}
127+
128+
private static computeAotCachePath(extensionPath: string, jvm: JVM, extensionId: string): string {
129+
const hash = Crypto.createHash('sha256').update(jvm.getJavaHome()).digest('hex').substring(0, 12);
130+
return Path.join(extensionPath, 'language-server', `${extensionId}_${hash}.aot`);
131+
}
132+
}

vscode-extensions/commons-vscode/src/launch-util.ts

Lines changed: 15 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
workspace
1717
} from 'vscode';
1818
import * as Path from 'path';
19-
import * as Crypto from 'crypto';
2019
import PortFinder from 'portfinder';
2120
import * as Net from 'net';
2221
import * as CommonsCommands from './commands';
@@ -27,13 +26,13 @@ import * as P2C from 'vscode-languageclient/lib/common/protocolConverter';
2726
import {HighlightService, HighlightParams} from './highlight-service';
2827
import { JVM, findJvm, findJdk } from '@pivotal-tools/jvm-launch-utils';
2928
import {HighlightCodeLensProvider} from "./code-lens-service";
29+
import { CdsSupport, CdsResult } from './cds';
3030

3131
const p2c = P2C.createConverter(undefined, false, false);
3232

3333
PortFinder.basePort = 45556;
3434

3535
const LOG_RESOLVE_VM_ARG_PREFIX = '-Xlog:jni+resolve=';
36-
const LOG_AOT_VM_ARG_PREFIX = '-Xlog:aot';
3736
const DEBUG_ARG = '-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=y';
3837

3938
async function fileExists(filePath: string): Promise<boolean> {
@@ -154,56 +153,12 @@ async function findJdtEmbeddedJRE(): Promise<string | undefined> {
154153
}
155154
}
156155

157-
function hashString(value: string): string {
158-
return Crypto.createHash('sha256').update(value).digest('hex').substring(0, 12);
159-
}
160-
161-
function getAotCachePath(extensionPath: string, jvm: JVM): string {
162-
const javaHomeHash = hashString(jvm.getJavaHome());
163-
return Path.join(extensionPath, 'language-server', `spring-boot-ls_${javaHomeHash}.aot`);
164-
}
165-
166-
async function prepareCdsArgs(options: ActivatorOptions, context: ExtensionContext, jvm: JVM, useSocket: boolean): Promise<CdsResult> {
167-
if (!options.workspaceOptions.get("cds.enabled")) {
168-
return { cdsArgs: [] };
169-
}
170-
171-
if (jvm.getMajorVersion() < 25) {
172-
window.showInformationMessage(
173-
'Spring Boot Language Server: CDS is enabled but requires Java 25+. ' +
174-
`Current Java version is ${jvm.getMajorVersion()}. Starting without CDS.`
175-
);
176-
return { cdsArgs: [] };
177-
}
178-
179-
const aotCachePath = getAotCachePath(context.extensionPath, jvm);
180-
const cdsArgs: string[] = [];
181-
182-
if (await fileExists(aotCachePath)) {
183-
options.clientOptions.outputChannel.appendLine(`CDS: Using existing AOT cache: ${aotCachePath}`);
184-
cdsArgs.push(`-XX:AOTCache=${aotCachePath}`);
185-
} else {
186-
options.clientOptions.outputChannel.appendLine(`CDS: No AOT cache found, will record cache on this run: ${aotCachePath}`);
187-
cdsArgs.push(`-XX:AOTCacheOutput=${aotCachePath}`);
188-
}
189-
190-
if (!useSocket) {
191-
cdsArgs.push(`${LOG_AOT_VM_ARG_PREFIX}*=off`);
192-
}
193-
194-
return { cdsArgs };
195-
}
196-
197-
interface CdsResult {
198-
cdsArgs: string[];
199-
}
200-
201156
function addCdsArgs(vmArgs: string[], cdsResult?: CdsResult): void {
202157
if (!cdsResult?.cdsArgs.length) {
203158
return;
204159
}
205160
for (const arg of cdsResult.cdsArgs) {
206-
if (arg.startsWith(LOG_AOT_VM_ARG_PREFIX) && hasVmArg(LOG_AOT_VM_ARG_PREFIX, vmArgs)) {
161+
if (arg.startsWith('-Xlog:aot') && hasVmArg('-Xlog:aot', vmArgs)) {
207162
continue;
208163
}
209164
vmArgs.push(arg);
@@ -246,13 +201,21 @@ export async function activate(options: ActivatorOptions, context: ExtensionCont
246201
clientOptions.outputChannel.appendLine("isJavaEightOrHigher => true");
247202

248203
const useSocket = !!process.env['SPRING_LS_USE_SOCKET'];
249-
const cdsResult = await prepareCdsArgs(options, context, jvm, useSocket);
204+
const cds = new CdsSupport(options, context, jvm, useSocket);
205+
const cdsResult = await cds.prepareArgs();
250206

207+
let client: LanguageClient;
251208
if (useSocket) {
252-
return setupLanguageClient(context, await createServerOptionsForPortComm(options, context, jvm, cdsResult), options);
209+
client = await setupLanguageClient(context, await createServerOptionsForPortComm(options, context, jvm, cdsResult), options);
253210
} else {
254-
return setupLanguageClient(context, await createServerOptions(options, context, jvm, undefined, cdsResult), options);
211+
client = await setupLanguageClient(context, await createServerOptions(options, context, jvm, undefined, cdsResult), options);
255212
}
213+
214+
if (cdsResult.isTrainingRun) {
215+
cds.handleTrainingRun(client);
216+
}
217+
218+
return client;
256219
}
257220

258221
async function createServerOptions(options: ActivatorOptions, context: ExtensionContext, jvm: JVM, port?: number, cdsResult?: CdsResult): Promise<Executable> {
@@ -385,8 +348,8 @@ async function addCpAndLauncherToJvmArgs(args: string[], options: ActivatorOptio
385348
}
386349
}
387350

388-
function hasHeapArg(_vmargs?: string[]) : boolean {
389-
return hasVmArg('-Xmx');
351+
function hasHeapArg(vmargs?: string[]) : boolean {
352+
return hasVmArg('-Xmx', vmargs);
390353
}
391354

392355
function hasVmArg(argPrefix: string, vmargs?: string[]): boolean {

0 commit comments

Comments
 (0)