From c5d44858fffa6fa0be57eb9822eac89c040bc93d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 22 Mar 2017 11:24:19 +0100 Subject: [PATCH] better fix for #21709 --- .../workbench/api/node/mainThreadDocuments.ts | 74 +++++++++---------- .../test/node/api/mainThreadDocuments.test.ts | 64 ++++++++++++++++ 2 files changed, 101 insertions(+), 37 deletions(-) create mode 100644 src/vs/workbench/test/node/api/mainThreadDocuments.test.ts diff --git a/src/vs/workbench/api/node/mainThreadDocuments.ts b/src/vs/workbench/api/node/mainThreadDocuments.ts index 6e0e80af9db..95fd38718ce 100644 --- a/src/vs/workbench/api/node/mainThreadDocuments.ts +++ b/src/vs/workbench/api/node/mainThreadDocuments.ts @@ -7,7 +7,6 @@ import URI from 'vs/base/common/uri'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { EmitterEvent } from 'vs/base/common/eventEmitter'; -import { setDisposableTimeout } from 'vs/base/common/async'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle'; @@ -25,48 +24,51 @@ import { MainThreadDocumentsAndEditors } from './mainThreadDocumentsAndEditors'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { ITextEditorModel } from 'vs/workbench/common/editor'; -class TimeoutReference { +export class BoundModelReferenceCollection { - private static _delay = 1000 * 60 * 3; - - private _timer: IDisposable; - private _disposed = false; + private _data = new Array<{ length: number, dispose(): void }>(); + private _length = 0; constructor( - readonly codeEditorService: ICodeEditorService, - readonly editorGroupService: IEditorGroupService, - readonly reference: IReference + private _maxAge: number = 1000 * 60 * 3, + private _maxLength: number = 1024 * 1024 * 80 ) { - - const check = () => { - if (!this.isUsed()) { - this.dispose(); - } else { - this._timer = setDisposableTimeout(check, TimeoutReference._delay); - } - }; - this._timer = setDisposableTimeout(check, TimeoutReference._delay); + // } dispose(): void { - if (!this._disposed) { - this._disposed = true; - dispose(this.reference, this._timer); - } + this._data = dispose(this._data); } - private isUsed(): boolean { - for (const editor of this.codeEditorService.listCodeEditors()) { - if (editor.getModel() === this.reference.object.textEditorModel) { - return true; + add(ref: IReference): void { + let length = ref.object.textEditorModel.getValueLength(); + let handle: number; + let entry: { length: number, dispose(): void }; + const dispose = () => { + let idx = this._data.indexOf(entry); + if (idx >= 0) { + this._length -= length; + ref.dispose(); + clearTimeout(handle); + this._data.splice(idx, 1); } + }; + handle = setTimeout(dispose, this._maxAge); + entry = { length, dispose }; + + this._data.push(entry); + this._length += length; + this._cleanup(); + } + + private _cleanup(): void { + let diff = this._length - this._maxLength; + let i: number; + for (i = 0; i < this._data.length && diff > 0; i++) { + diff -= this._data[i].length; + this._data[i].dispose(); + i -= 1; } - for (const group of this.editorGroupService.getStacksModel().groups) { - if (group.contains(this.reference.object.textEditorModel.uri)) { - return true; - } - } - return false; } } @@ -86,6 +88,7 @@ export class MainThreadDocuments extends MainThreadDocumentsShape { private _proxy: ExtHostDocumentsShape; private _modelIsSynced: { [modelId: string]: boolean; }; private _resourceContentProvider: { [handle: number]: IDisposable }; + private _modelReferenceCollection = new BoundModelReferenceCollection(); constructor( documentsAndEditors: MainThreadDocumentsAndEditors, @@ -113,6 +116,7 @@ export class MainThreadDocuments extends MainThreadDocumentsShape { this._modelIsSynced = {}; this._toDispose = []; + this._toDispose.push(this._modelReferenceCollection); this._toDispose.push(documentsAndEditors.onDocumentAdd(models => models.forEach(this._onModelAdded, this))); this._toDispose.push(documentsAndEditors.onDocumentRemove(urls => urls.forEach(this._onModelRemoved, this))); modelService.onModelModeChanged(this._onModelModeChanged, this, this._toDispose); @@ -234,11 +238,7 @@ export class MainThreadDocuments extends MainThreadDocumentsShape { private _handleAsResourceInput(uri: URI): TPromise { return this._textModelResolverService.createModelReference(uri).then(ref => { - // TimeoutReference will check every 3 min if the - // reference is still in use. This is quite harsh to - // extensions but we don't want them to make us hold - // on to model indefinitely - this._toDispose.push(new TimeoutReference(this._codeEditorService, this._editorGroupService, ref)); + this._modelReferenceCollection.add(ref); const result = !!ref.object; return result; }); diff --git a/src/vs/workbench/test/node/api/mainThreadDocuments.test.ts b/src/vs/workbench/test/node/api/mainThreadDocuments.test.ts new file mode 100644 index 00000000000..a7c3a5f8933 --- /dev/null +++ b/src/vs/workbench/test/node/api/mainThreadDocuments.test.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as assert from 'assert'; +import { BoundModelReferenceCollection } from 'vs/workbench/api/node/mainThreadDocuments'; +import { Model } from 'vs/editor/common/model/model'; +import { TPromise } from "vs/base/common/winjs.base"; + +suite('BoundModelReferenceCollection', () => { + + let col = new BoundModelReferenceCollection(15, 75); + + teardown(() => { + col.dispose(); + }); + + test('max age', () => { + + let didDispose = false; + + col.add({ + object: { textEditorModel: Model.createFromString('farboo') }, + dispose() { + didDispose = true; + } + }); + + return TPromise.timeout(30).then(() => { + assert.equal(didDispose, true); + }); + }); + + test('max size', () => { + + let disposed: number[] = []; + + col.add({ + object: { textEditorModel: Model.createFromString('farboo') }, + dispose() { + disposed.push(0); + } + }); + col.add({ + object: { textEditorModel: Model.createFromString('boofar') }, + dispose() { + disposed.push(1); + } + }); + + col.add({ + object: { textEditorModel: Model.createFromString(new Array(71).join('x')) }, + dispose() { + disposed.push(2); + } + }); + + assert.deepEqual(disposed, [0, 1]); + }); + +});