mirror of
https://github.com/Microsoft/vscode
synced 2024-08-25 03:56:03 +00:00
chore - add IInstantiationService#dispose
(#209421)
This commit is contained in:
parent
e773c56fe7
commit
03ef5ffe3f
|
@ -281,8 +281,8 @@ export interface IDisposable {
|
|||
/**
|
||||
* Check if `thing` is {@link IDisposable disposable}.
|
||||
*/
|
||||
export function isDisposable<E extends object>(thing: E): thing is E & IDisposable {
|
||||
return typeof (<IDisposable>thing).dispose === 'function' && (<IDisposable>thing).dispose.length === 0;
|
||||
export function isDisposable<E extends any>(thing: E): thing is E & IDisposable {
|
||||
return typeof thing === 'object' && thing !== null && typeof (<IDisposable><any>thing).dispose === 'function' && (<IDisposable><any>thing).dispose.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -63,6 +63,16 @@ export interface IInstantiationService {
|
|||
* and adds/overwrites the given services.
|
||||
*/
|
||||
createChild(services: ServiceCollection): IInstantiationService;
|
||||
|
||||
/**
|
||||
* Disposes this instantiation service.
|
||||
*
|
||||
* - Will dispose all services that this instantiation service has created.
|
||||
* - Will dispose all its children but not its parent.
|
||||
* - Will NOT dispose services-instances that this service has been created with
|
||||
* - Will NOT dispose consumer-instances this service has created
|
||||
*/
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { GlobalIdleValue } from 'vs/base/common/async';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { illegalState } from 'vs/base/common/errors';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { dispose, IDisposable, isDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { SyncDescriptor, SyncDescriptor0 } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { Graph } from 'vs/platform/instantiation/common/graph';
|
||||
import { GetLeadingNonServiceArgs, IInstantiationService, ServiceIdentifier, ServicesAccessor, _util } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
@ -32,6 +32,10 @@ export class InstantiationService implements IInstantiationService {
|
|||
readonly _globalGraph?: Graph<string>;
|
||||
private _globalGraphImplicitDependency?: string;
|
||||
|
||||
private _isDisposed = false;
|
||||
private readonly _servicesToMaybeDispose = new Set<any>();
|
||||
private readonly _children = new Set<InstantiationService>();
|
||||
|
||||
constructor(
|
||||
private readonly _services: ServiceCollection = new ServiceCollection(),
|
||||
private readonly _strict: boolean = false,
|
||||
|
@ -43,11 +47,45 @@ export class InstantiationService implements IInstantiationService {
|
|||
this._globalGraph = _enableTracing ? _parent?._globalGraph ?? new Graph(e => e) : undefined;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (!this._isDisposed) {
|
||||
this._isDisposed = true;
|
||||
// dispose all child services
|
||||
dispose(this._children);
|
||||
this._children.clear();
|
||||
|
||||
// dispose all services created by this service
|
||||
for (const candidate of this._servicesToMaybeDispose) {
|
||||
if (isDisposable(candidate)) {
|
||||
candidate.dispose();
|
||||
}
|
||||
}
|
||||
this._servicesToMaybeDispose.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private _throwIfDisposed(): void {
|
||||
if (this._isDisposed) {
|
||||
throw new Error('InstantiationService has been disposed');
|
||||
}
|
||||
}
|
||||
|
||||
createChild(services: ServiceCollection): IInstantiationService {
|
||||
return new InstantiationService(services, this._strict, this, this._enableTracing);
|
||||
this._throwIfDisposed();
|
||||
|
||||
const result = new class extends InstantiationService {
|
||||
override dispose(): void {
|
||||
this._children.delete(result);
|
||||
super.dispose();
|
||||
}
|
||||
}(services, this._strict, this, this._enableTracing);
|
||||
this._children.add(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
invokeFunction<R, TS extends any[] = []>(fn: (accessor: ServicesAccessor, ...args: TS) => R, ...args: TS): R {
|
||||
this._throwIfDisposed();
|
||||
|
||||
const _trace = Trace.traceInvocation(this._enableTracing, fn);
|
||||
let _done = false;
|
||||
try {
|
||||
|
@ -75,6 +113,8 @@ export class InstantiationService implements IInstantiationService {
|
|||
createInstance<T>(descriptor: SyncDescriptor0<T>): T;
|
||||
createInstance<Ctor extends new (...args: any[]) => any, R extends InstanceType<Ctor>>(ctor: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;
|
||||
createInstance(ctorOrDescriptor: any | SyncDescriptor<any>, ...rest: any[]): any {
|
||||
this._throwIfDisposed();
|
||||
|
||||
let _trace: Trace;
|
||||
let result: any;
|
||||
if (ctorOrDescriptor instanceof SyncDescriptor) {
|
||||
|
@ -119,11 +159,11 @@ export class InstantiationService implements IInstantiationService {
|
|||
return Reflect.construct<any, T>(ctor, args.concat(serviceArgs));
|
||||
}
|
||||
|
||||
private _setServiceInstance<T>(id: ServiceIdentifier<T>, instance: T): void {
|
||||
private _setCreatedServiceInstance<T>(id: ServiceIdentifier<T>, instance: T): void {
|
||||
if (this._services.get(id) instanceof SyncDescriptor) {
|
||||
this._services.set(id, instance);
|
||||
} else if (this._parent) {
|
||||
this._parent._setServiceInstance(id, instance);
|
||||
this._parent._setCreatedServiceInstance(id, instance);
|
||||
} else {
|
||||
throw new Error('illegalState - setting UNKNOWN service instance');
|
||||
}
|
||||
|
@ -221,7 +261,7 @@ export class InstantiationService implements IInstantiationService {
|
|||
if (instanceOrDesc instanceof SyncDescriptor) {
|
||||
// create instance and overwrite the service collections
|
||||
const instance = this._createServiceInstanceWithOwner(data.id, data.desc.ctor, data.desc.staticArguments, data.desc.supportsDelayedInstantiation, data._trace);
|
||||
this._setServiceInstance(data.id, instance);
|
||||
this._setCreatedServiceInstance(data.id, instance);
|
||||
}
|
||||
graph.removeNode(data);
|
||||
}
|
||||
|
@ -231,7 +271,7 @@ export class InstantiationService implements IInstantiationService {
|
|||
|
||||
private _createServiceInstanceWithOwner<T>(id: ServiceIdentifier<T>, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace): T {
|
||||
if (this._services.get(id) instanceof SyncDescriptor) {
|
||||
return this._createServiceInstance(id, ctor, args, supportsDelayedInstantiation, _trace);
|
||||
return this._createServiceInstance(id, ctor, args, supportsDelayedInstantiation, _trace, this._servicesToMaybeDispose);
|
||||
} else if (this._parent) {
|
||||
return this._parent._createServiceInstanceWithOwner(id, ctor, args, supportsDelayedInstantiation, _trace);
|
||||
} else {
|
||||
|
@ -239,10 +279,12 @@ export class InstantiationService implements IInstantiationService {
|
|||
}
|
||||
}
|
||||
|
||||
private _createServiceInstance<T>(id: ServiceIdentifier<T>, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace): T {
|
||||
private _createServiceInstance<T>(id: ServiceIdentifier<T>, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace, disposeBucket: Set<any>): T {
|
||||
if (!supportsDelayedInstantiation) {
|
||||
// eager instantiation
|
||||
return this._createInstance(ctor, args, _trace);
|
||||
const result = this._createInstance<T>(ctor, args, _trace);
|
||||
disposeBucket.add(result);
|
||||
return result;
|
||||
|
||||
} else {
|
||||
const child = new InstantiationService(undefined, this._strict, this, this._enableTracing);
|
||||
|
@ -274,7 +316,7 @@ export class InstantiationService implements IInstantiationService {
|
|||
}
|
||||
}
|
||||
earlyListeners.clear();
|
||||
|
||||
disposeBucket.add(result);
|
||||
return result;
|
||||
});
|
||||
return <T>new Proxy(Object.create(null), {
|
||||
|
|
|
@ -655,5 +655,163 @@ suite('Instantiation Service', () => {
|
|||
assert.strictEqual(eventCount, 1);
|
||||
});
|
||||
|
||||
|
||||
test('Dispose services it created', function () {
|
||||
let disposedA = false;
|
||||
let disposedB = false;
|
||||
|
||||
const A = createDecorator<A>('A');
|
||||
interface A {
|
||||
_serviceBrand: undefined;
|
||||
value: 1;
|
||||
}
|
||||
class AImpl implements A {
|
||||
_serviceBrand: undefined;
|
||||
value: 1 = 1;
|
||||
dispose() {
|
||||
disposedA = true;
|
||||
}
|
||||
}
|
||||
|
||||
const B = createDecorator<B>('B');
|
||||
interface B {
|
||||
_serviceBrand: undefined;
|
||||
value: 1;
|
||||
}
|
||||
class BImpl implements B {
|
||||
_serviceBrand: undefined;
|
||||
value: 1 = 1;
|
||||
dispose() {
|
||||
disposedB = true;
|
||||
}
|
||||
}
|
||||
|
||||
const insta = new InstantiationService(new ServiceCollection(
|
||||
[A, new SyncDescriptor(AImpl, undefined, true)],
|
||||
[B, new BImpl()],
|
||||
), true, undefined, true);
|
||||
|
||||
class Consumer {
|
||||
constructor(
|
||||
@A public readonly a: A,
|
||||
@B public readonly b: B
|
||||
) {
|
||||
assert.strictEqual(a.value, b.value);
|
||||
}
|
||||
}
|
||||
|
||||
const c: Consumer = insta.createInstance(Consumer);
|
||||
|
||||
insta.dispose();
|
||||
assert.ok(c);
|
||||
assert.strictEqual(disposedA, true);
|
||||
assert.strictEqual(disposedB, false);
|
||||
});
|
||||
|
||||
test('Disposed service cannot be used anymore', function () {
|
||||
|
||||
|
||||
const B = createDecorator<B>('B');
|
||||
interface B {
|
||||
_serviceBrand: undefined;
|
||||
value: 1;
|
||||
}
|
||||
class BImpl implements B {
|
||||
_serviceBrand: undefined;
|
||||
value: 1 = 1;
|
||||
}
|
||||
|
||||
const insta = new InstantiationService(new ServiceCollection(
|
||||
[B, new BImpl()],
|
||||
), true, undefined, true);
|
||||
|
||||
class Consumer {
|
||||
constructor(
|
||||
@B public readonly b: B
|
||||
) {
|
||||
assert.strictEqual(b.value, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const c: Consumer = insta.createInstance(Consumer);
|
||||
assert.ok(c);
|
||||
|
||||
insta.dispose();
|
||||
|
||||
assert.throws(() => insta.createInstance(Consumer));
|
||||
assert.throws(() => insta.invokeFunction(accessor => { }));
|
||||
assert.throws(() => insta.createChild(new ServiceCollection()));
|
||||
});
|
||||
|
||||
test('Child does not dispose parent', function () {
|
||||
|
||||
const B = createDecorator<B>('B');
|
||||
interface B {
|
||||
_serviceBrand: undefined;
|
||||
value: 1;
|
||||
}
|
||||
class BImpl implements B {
|
||||
_serviceBrand: undefined;
|
||||
value: 1 = 1;
|
||||
}
|
||||
|
||||
const insta1 = new InstantiationService(new ServiceCollection(
|
||||
[B, new BImpl()],
|
||||
), true, undefined, true);
|
||||
|
||||
const insta2 = insta1.createChild(new ServiceCollection());
|
||||
|
||||
class Consumer {
|
||||
constructor(
|
||||
@B public readonly b: B
|
||||
) {
|
||||
assert.strictEqual(b.value, 1);
|
||||
}
|
||||
}
|
||||
|
||||
assert.ok(insta1.createInstance(Consumer));
|
||||
assert.ok(insta2.createInstance(Consumer));
|
||||
|
||||
insta2.dispose();
|
||||
|
||||
assert.ok(insta1.createInstance(Consumer)); // parent NOT disposed by child
|
||||
assert.throws(() => insta2.createInstance(Consumer));
|
||||
});
|
||||
|
||||
test('Parent does dispose children', function () {
|
||||
|
||||
const B = createDecorator<B>('B');
|
||||
interface B {
|
||||
_serviceBrand: undefined;
|
||||
value: 1;
|
||||
}
|
||||
class BImpl implements B {
|
||||
_serviceBrand: undefined;
|
||||
value: 1 = 1;
|
||||
}
|
||||
|
||||
const insta1 = new InstantiationService(new ServiceCollection(
|
||||
[B, new BImpl()],
|
||||
), true, undefined, true);
|
||||
|
||||
const insta2 = insta1.createChild(new ServiceCollection());
|
||||
|
||||
class Consumer {
|
||||
constructor(
|
||||
@B public readonly b: B
|
||||
) {
|
||||
assert.strictEqual(b.value, 1);
|
||||
}
|
||||
}
|
||||
|
||||
assert.ok(insta1.createInstance(Consumer));
|
||||
assert.ok(insta2.createInstance(Consumer));
|
||||
|
||||
insta1.dispose();
|
||||
|
||||
assert.throws(() => insta2.createInstance(Consumer)); // child is disposed by parent
|
||||
assert.throws(() => insta1.createInstance(Consumer));
|
||||
});
|
||||
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
});
|
||||
|
|
|
@ -21,7 +21,7 @@ export class TestInstantiationService extends InstantiationService implements ID
|
|||
|
||||
private _servciesMap: Map<ServiceIdentifier<any>, any>;
|
||||
|
||||
constructor(private _serviceCollection: ServiceCollection = new ServiceCollection(), strict: boolean = false, parent?: TestInstantiationService) {
|
||||
constructor(private _serviceCollection: ServiceCollection = new ServiceCollection(), strict: boolean = false, parent?: TestInstantiationService, private _properDispose?: boolean) {
|
||||
super(_serviceCollection, strict, parent);
|
||||
|
||||
this._servciesMap = new Map<ServiceIdentifier<any>, any>();
|
||||
|
@ -130,8 +130,11 @@ export class TestInstantiationService extends InstantiationService implements ID
|
|||
return new TestInstantiationService(services, false, this);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
override dispose() {
|
||||
sinon.restore();
|
||||
if (this._properDispose) {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue