src/vs/platform/instantiation/common/instantiationService.ts

475 LOC · 451 covered · 24 uncovered · 117 ranges · 6072 concepts · 36 introducers · 3053 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- instantiationService.ts ×21
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 { GlobalIdleValue } from '../../../base/common/async.js';
7 > import { Event } from '../../../base/common/event.js';
8 > import { illegalState } from '../../../base/common/errors.js';
9 > import { DisposableStore, dispose, IDisposable, isDisposable, toDisposable } from '../../../base/common/lifecycle.js';
10 > import { SyncDescriptor, SyncDescriptor0 } from './descriptors.js';
11 > import { Graph } from './graph.js';
12 > import { GetLeadingNonServiceArgs, IInstantiationService, ServiceIdentifier, ServicesAccessor, _util } from './instantiation.js';
13 > import { ServiceCollection } from './serviceCollection.js';
14 > import { LinkedList } from '../../../base/common/linkedList.js';
15 >
16 > // TRACING
17 > const _enableAllTracing = false
18 > // || "TRUE" // DO NOT CHECK IN!
19 > ;
20 >
21 > class CyclicDependencyError extends Error {
22 > constructor(graph: Graph<any>) {
23 > super('cyclic dependency between services'); instantiationService.ts ×3
24 > this.message = graph.findCycleSlow() ?? `UNABLE to detect cycle, dumping graph: \n${graph.toString()}`;
25 > }
27 >
28 > export class InstantiationService implements IInstantiationService {
29 >
30 > declare readonly _serviceBrand: undefined;
31 >
32 > readonly _globalGraph?: Graph<string>;
33 > private _globalGraphImplicitDependency?: string;
34 >
35 > private _isDisposed = false;
36 > private readonly _servicesToMaybeDispose = new Set<any>();
37 > private readonly _children = new Set<InstantiationService>();
38 >
39 > constructor(
40 > private readonly _services: ServiceCollection = new ServiceCollection(), instantiationService.ts ×1
41 > private readonly _strict: boolean = false,
42 > private readonly _parent?: InstantiationService,
43 > private readonly _enableTracing: boolean = _enableAllTracing
44 > ) {
45 >
46 > this._services.set(IInstantiationService, this);
47 > this._globalGraph = _enableTracing ? _parent?._globalGraph ?? new Graph(e => e) : undefined;
48 > }
50 > dispose(): void {
51 > if (!this._isDisposed) { instantiationService.ts ×2
52 > this._isDisposed = true;
53 > // dispose all child services
54 > dispose(this._children);
55 > this._children.clear();
56 >
57 > // dispose all services created by this service
58 > for (const candidate of this._servicesToMaybeDispose) {
59 > if (isDisposable(candidate)) { instantiationService.ts ×2
60 > candidate.dispose();
61 > }
62 > }
63 > this._servicesToMaybeDispose.clear(); instantiationService.ts ×2
64 > }
65 > }
67 > private _throwIfDisposed(): void {
68 > if (this._isDisposed) { instantiationService.ts ×2
69 > throw new Error('InstantiationService has been disposed'); instantiationService.ts ×1
70 > }
73 > createChild(services: ServiceCollection, store?: DisposableStore): IInstantiationService {
74 > this._throwIfDisposed(); instantiationService.ts ×2
75 >
76 > const that = this;
77 > const result = new class extends InstantiationService {
78 > override dispose(): void {
79 > that._children.delete(result); instantiationService.ts ×1
80 > super.dispose();
81 > }
82 > }(services, this._strict, this, this._enableTracing); instantiationService.ts ×2
83 > this._children.add(result);
84 >
85 > store?.add(result);
86 > return result;
87 > }
89 > invokeFunction<R, TS extends any[] = []>(fn: (accessor: ServicesAccessor, ...args: TS) => R, ...args: TS): R {
90 > this._throwIfDisposed(); instantiationService.ts ×3
91 >
92 > const _trace = Trace.traceInvocation(this._enableTracing, fn);
93 > let _done = false;
94 > try {
95 > const accessor: ServicesAccessor = {
96 > get: <T>(id: ServiceIdentifier<T>) => {
98 > if (_done) {
99 > throw illegalState('service accessor is only valid during the invocation of its target method'); errors.ts ×2
100 > }
102 > const result = this._getOrCreateServiceInstance(id, _trace);
103 > if (!result) {
104 > this._throwIfStrict(`[invokeFunction] unknown service '${id}'`, false); instantiationService.ts ×2
105 > }
106 > return result; instantiationService.ts ×1
109 > return fn(accessor, ...args);
110 > } finally {
111 > _done = true; instantiationService.ts ×2
112 > _trace.stop();
113 > }
116 > createInstance<T>(descriptor: SyncDescriptor0<T>): T;
117 > createInstance<Ctor extends new (...args: any[]) => unknown, R extends InstanceType<Ctor>>(ctor: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;
118 > createInstance(ctorOrDescriptor: any | SyncDescriptor<any>, ...rest: unknown[]): unknown {
119 > this._throwIfDisposed(); instantiationService.ts ×2
120 >
121 > let _trace: Trace;
122 > let result: unknown;
123 > if (ctorOrDescriptor instanceof SyncDescriptor) {
124 _trace = Trace.traceCreation(this._enableTracing, ctorOrDescriptor.ctor);
125 result = this._createInstance(ctorOrDescriptor.ctor, ctorOrDescriptor.staticArguments.concat(rest), _trace);
127 > _trace = Trace.traceCreation(this._enableTracing, ctorOrDescriptor);
128 > result = this._createInstance(ctorOrDescriptor, rest, _trace);
129 > }
130 > _trace.stop();
131 > return result;
132 > }
134 > private _createInstance<T>(ctor: any, args: unknown[] = [], _trace: Trace): T {
136 > // arguments defined by service decorators
137 > const serviceDependencies = _util.getServiceDependencies(ctor).sort((a, b) => a.index - b.index);
138 > const serviceArgs: unknown[] = [];
139 > for (const dependency of serviceDependencies) {
140 > const service = this._getOrCreateServiceInstance(dependency.id, _trace); instantiationService.ts ×2
141 > if (!service) {
142 > this._throwIfStrict(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id}.`, false); instantiationService.ts ×1
143 > }
144 > serviceArgs.push(service); instantiationService.ts ×2
145 > }
147 > const firstServiceArgPos = serviceDependencies.length > 0 ? serviceDependencies[0].index : args.length;
148 >
149 > // check for argument mismatches, adjust static args if needed
150 > if (args.length !== firstServiceArgPos) {
151 console.trace(`[createInstance] First service dependency of ${ctor.name} at position ${firstServiceArgPos + 1} conflicts with ${args.length} static arguments`);
152
153 const delta = firstServiceArgPos - args.length;
154 if (delta > 0) {
155 args = args.concat(new Array(delta));
156 } else {
157 args = args.slice(0, firstServiceArgPos);
158 }
159 }
161 > // now create the instance
162 > return Reflect.construct<any, T>(ctor, args.concat(serviceArgs));
163 > }
165 > private _setCreatedServiceInstance<T>(id: ServiceIdentifier<T>, instance: T): void {
166 > if (this._services.get(id) instanceof SyncDescriptor) { instantiationService.ts ×8
167 > this._services.set(id, instance);
168 > } else if (this._parent) {
169 > this._parent._setCreatedServiceInstance(id, instance); instantiationService.ts ×6
170 > } else {
171 throw new Error('illegalState - setting UNKNOWN service instance');
172 }
175 > private _getServiceInstanceOrDescriptor<T>(id: ServiceIdentifier<T>): T | SyncDescriptor<T> {
176 > const instanceOrDesc = this._services.get(id); instantiationService.ts ×6
177 > if (!instanceOrDesc && this._parent) {
178 > return this._parent._getServiceInstanceOrDescriptor(id); instantiationService.ts ×1
180 > return instanceOrDesc;
181 > }
182 > }
184 > protected _getOrCreateServiceInstance<T>(id: ServiceIdentifier<T>, _trace: Trace): T {
185 > if (this._globalGraph && this._globalGraphImplicitDependency) { instantiationService.ts ×6
186 > this._globalGraph.insertEdge(this._globalGraphImplicitDependency, String(id)); instantiationService.ts ×6
187 > }
188 > const thing = this._getServiceInstanceOrDescriptor(id); instantiationService.ts ×6
189 > if (thing instanceof SyncDescriptor) {
190 > return this._safeCreateAndCacheServiceInstance(id, thing, _trace.branch(id, true)); instantiationService.ts ×8
192 > _trace.branch(id, false); instantiationService.ts ×1
193 > return thing;
194 > }
197 > private readonly _activeInstantiations = new Set<ServiceIdentifier<any>>();
198 >
199 >
200 > private _safeCreateAndCacheServiceInstance<T>(id: ServiceIdentifier<T>, desc: SyncDescriptor<T>, _trace: Trace): T {
201 > if (this._activeInstantiations.has(id)) { instantiationService.ts ×8
202 > throw new Error(`illegal state - RECURSIVELY instantiating service '${id}'`); instantiationService.ts ×6
203 > }
204 > this._activeInstantiations.add(id); instantiationService.ts ×8
205 > try {
206 > return this._createAndCacheServiceInstance(id, desc, _trace);
207 > } finally {
208 > this._activeInstantiations.delete(id);
209 > }
210 > }
212 > private _createAndCacheServiceInstance<T>(id: ServiceIdentifier<T>, desc: SyncDescriptor<T>, _trace: Trace): T {
214 > type Triple = { id: ServiceIdentifier<any>; desc: SyncDescriptor<any>; _trace: Trace };
215 > const graph = new Graph<Triple>(data => data.id.toString());
216 >
217 > let cycleCount = 0;
218 > const stack = [{ id, desc, _trace }];
219 > const seen = new Set<string>();
220 > while (stack.length) {
221 > const item = stack.pop()!;
222 >
223 > if (seen.has(String(item.id))) {
224 > continue; instantiationService.ts ×3
225 > }
226 > seen.add(String(item.id)); instantiationService.ts ×8
227 >
228 > graph.lookupOrInsertNode(item);
229 >
230 > // a weak but working heuristic for cycle checks
231 > if (cycleCount++ > 1000) {
232 throw new CyclicDependencyError(graph);
233 }
235 > // check all dependencies for existence and if they need to be created first
236 > for (const dependency of _util.getServiceDependencies(item.desc.ctor)) {
238 > const instanceOrDesc = this._getServiceInstanceOrDescriptor(dependency.id);
239 > if (!instanceOrDesc) {
240 this._throwIfStrict(`[createInstance] ${id} depends on ${dependency.id} which is NOT registered.`, true);
241 }
243 > // take note of all service dependencies
244 > this._globalGraph?.insertEdge(String(item.id), String(dependency.id));
245 >
246 > if (instanceOrDesc instanceof SyncDescriptor) {
247 > const d = { id: dependency.id, desc: instanceOrDesc, _trace: item._trace.branch(dependency.id, true) };
248 > graph.insertEdge(item, d);
249 > stack.push(d);
250 > }
251 > }
253 >
254 > while (true) {
255 > const roots = graph.roots();
256 >
257 > // if there is no more roots but still
258 > // nodes in the graph we have a cycle
259 > if (roots.length === 0) {
260 > if (!graph.isEmpty()) {
261 > throw new CyclicDependencyError(graph); instantiationService.ts ×3
262 > }
264 > }
265 >
266 > for (const { data } of roots) {
267 > // Repeat the check for this still being a service sync descriptor. That's because
268 > // instantiating a dependency might have side-effect and recursively trigger instantiation
269 > // so that some dependencies are now fullfilled already.
270 > const instanceOrDesc = this._getServiceInstanceOrDescriptor(data.id);
271 > if (instanceOrDesc instanceof SyncDescriptor) {
272 > // create instance and overwrite the service collections
273 > const instance = this._createServiceInstanceWithOwner(data.id, data.desc.ctor, data.desc.staticArguments, data.desc.supportsDelayedInstantiation, data._trace);
274 > this._setCreatedServiceInstance(data.id, instance);
275 > }
276 > graph.removeNode(data);
277 > }
278 > }
279 > return <T>this._getServiceInstanceOrDescriptor(id);
282 > private _createServiceInstanceWithOwner<T>(id: ServiceIdentifier<T>, ctor: any, args: unknown[] = [], supportsDelayedInstantiation: boolean, _trace: Trace): T {
283 > if (this._services.get(id) instanceof SyncDescriptor) { instantiationService.ts ×8
284 > return this._createServiceInstance(id, ctor, args, supportsDelayedInstantiation, _trace, this._servicesToMaybeDispose);
285 > } else if (this._parent) {
286 > return this._parent._createServiceInstanceWithOwner(id, ctor, args, supportsDelayedInstantiation, _trace); instantiationService.ts ×6
287 > } else {
288 throw new Error(`illegalState - creating UNKNOWN service instance ${ctor.name}`);
289 }
292 > private _createServiceInstance<T>(id: ServiceIdentifier<T>, ctor: any, args: unknown[] = [], supportsDelayedInstantiation: boolean, _trace: Trace, disposeBucket: Set<any>): T {
293 > if (!supportsDelayedInstantiation) { instantiationService.ts ×8
294 > // eager instantiation instantiationService.ts ×1
295 > const result = this._createInstance<T>(ctor, args, _trace);
296 > disposeBucket.add(result);
297 > return result;
298 >
300 > const child = new InstantiationService(undefined, this._strict, this, this._enableTracing); instantiationService.ts ×9
301 > child._globalGraphImplicitDependency = String(id);
302 >
303 > type EaryListenerData = {
304 > listener: Parameters<Event<any>>;
305 > disposable?: IDisposable;
306 > };
307 >
308 > // Return a proxy object that's backed by an idle value. That
309 > // strategy is to instantiate services in our idle time or when actually
310 > // needed but not when injected into a consumer
311 >
312 > // return "empty events" when the service isn't instantiated yet
313 > const earlyListeners = new Map<string, LinkedList<EaryListenerData>>();
314 >
315 > const idle = new GlobalIdleValue<any>(() => {
316 > const result = child._createInstance<T>(ctor, args, _trace);
317 >
318 > // early listeners that we kept are now being subscribed to
319 > // the real service
320 > for (const [key, values] of earlyListeners) {
321 > // eslint-disable-next-line local/code-no-any-casts instantiationService.ts ×5
322 > const candidate = <Event<any>>(<any>result)[key];
323 > if (typeof candidate === 'function') {
324 > for (const value of values) {
325 > value.disposable = candidate.apply(result, value.listener); instantiationService.ts ×3
326 > }
328 > }
329 > earlyListeners.clear(); instantiationService.ts ×9
330 > disposeBucket.add(result);
331 > return result;
332 > });
333 > return <T>new Proxy(Object.create(null), {
334 > get(target: any, key: PropertyKey): unknown {
335 >
336 > if (!idle.isInitialized) {
337 > // looks like an event
338 > if (typeof key === 'string' && (key.startsWith('onDid') || key.startsWith('onWill'))) {
339 > let list = earlyListeners.get(key); instantiationService.ts ×5
340 > if (!list) {
341 > list = new LinkedList();
342 > earlyListeners.set(key, list);
343 > }
344 > const event: Event<any> = (callback, thisArg, disposables) => {
345 > if (idle.isInitialized) {
346 > return idle.value[key](callback, thisArg, disposables); instantiationService.ts ×1
348 > const entry: EaryListenerData = { listener: [callback, thisArg, disposables], disposable: undefined }; instantiationService.ts ×3
349 > const rm = list.push(entry);
350 > const result = toDisposable(() => {
351 > rm();
352 > entry.disposable?.dispose();
353 > });
354 > return result;
355 > }
357 > return event;
358 > }
360 >
361 > // value already exists
362 > if (key in target) {
363 > return target[key]; instantiationService.ts ×3
364 > }
366 > // create value
367 > const obj = idle.value;
368 > let prop = obj[key];
369 > if (typeof prop !== 'function') {
370 > return prop; instantiationService.ts ×2
371 > }
372 > prop = prop.bind(obj); instantiationService.ts ×1
373 > target[key] = prop;
374 > return prop;
376 > set(_target: T, p: PropertyKey, value: any): boolean {
377 idle.value[p] = value;
378 return true;
379 },
380 > getPrototypeOf(_target: T) { instantiationService.ts ×9
381 > return ctor.prototype; instantiationService.ts ×6
382 > }
384 > }
387 > private _throwIfStrict(msg: string, printWarning: boolean): void {
388 > if (printWarning) { instantiationService.ts ×3
389 console.warn(msg);
390 }
391 > if (this._strict) { instantiationService.ts ×3
392 > throw new Error(msg); instantiationService.ts ×2
393 > }
396 >
397 > //#region -- tracing ---
398 >
399 > const enum TraceType {
400 > None = 0,
401 > Creation = 1,
402 > Invocation = 2,
403 > Branch = 3,
404 > }
405 >
406 > export class Trace {
407 >
408 > static all = new Set<string>();
409 >
410 > private static readonly _None = new class extends Trace {
411 > constructor() { super(TraceType.None, null); }
412 > override stop() { }
413 > override branch() { return this; }
414 > };
415 >
416 > static traceInvocation(_enableTracing: boolean, ctor: any): Trace {
417 > return !_enableTracing ? Trace._None : new Trace(TraceType.Invocation, ctor.name || new Error().stack!.split('\n').slice(3, 4).join('\n')); instantiationService.ts ×2
418 > }
420 > static traceCreation(_enableTracing: boolean, ctor: any): Trace {
421 > return !_enableTracing ? Trace._None : new Trace(TraceType.Creation, ctor.name); instantiationService.ts ×1
422 > }
424 > private static _totals: number = 0;
425 > private readonly _start: number = Date.now();
426 > private readonly _dep: [ServiceIdentifier<any>, boolean, Trace?][] = [];
427 >
428 > private constructor(
429 > readonly type: TraceType,
430 > readonly name: string | null
431 > ) { }
432 >
433 > branch(id: ServiceIdentifier<any>, first: boolean): Trace {
434 > const child = new Trace(TraceType.Branch, id.toString()); instantiationService.ts ×5
435 > this._dep.push([id, first, child]);
436 > return child;
437 > }
439 > stop() {
440 > const dur = Date.now() - this._start; instantiationService.ts ×5
441 > Trace._totals += dur;
442 >
443 > let causedCreation = false;
444 >
445 > function printChild(n: number, trace: Trace) {
446 > const res: string[] = [];
447 > const prefix = new Array(n + 1).join('\t');
448 > for (const [id, first, child] of trace._dep) {
449 > if (first && child) {
450 > causedCreation = true; instantiationService.ts ×9
451 > res.push(`${prefix}CREATES -> ${id}`);
452 > const nested = printChild(n + 1, child);
453 > if (nested) {
454 > res.push(nested); instantiationService.ts ×6
455 > }
457 > res.push(`${prefix}uses -> ${id}`); instantiationService.ts ×1
458 > }
460 > return res.join('\n');
461 > }
462 >
463 > const lines = [
464 > `${this.type === TraceType.Creation ? 'CREATE' : 'CALL'} ${this.name}`,
465 > `${printChild(1, this)}`,
466 > `DONE, took ${dur.toFixed(2)}ms (grand total ${Trace._totals.toFixed(2)}ms)`
467 > ];
468 >
469 > if (dur > 2 || causedCreation) {
470 > Trace.all.add(lines.join('\n')); instantiationService.ts ×9
471 > }
474 >
475 > //#endregion