Skip to content

Commit 6afb9d8

Browse files
committed
updated cacheService interface and some other minor changes
1 parent 672f95f commit 6afb9d8

16 files changed

+749
-90
lines changed

vscode/src/telemetry/events/baseEvent.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
import { LOGGER } from "../../logger";
1717
import { AnonymousIdManager } from "../impl/AnonymousIdManager";
18-
import { cacheService } from "../impl/cacheServiceImpl";
18+
import { cacheServiceIndex } from "../impl/cache";
1919
import { getHashCode, getValuesToBeTransformed, transformValue } from "../utils";
2020

2121
export interface BaseEventPayload {
@@ -66,7 +66,7 @@ export abstract class BaseEvent<T> {
6666
protected addEventToCache = (): void => {
6767
const dataString = JSON.stringify(this.getData);
6868
const calculatedHashVal = getHashCode(dataString);
69-
cacheService.put(this.NAME, calculatedHashVal).then((isAdded: boolean) => {
69+
cacheServiceIndex.simpleCache.put(this.NAME, calculatedHashVal).then((isAdded: boolean) => {
7070
LOGGER.debug(`${this.NAME} added in cache ${isAdded ? "Successfully" : "Unsucessfully"}`);
7171
});
7272
}

vscode/src/telemetry/events/start.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
import { globalState } from "../../globalState";
1717
import { LOGGER } from "../../logger";
18-
import { cacheService } from "../impl/cacheServiceImpl";
18+
import { cacheServiceIndex } from "../impl/cache";
1919
import { getEnvironmentInfo } from "../impl/enviromentDetails";
2020
import { getHashCode } from "../utils";
2121
import { BaseEvent } from "./baseEvent";
@@ -67,7 +67,7 @@ export class ExtensionStartEvent extends BaseEvent<StartEventData> {
6767

6868
public static builder = (): ExtensionStartEvent | null => {
6969
const startEventData = getEnvironmentInfo(globalState.getExtensionContextInfo());
70-
const cachedValue: string | undefined = cacheService.get(this.NAME);
70+
const cachedValue: string | undefined = cacheServiceIndex.simpleCache.get(this.NAME);
7171
const envString = JSON.stringify(startEventData);
7272
const newValue = getHashCode(envString);
7373

vscode/src/telemetry/events/workspaceChange.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
16+
import { randomUUID } from "crypto";
1617
import { LOGGER } from "../../logger";
1718
import { Telemetry } from "../telemetry";
1819
import { BaseEvent } from "./baseEvent";
20+
import { cacheServiceIndex } from "../impl/cache";
21+
import { ProjectCacheValue } from "../impl/cache/projectCacheValue";
1922

2023
interface ProjectInfo {
2124
id: string;
@@ -40,9 +43,30 @@ export class WorkspaceChangeEvent extends BaseEvent<WorkspaceChangeData> {
4043
private static readonly propertiesToTransform = ['javaVersion'];
4144

4245
constructor(payload: WorkspaceChangeData) {
43-
const updatedPayload: WorkspaceChangeData = BaseEvent.transformEvent(WorkspaceChangeEvent.propertiesToTransform, payload);
46+
const updatedPayload: WorkspaceChangeData = WorkspaceChangeEvent.transformPayload(payload);
4447
super(WorkspaceChangeEvent.NAME, WorkspaceChangeEvent.ENDPOINT, updatedPayload);
4548
}
49+
50+
private static transformPayload = (payload: WorkspaceChangeData) => {
51+
const transformedPayload: WorkspaceChangeData = BaseEvent.transformEvent(WorkspaceChangeEvent.propertiesToTransform, payload);
52+
return WorkspaceChangeEvent.updateProjectId(transformedPayload)
53+
}
54+
55+
private static updateProjectId = (payload: WorkspaceChangeData) => {
56+
const updatedProjectInfo = payload.projectInfo.map(project => {
57+
const existingId = cacheServiceIndex.projectCache.get(project.id);
58+
const uniqueId = existingId ?? randomUUID();
59+
60+
if (!existingId) {
61+
// Cannot be awaited because the caller is constructor and it cannot be a async call
62+
cacheServiceIndex.projectCache.put(project.id, new ProjectCacheValue(uniqueId));
63+
}
64+
65+
return { ...project, id: uniqueId };
66+
});
67+
68+
return { ...payload, projectInfo: updatedProjectInfo };
69+
}
4670

4771
public onSuccessPostEventCallback = async (): Promise<void> => {
4872
LOGGER.debug(`WorkspaceChange event sent successfully`);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright (c) 2025, Oracle and/or its affiliates.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
export type CacheValueObj<T> = {
18+
type: string;
19+
payload: T;
20+
lastUsed: number;
21+
}
22+
23+
export abstract class BaseCacheValue<T> {
24+
public readonly lastUsed: number;
25+
26+
constructor(public readonly type: string, public readonly payload: T, lastUsed?: number) {
27+
this.lastUsed = lastUsed ?? Date.now();
28+
}
29+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
Copyright (c) 2025, Oracle and/or its affiliates.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { ProjectCacheService } from "./projectCacheService";
18+
import { SimpleCacheService } from "./simpleCacheService";
19+
20+
export namespace cacheServiceIndex {
21+
export const simpleCache = new SimpleCacheService();
22+
export const projectCache = new ProjectCacheService();
23+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
Copyright (c) 2024-2025, Oracle and/or its affiliates.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
import { CacheService } from "../../types";
17+
import { LOGGER } from "../../../logger";
18+
import { globalState } from "../../../globalState";
19+
import { isError } from "../../../utils";
20+
import { removeEntriesOnOverflow } from "./utils";
21+
import { ProjectCacheValue } from "./projectCacheValue";
22+
23+
export class ProjectCacheService implements CacheService<ProjectCacheValue, String> {
24+
readonly MAX_KEYS_SIZE: number = 5000;
25+
private removingKeys: boolean = false;
26+
27+
public get = (key: string) => {
28+
try {
29+
const updatedKey = this.getUpdatedKey(key);
30+
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();
31+
32+
const value = vscGlobalState.get<ProjectCacheValue>(updatedKey);
33+
if (value) {
34+
this.put(updatedKey, ProjectCacheValue.fromObject({ ...value, lastUsed: Date.now() }));
35+
}
36+
37+
return value?.payload;
38+
} catch (err) {
39+
LOGGER.error(`Error while retrieving ${key} from cache: ${(err as Error).message}`);
40+
return undefined;
41+
}
42+
}
43+
44+
public put = async (key: string, value: ProjectCacheValue) => {
45+
try {
46+
const updatedKey = this.getUpdatedKey(key);
47+
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();
48+
49+
await vscGlobalState.update(updatedKey, value);
50+
if (vscGlobalState.keys().length > this.MAX_KEYS_SIZE) {
51+
this.removeOnOverflow();
52+
}
53+
LOGGER.debug(`Updating key: ${key} to ${value}`);
54+
55+
return true;
56+
} catch (err) {
57+
LOGGER.error(`Error while storing ${key} in cache: ${(err as Error).message}`);
58+
return false;
59+
}
60+
}
61+
62+
public removeOnOverflow = async () => {
63+
try {
64+
if (this.removingKeys) {
65+
LOGGER.log("Ignoring removing keys request, since it is already in progress");
66+
return;
67+
}
68+
this.removingKeys = true;
69+
70+
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();
71+
const comparator = (a: ProjectCacheValue, b: ProjectCacheValue) => (a.lastUsed - b.lastUsed);
72+
73+
await removeEntriesOnOverflow(vscGlobalState, ProjectCacheValue.type, comparator);
74+
} catch (error) {
75+
LOGGER.error("Some error occurred while removing keys " + (isError(error) ? error.message : error));
76+
} finally {
77+
this.removingKeys = false;
78+
}
79+
}
80+
81+
// for unit tests needs to be public
82+
public getUpdatedKey = (key: string) => `${ProjectCacheValue.type}.${key}`;
83+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright (c) 2025, Oracle and/or its affiliates.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { BaseCacheValue, CacheValueObj } from "./BaseCacheValue";
18+
19+
export class ProjectCacheValue extends BaseCacheValue<string> {
20+
public static readonly type = "prjId";
21+
22+
constructor(payload: string, lastUsed?: number){
23+
super(ProjectCacheValue.type, payload, lastUsed);
24+
}
25+
26+
public static fromObject(obj: CacheValueObj<string>): ProjectCacheValue {
27+
if (obj.type !== ProjectCacheValue.type) {
28+
throw new Error(`Invalid object type for ProjectCacheEntry: received ${obj.type}`);
29+
}
30+
const entry = new ProjectCacheValue(obj.payload, obj.lastUsed);
31+
32+
return entry;
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright (c) 2024-2025, Oracle and/or its affiliates.
2+
Copyright (c) 2025, Oracle and/or its affiliates.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -13,32 +13,32 @@
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
16-
import { CacheService } from "../types";
17-
import { LOGGER } from "../../logger";
18-
import { globalState } from "../../globalState";
1916

20-
class CacheServiceImpl implements CacheService {
21-
public get = (key: string): string | undefined => {
17+
import { globalState } from "../../../globalState";
18+
import { LOGGER } from "../../../logger";
19+
import { CacheService } from "../../types";
20+
21+
export class SimpleCacheService implements CacheService<string, string> {
22+
get(key: string) {
2223
try {
2324
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();
24-
return vscGlobalState.get(key);
25+
return vscGlobalState.get<string>(key);
2526
} catch (err) {
2627
LOGGER.error(`Error while retrieving ${key} from cache: ${(err as Error).message}`);
2728
return undefined;
2829
}
2930
}
30-
31-
public put = async (key: string, value: string): Promise<boolean> => {
31+
32+
async put(key: string, value: string) {
3233
try {
3334
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();
3435
await vscGlobalState.update(key, value);
3536
LOGGER.debug(`Updating key: ${key} to ${value}`);
37+
3638
return true;
3739
} catch (err) {
3840
LOGGER.error(`Error while storing ${key} in cache: ${(err as Error).message}`);
3941
return false;
4042
}
4143
}
4244
}
43-
44-
export const cacheService = new CacheServiceImpl();
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
Copyright (c) 2025, Oracle and/or its affiliates.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { Memento } from "vscode";
18+
import { LOGGER } from "../../../logger";
19+
import { BaseCacheValue } from "./BaseCacheValue";
20+
21+
export const removeEntriesOnOverflow = async <T>(globalCache: Memento & {
22+
setKeysForSync(keys: readonly string[]): void;
23+
},
24+
type: string,
25+
comparator: (a: BaseCacheValue<T>, b: BaseCacheValue<T>) => number
26+
) => {
27+
const allKeys = globalCache.keys();
28+
29+
const entries: (BaseCacheValue<T> & { key: string })[] = [];
30+
31+
for (const key of allKeys) {
32+
const value = globalCache.get<BaseCacheValue<T>>(key);
33+
if (value?.type === type) {
34+
entries.push({ key, ...value });
35+
}
36+
}
37+
38+
const half = Math.floor(entries.length / 2);
39+
entries.sort(comparator);
40+
const toEvict = entries.slice(0, half);
41+
LOGGER.debug(toEvict.toString());
42+
const toEvictPromises: Promise<void>[] = [];
43+
for (const entry of toEvict) {
44+
toEvictPromises.push(Promise.resolve(globalCache.update(entry.key, undefined)));
45+
}
46+
await Promise.allSettled(toEvictPromises);
47+
48+
LOGGER.debug(`Evicted ${toEvict.length} least-used cache keys due to overflow.`);
49+
}

0 commit comments

Comments
 (0)