Skip to content

Commit 389aa8a

Browse files
authored
Fix MdDocumentInfoCache computing values twice (microsoft#152799)
* Fix MdDocumentInfoCache computing values twice Fixes a race where values could be computed twice before being cached * Remove only
1 parent 3406329 commit 389aa8a

File tree

2 files changed

+69
-14
lines changed

2 files changed

+69
-14
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as assert from 'assert';
7+
import 'mocha';
8+
import { InMemoryDocument } from '../util/inMemoryDocument';
9+
import { MdDocumentInfoCache } from '../util/workspaceCache';
10+
import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace';
11+
import { workspacePath } from './util';
12+
13+
suite('DocumentInfoCache', () => {
14+
test('Repeated calls should only compute value once', async () => {
15+
const doc = workspacePath('doc.md');
16+
const workspace = new InMemoryWorkspaceMarkdownDocuments([
17+
new InMemoryDocument(doc, '')
18+
]);
19+
20+
let i = 0;
21+
const cache = new MdDocumentInfoCache<number>(workspace, async () => {
22+
return ++i;
23+
});
24+
25+
const a = cache.get(doc);
26+
const b = cache.get(doc);
27+
28+
assert.strictEqual(await a, 1);
29+
assert.strictEqual(i, 1);
30+
assert.strictEqual(await b, 1);
31+
assert.strictEqual(i, 1);
32+
});
33+
});

extensions/markdown-language-features/src/util/workspaceCache.ts

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,48 +43,70 @@ class LazyResourceMap<T> {
4343
export class MdDocumentInfoCache<T> extends Disposable {
4444

4545
private readonly _cache = new LazyResourceMap<T>();
46+
private readonly _loadingDocuments = new ResourceMap<Promise<SkinnyTextDocument | undefined>>();
4647

4748
public constructor(
4849
private readonly workspaceContents: MdWorkspaceContents,
4950
private readonly getValue: (document: SkinnyTextDocument) => Promise<T>,
5051
) {
5152
super();
5253

53-
this._register(this.workspaceContents.onDidChangeMarkdownDocument(doc => this.onDidChangeDocument(doc)));
54-
this._register(this.workspaceContents.onDidCreateMarkdownDocument(doc => this.onDidChangeDocument(doc)));
54+
this._register(this.workspaceContents.onDidChangeMarkdownDocument(doc => this.invalidate(doc)));
5555
this._register(this.workspaceContents.onDidDeleteMarkdownDocument(this.onDidDeleteDocument, this));
5656
}
5757

5858
public async get(resource: vscode.Uri): Promise<T | undefined> {
59-
const existing = this._cache.get(resource);
59+
let existing = this._cache.get(resource);
6060
if (existing) {
6161
return existing;
6262
}
6363

64-
const doc = await this.workspaceContents.getOrLoadMarkdownDocument(resource);
65-
return doc && this.onDidChangeDocument(doc, true)?.value;
64+
const doc = await this.loadDocument(resource);
65+
if (!doc) {
66+
return undefined;
67+
}
68+
69+
// Check if we have invalidated
70+
existing = this._cache.get(resource);
71+
if (existing) {
72+
return existing;
73+
}
74+
75+
return this.resetEntry(doc)?.value;
6676
}
6777

6878
public async getForDocument(document: SkinnyTextDocument): Promise<T> {
6979
const existing = this._cache.get(document.uri);
7080
if (existing) {
7181
return existing;
7282
}
83+
return this.resetEntry(document).value;
84+
}
7385

74-
return this.onDidChangeDocument(document, true)!.value;
86+
private loadDocument(resource: vscode.Uri): Promise<SkinnyTextDocument | undefined> {
87+
const existing = this._loadingDocuments.get(resource);
88+
if (existing) {
89+
return existing;
90+
}
91+
92+
const p = this.workspaceContents.getOrLoadMarkdownDocument(resource);
93+
this._loadingDocuments.set(resource, p);
94+
p.finally(() => {
95+
this._loadingDocuments.delete(resource);
96+
});
97+
return p;
7598
}
7699

77-
public async entries(): Promise<Array<[vscode.Uri, T]>> {
78-
return this._cache.entries();
100+
private resetEntry(document: SkinnyTextDocument): Lazy<Promise<T>> {
101+
const value = lazy(() => this.getValue(document));
102+
this._cache.set(document.uri, value);
103+
return value;
79104
}
80105

81-
private onDidChangeDocument(document: SkinnyTextDocument, forceAdd = false): Lazy<Promise<T>> | undefined {
82-
if (forceAdd || this._cache.has(document.uri)) {
83-
const value = lazy(() => this.getValue(document));
84-
this._cache.set(document.uri, value);
85-
return value;
106+
private invalidate(document: SkinnyTextDocument): void {
107+
if (this._cache.has(document.uri)) {
108+
this.resetEntry(document);
86109
}
87-
return undefined;
88110
}
89111

90112
private onDidDeleteDocument(resource: vscode.Uri) {

0 commit comments

Comments
 (0)