123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import { IdleValue } from '../../../base/common/async.js';
- import { illegalState } from '../../../base/common/errors.js';
- import { SyncDescriptor } from './descriptors.js';
- import { Graph } from './graph.js';
- import { IInstantiationService, _util } from './instantiation.js';
- import { ServiceCollection } from './serviceCollection.js';
- // TRACING
- const _enableTracing = false;
- class CyclicDependencyError extends Error {
- constructor(graph) {
- var _a;
- super('cyclic dependency between services');
- this.message = (_a = graph.findCycleSlow()) !== null && _a !== void 0 ? _a : `UNABLE to detect cycle, dumping graph: \n${graph.toString()}`;
- }
- }
- export class InstantiationService {
- constructor(services = new ServiceCollection(), strict = false, parent) {
- this._activeInstantiations = new Set();
- this._services = services;
- this._strict = strict;
- this._parent = parent;
- this._services.set(IInstantiationService, this);
- }
- createChild(services) {
- return new InstantiationService(services, this._strict, this);
- }
- invokeFunction(fn, ...args) {
- let _trace = Trace.traceInvocation(fn);
- let _done = false;
- try {
- const accessor = {
- get: (id) => {
- if (_done) {
- throw illegalState('service accessor is only valid during the invocation of its target method');
- }
- const result = this._getOrCreateServiceInstance(id, _trace);
- if (!result) {
- throw new Error(`[invokeFunction] unknown service '${id}'`);
- }
- return result;
- }
- };
- return fn(accessor, ...args);
- }
- finally {
- _done = true;
- _trace.stop();
- }
- }
- createInstance(ctorOrDescriptor, ...rest) {
- let _trace;
- let result;
- if (ctorOrDescriptor instanceof SyncDescriptor) {
- _trace = Trace.traceCreation(ctorOrDescriptor.ctor);
- result = this._createInstance(ctorOrDescriptor.ctor, ctorOrDescriptor.staticArguments.concat(rest), _trace);
- }
- else {
- _trace = Trace.traceCreation(ctorOrDescriptor);
- result = this._createInstance(ctorOrDescriptor, rest, _trace);
- }
- _trace.stop();
- return result;
- }
- _createInstance(ctor, args = [], _trace) {
- // arguments defined by service decorators
- let serviceDependencies = _util.getServiceDependencies(ctor).sort((a, b) => a.index - b.index);
- let serviceArgs = [];
- for (const dependency of serviceDependencies) {
- let service = this._getOrCreateServiceInstance(dependency.id, _trace);
- if (!service && this._strict && !dependency.optional) {
- throw new Error(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id}.`);
- }
- serviceArgs.push(service);
- }
- let firstServiceArgPos = serviceDependencies.length > 0 ? serviceDependencies[0].index : args.length;
- // check for argument mismatches, adjust static args if needed
- if (args.length !== firstServiceArgPos) {
- console.warn(`[createInstance] First service dependency of ${ctor.name} at position ${firstServiceArgPos + 1} conflicts with ${args.length} static arguments`);
- let delta = firstServiceArgPos - args.length;
- if (delta > 0) {
- args = args.concat(new Array(delta));
- }
- else {
- args = args.slice(0, firstServiceArgPos);
- }
- }
- // now create the instance
- return new ctor(...[...args, ...serviceArgs]);
- }
- _setServiceInstance(id, instance) {
- if (this._services.get(id) instanceof SyncDescriptor) {
- this._services.set(id, instance);
- }
- else if (this._parent) {
- this._parent._setServiceInstance(id, instance);
- }
- else {
- throw new Error('illegalState - setting UNKNOWN service instance');
- }
- }
- _getServiceInstanceOrDescriptor(id) {
- let instanceOrDesc = this._services.get(id);
- if (!instanceOrDesc && this._parent) {
- return this._parent._getServiceInstanceOrDescriptor(id);
- }
- else {
- return instanceOrDesc;
- }
- }
- _getOrCreateServiceInstance(id, _trace) {
- let thing = this._getServiceInstanceOrDescriptor(id);
- if (thing instanceof SyncDescriptor) {
- return this._safeCreateAndCacheServiceInstance(id, thing, _trace.branch(id, true));
- }
- else {
- _trace.branch(id, false);
- return thing;
- }
- }
- _safeCreateAndCacheServiceInstance(id, desc, _trace) {
- if (this._activeInstantiations.has(id)) {
- throw new Error(`illegal state - RECURSIVELY instantiating service '${id}'`);
- }
- this._activeInstantiations.add(id);
- try {
- return this._createAndCacheServiceInstance(id, desc, _trace);
- }
- finally {
- this._activeInstantiations.delete(id);
- }
- }
- _createAndCacheServiceInstance(id, desc, _trace) {
- const graph = new Graph(data => data.id.toString());
- let cycleCount = 0;
- const stack = [{ id, desc, _trace }];
- while (stack.length) {
- const item = stack.pop();
- graph.lookupOrInsertNode(item);
- // a weak but working heuristic for cycle checks
- if (cycleCount++ > 1000) {
- throw new CyclicDependencyError(graph);
- }
- // check all dependencies for existence and if they need to be created first
- for (let dependency of _util.getServiceDependencies(item.desc.ctor)) {
- let instanceOrDesc = this._getServiceInstanceOrDescriptor(dependency.id);
- if (!instanceOrDesc && !dependency.optional) {
- console.warn(`[createInstance] ${id} depends on ${dependency.id} which is NOT registered.`);
- }
- if (instanceOrDesc instanceof SyncDescriptor) {
- const d = { id: dependency.id, desc: instanceOrDesc, _trace: item._trace.branch(dependency.id, true) };
- graph.insertEdge(item, d);
- stack.push(d);
- }
- }
- }
- while (true) {
- const roots = graph.roots();
- // if there is no more roots but still
- // nodes in the graph we have a cycle
- if (roots.length === 0) {
- if (!graph.isEmpty()) {
- throw new CyclicDependencyError(graph);
- }
- break;
- }
- for (const { data } of roots) {
- // Repeat the check for this still being a service sync descriptor. That's because
- // instantiating a dependency might have side-effect and recursively trigger instantiation
- // so that some dependencies are now fullfilled already.
- const instanceOrDesc = this._getServiceInstanceOrDescriptor(data.id);
- 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);
- }
- graph.removeNode(data);
- }
- }
- return this._getServiceInstanceOrDescriptor(id);
- }
- _createServiceInstanceWithOwner(id, ctor, args = [], supportsDelayedInstantiation, _trace) {
- if (this._services.get(id) instanceof SyncDescriptor) {
- return this._createServiceInstance(ctor, args, supportsDelayedInstantiation, _trace);
- }
- else if (this._parent) {
- return this._parent._createServiceInstanceWithOwner(id, ctor, args, supportsDelayedInstantiation, _trace);
- }
- else {
- throw new Error(`illegalState - creating UNKNOWN service instance ${ctor.name}`);
- }
- }
- _createServiceInstance(ctor, args = [], _supportsDelayedInstantiation, _trace) {
- if (!_supportsDelayedInstantiation) {
- // eager instantiation
- return this._createInstance(ctor, args, _trace);
- }
- else {
- // Return a proxy object that's backed by an idle value. That
- // strategy is to instantiate services in our idle time or when actually
- // needed but not when injected into a consumer
- const idle = new IdleValue(() => this._createInstance(ctor, args, _trace));
- return new Proxy(Object.create(null), {
- get(target, key) {
- if (key in target) {
- return target[key];
- }
- let obj = idle.value;
- let prop = obj[key];
- if (typeof prop !== 'function') {
- return prop;
- }
- prop = prop.bind(obj);
- target[key] = prop;
- return prop;
- },
- set(_target, p, value) {
- idle.value[p] = value;
- return true;
- }
- });
- }
- }
- }
- class Trace {
- constructor(type, name) {
- this.type = type;
- this.name = name;
- this._start = Date.now();
- this._dep = [];
- }
- static traceInvocation(ctor) {
- return !_enableTracing ? Trace._None : new Trace(1 /* Invocation */, ctor.name || ctor.toString().substring(0, 42).replace(/\n/g, ''));
- }
- static traceCreation(ctor) {
- return !_enableTracing ? Trace._None : new Trace(0 /* Creation */, ctor.name);
- }
- branch(id, first) {
- let child = new Trace(2 /* Branch */, id.toString());
- this._dep.push([id, first, child]);
- return child;
- }
- stop() {
- let dur = Date.now() - this._start;
- Trace._totals += dur;
- let causedCreation = false;
- function printChild(n, trace) {
- let res = [];
- let prefix = new Array(n + 1).join('\t');
- for (const [id, first, child] of trace._dep) {
- if (first && child) {
- causedCreation = true;
- res.push(`${prefix}CREATES -> ${id}`);
- let nested = printChild(n + 1, child);
- if (nested) {
- res.push(nested);
- }
- }
- else {
- res.push(`${prefix}uses -> ${id}`);
- }
- }
- return res.join('\n');
- }
- let lines = [
- `${this.type === 0 /* Creation */ ? 'CREATE' : 'CALL'} ${this.name}`,
- `${printChild(1, this)}`,
- `DONE, took ${dur.toFixed(2)}ms (grand total ${Trace._totals.toFixed(2)}ms)`
- ];
- if (dur > 2 || causedCreation) {
- console.log(lines.join('\n'));
- }
- }
- }
- Trace._None = new class extends Trace {
- constructor() { super(-1, null); }
- stop() { }
- branch() { return this; }
- };
- Trace._totals = 0;
- //#endregion
|