Skip to content

Commit 23cd002

Browse files
authored
storage - do not eager parse extension storage (fix microsoft#163446) (microsoft#166281)
1 parent a5478bc commit 23cd002

File tree

5 files changed

+55
-12
lines changed

5 files changed

+55
-12
lines changed

src/vs/platform/extensionManagement/common/extensionStorage.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface IExtensionStorageService {
2727
readonly _serviceBrand: undefined;
2828

2929
getExtensionState(extension: IExtension | IGalleryExtension | string, global: boolean): IStringDictionary<any> | undefined;
30+
getExtensionStateRaw(extension: IExtension | IGalleryExtension | string, global: boolean): string | undefined;
3031
setExtensionState(extension: IExtension | IGalleryExtension | string, state: IStringDictionary<any> | undefined, global: boolean): void;
3132

3233
readonly onDidChangeExtensionStorageToSync: Event<void>;
@@ -43,6 +44,8 @@ export class ExtensionStorageService extends Disposable implements IExtensionSto
4344

4445
readonly _serviceBrand: undefined;
4546

47+
private static LARGE_STATE_WARNING_THRESHOLD = 512 * 1024;
48+
4649
private static toKey(extension: IExtensionIdWithVersion): string {
4750
return `extensionKeys/${adoptToGalleryExtensionId(extension.id)}@${extension.version}`;
4851
}
@@ -142,7 +145,7 @@ export class ExtensionStorageService extends Disposable implements IExtensionSto
142145

143146
getExtensionState(extension: IExtension | IGalleryExtension | string, global: boolean): IStringDictionary<any> | undefined {
144147
const extensionId = this.getExtensionId(extension);
145-
const jsonValue = this.storageService.get(extensionId, global ? StorageScope.PROFILE : StorageScope.WORKSPACE);
148+
const jsonValue = this.getExtensionStateRaw(extension, global);
146149
if (jsonValue) {
147150
try {
148151
return JSON.parse(jsonValue);
@@ -156,6 +159,17 @@ export class ExtensionStorageService extends Disposable implements IExtensionSto
156159
return undefined;
157160
}
158161

162+
getExtensionStateRaw(extension: IExtension | IGalleryExtension | string, global: boolean): string | undefined {
163+
const extensionId = this.getExtensionId(extension);
164+
const rawState = this.storageService.get(extensionId, global ? StorageScope.PROFILE : StorageScope.WORKSPACE);
165+
166+
if (rawState && rawState?.length > ExtensionStorageService.LARGE_STATE_WARNING_THRESHOLD) {
167+
this.logService.warn(`[mainThreadStorage] large extension state detected (extensionId: ${extensionId}, global: ${global}): ${rawState.length / 1024}kb. Consider to use 'storageUri' or 'globalStorageUri' to store this data on disk instead.`);
168+
}
169+
170+
return rawState;
171+
}
172+
159173
setExtensionState(extension: IExtension | IGalleryExtension | string, state: IStringDictionary<any> | undefined, global: boolean): void {
160174
const extensionId = this.getExtensionId(extension);
161175
if (state === undefined) {

src/vs/workbench/api/browser/mainThreadStorage.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ export class MainThreadStorage implements MainThreadStorageShape {
3232
this._storageListener = this._storageService.onDidChangeValue(e => {
3333
const shared = e.scope === StorageScope.PROFILE;
3434
if (shared && this._sharedStorageKeysToWatch.has(e.key)) {
35-
this._proxy.$acceptValue(shared, e.key, this._extensionStorageService.getExtensionState(e.key, shared));
35+
const rawState = this._extensionStorageService.getExtensionStateRaw(e.key, shared);
36+
if (typeof rawState === 'string') {
37+
this._proxy.$acceptValue(shared, e.key, rawState);
38+
}
3639
}
3740
});
3841
}
@@ -41,14 +44,14 @@ export class MainThreadStorage implements MainThreadStorageShape {
4144
this._storageListener.dispose();
4245
}
4346

44-
async $initializeExtensionStorage(shared: boolean, extensionId: string): Promise<object | undefined> {
47+
async $initializeExtensionStorage(shared: boolean, extensionId: string): Promise<string | undefined> {
4548

4649
await this.checkAndMigrateExtensionStorage(extensionId, shared);
4750

4851
if (shared) {
4952
this._sharedStorageKeysToWatch.set(extensionId, true);
5053
}
51-
return this._extensionStorageService.getExtensionState(extensionId, shared);
54+
return this._extensionStorageService.getExtensionStateRaw(extensionId, shared);
5255
}
5356

5457
async $setValue(shared: boolean, key: string, value: object): Promise<void> {

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ export interface MainThreadStatusBarShape extends IDisposable {
594594
}
595595

596596
export interface MainThreadStorageShape extends IDisposable {
597-
$initializeExtensionStorage(shared: boolean, extensionId: string): Promise<object | undefined>;
597+
$initializeExtensionStorage(shared: boolean, extensionId: string): Promise<string | undefined>;
598598
$setValue(shared: boolean, extensionId: string, value: object): Promise<void>;
599599
$registerExtensionStorageKeysToSync(extension: IExtensionIdWithVersion, keys: string[]): void;
600600
}
@@ -2139,7 +2139,7 @@ export interface ExtHostInteractiveShape {
21392139
}
21402140

21412141
export interface ExtHostStorageShape {
2142-
$acceptValue(shared: boolean, extensionId: string, value: object | undefined): void;
2142+
$acceptValue(shared: boolean, extensionId: string, value: string): void;
21432143
}
21442144

21452145
export interface ExtHostThemingShape {

src/vs/workbench/api/common/extHostExtensionService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
154154
this._myRegistry = new ExtensionDescriptionRegistry(
155155
filterExtensions(this._globalRegistry, myExtensionsSet)
156156
);
157-
this._storage = new ExtHostStorage(this._extHostContext);
157+
this._storage = new ExtHostStorage(this._extHostContext, this._logService);
158158
this._secretState = new ExtHostSecretState(this._extHostContext);
159159
this._storagePath = storagePath;
160160

src/vs/workbench/api/common/extHostStorage.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Emitter } from 'vs/base/common/event';
88
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
99
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
1010
import { IExtensionIdWithVersion } from 'vs/platform/extensionManagement/common/extensionStorage';
11+
import { ILogService } from 'vs/platform/log/common/log';
1112

1213
export interface IStorageChangeEvent {
1314
shared: boolean;
@@ -24,24 +25,49 @@ export class ExtHostStorage implements ExtHostStorageShape {
2425
private readonly _onDidChangeStorage = new Emitter<IStorageChangeEvent>();
2526
readonly onDidChangeStorage = this._onDidChangeStorage.event;
2627

27-
constructor(mainContext: IExtHostRpcService) {
28+
constructor(
29+
mainContext: IExtHostRpcService,
30+
private readonly _logService: ILogService
31+
) {
2832
this._proxy = mainContext.getProxy(MainContext.MainThreadStorage);
2933
}
3034

3135
registerExtensionStorageKeysToSync(extension: IExtensionIdWithVersion, keys: string[]): void {
3236
this._proxy.$registerExtensionStorageKeysToSync(extension, keys);
3337
}
3438

35-
initializeExtensionStorage(shared: boolean, key: string, defaultValue?: object): Promise<object | undefined> {
36-
return this._proxy.$initializeExtensionStorage(shared, key).then(value => value || defaultValue);
39+
async initializeExtensionStorage(shared: boolean, key: string, defaultValue?: object): Promise<object | undefined> {
40+
const value = await this._proxy.$initializeExtensionStorage(shared, key);
41+
42+
let parsedValue: object | undefined;
43+
if (value) {
44+
parsedValue = this.safeParseValue(shared, key, value);
45+
}
46+
47+
return parsedValue || defaultValue;
3748
}
3849

3950
setValue(shared: boolean, key: string, value: object): Promise<void> {
4051
return this._proxy.$setValue(shared, key, value);
4152
}
4253

43-
$acceptValue(shared: boolean, key: string, value: object): void {
44-
this._onDidChangeStorage.fire({ shared, key, value });
54+
$acceptValue(shared: boolean, key: string, value: string): void {
55+
const parsedValue = this.safeParseValue(shared, key, value);
56+
if (parsedValue) {
57+
this._onDidChangeStorage.fire({ shared, key, value: parsedValue });
58+
}
59+
}
60+
61+
private safeParseValue(shared: boolean, key: string, value: string): object | undefined {
62+
try {
63+
return JSON.parse(value);
64+
} catch (error) {
65+
// Do not fail this call but log it for diagnostics
66+
// https://github.com/microsoft/vscode/issues/132777
67+
this._logService.error(`[extHostStorage] unexpected error parsing storage contents (extensionId: ${key}, global: ${shared}): ${error}`);
68+
}
69+
70+
return undefined;
4571
}
4672
}
4773

0 commit comments

Comments
 (0)