instantiationService.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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. import { IdleValue } from '../../../base/common/async.js';
  6. import { illegalState } from '../../../base/common/errors.js';
  7. import { SyncDescriptor } from './descriptors.js';
  8. import { Graph } from './graph.js';
  9. import { IInstantiationService, _util } from './instantiation.js';
  10. import { ServiceCollection } from './serviceCollection.js';
  11. // TRACING
  12. const _enableTracing = false;
  13. class CyclicDependencyError extends Error {
  14. constructor(graph) {
  15. var _a;
  16. super('cyclic dependency between services');
  17. this.message = (_a = graph.findCycleSlow()) !== null && _a !== void 0 ? _a : `UNABLE to detect cycle, dumping graph: \n${graph.toString()}`;
  18. }
  19. }
  20. export class InstantiationService {
  21. constructor(services = new ServiceCollection(), strict = false, parent) {
  22. this._activeInstantiations = new Set();
  23. this._services = services;
  24. this._strict = strict;
  25. this._parent = parent;
  26. this._services.set(IInstantiationService, this);
  27. }
  28. createChild(services) {
  29. return new InstantiationService(services, this._strict, this);
  30. }
  31. invokeFunction(fn, ...args) {
  32. let _trace = Trace.traceInvocation(fn);
  33. let _done = false;
  34. try {
  35. const accessor = {
  36. get: (id) => {
  37. if (_done) {
  38. throw illegalState('service accessor is only valid during the invocation of its target method');
  39. }
  40. const result = this._getOrCreateServiceInstance(id, _trace);
  41. if (!result) {
  42. throw new Error(`[invokeFunction] unknown service '${id}'`);
  43. }
  44. return result;
  45. }
  46. };
  47. return fn(accessor, ...args);
  48. }
  49. finally {
  50. _done = true;
  51. _trace.stop();
  52. }
  53. }
  54. createInstance(ctorOrDescriptor, ...rest) {
  55. let _trace;
  56. let result;
  57. if (ctorOrDescriptor instanceof SyncDescriptor) {
  58. _trace = Trace.traceCreation(ctorOrDescriptor.ctor);
  59. result = this._createInstance(ctorOrDescriptor.ctor, ctorOrDescriptor.staticArguments.concat(rest), _trace);
  60. }
  61. else {
  62. _trace = Trace.traceCreation(ctorOrDescriptor);
  63. result = this._createInstance(ctorOrDescriptor, rest, _trace);
  64. }
  65. _trace.stop();
  66. return result;
  67. }
  68. _createInstance(ctor, args = [], _trace) {
  69. // arguments defined by service decorators
  70. let serviceDependencies = _util.getServiceDependencies(ctor).sort((a, b) => a.index - b.index);
  71. let serviceArgs = [];
  72. for (const dependency of serviceDependencies) {
  73. let service = this._getOrCreateServiceInstance(dependency.id, _trace);
  74. if (!service && this._strict && !dependency.optional) {
  75. throw new Error(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id}.`);
  76. }
  77. serviceArgs.push(service);
  78. }
  79. let firstServiceArgPos = serviceDependencies.length > 0 ? serviceDependencies[0].index : args.length;
  80. // check for argument mismatches, adjust static args if needed
  81. if (args.length !== firstServiceArgPos) {
  82. console.warn(`[createInstance] First service dependency of ${ctor.name} at position ${firstServiceArgPos + 1} conflicts with ${args.length} static arguments`);
  83. let delta = firstServiceArgPos - args.length;
  84. if (delta > 0) {
  85. args = args.concat(new Array(delta));
  86. }
  87. else {
  88. args = args.slice(0, firstServiceArgPos);
  89. }
  90. }
  91. // now create the instance
  92. return new ctor(...[...args, ...serviceArgs]);
  93. }
  94. _setServiceInstance(id, instance) {
  95. if (this._services.get(id) instanceof SyncDescriptor) {
  96. this._services.set(id, instance);
  97. }
  98. else if (this._parent) {
  99. this._parent._setServiceInstance(id, instance);
  100. }
  101. else {
  102. throw new Error('illegalState - setting UNKNOWN service instance');
  103. }
  104. }
  105. _getServiceInstanceOrDescriptor(id) {
  106. let instanceOrDesc = this._services.get(id);
  107. if (!instanceOrDesc && this._parent) {
  108. return this._parent._getServiceInstanceOrDescriptor(id);
  109. }
  110. else {
  111. return instanceOrDesc;
  112. }
  113. }
  114. _getOrCreateServiceInstance(id, _trace) {
  115. let thing = this._getServiceInstanceOrDescriptor(id);
  116. if (thing instanceof SyncDescriptor) {
  117. return this._safeCreateAndCacheServiceInstance(id, thing, _trace.branch(id, true));
  118. }
  119. else {
  120. _trace.branch(id, false);
  121. return thing;
  122. }
  123. }
  124. _safeCreateAndCacheServiceInstance(id, desc, _trace) {
  125. if (this._activeInstantiations.has(id)) {
  126. throw new Error(`illegal state - RECURSIVELY instantiating service '${id}'`);
  127. }
  128. this._activeInstantiations.add(id);
  129. try {
  130. return this._createAndCacheServiceInstance(id, desc, _trace);
  131. }
  132. finally {
  133. this._activeInstantiations.delete(id);
  134. }
  135. }
  136. _createAndCacheServiceInstance(id, desc, _trace) {
  137. const graph = new Graph(data => data.id.toString());
  138. let cycleCount = 0;
  139. const stack = [{ id, desc, _trace }];
  140. while (stack.length) {
  141. const item = stack.pop();
  142. graph.lookupOrInsertNode(item);
  143. // a weak but working heuristic for cycle checks
  144. if (cycleCount++ > 1000) {
  145. throw new CyclicDependencyError(graph);
  146. }
  147. // check all dependencies for existence and if they need to be created first
  148. for (let dependency of _util.getServiceDependencies(item.desc.ctor)) {
  149. let instanceOrDesc = this._getServiceInstanceOrDescriptor(dependency.id);
  150. if (!instanceOrDesc && !dependency.optional) {
  151. console.warn(`[createInstance] ${id} depends on ${dependency.id} which is NOT registered.`);
  152. }
  153. if (instanceOrDesc instanceof SyncDescriptor) {
  154. const d = { id: dependency.id, desc: instanceOrDesc, _trace: item._trace.branch(dependency.id, true) };
  155. graph.insertEdge(item, d);
  156. stack.push(d);
  157. }
  158. }
  159. }
  160. while (true) {
  161. const roots = graph.roots();
  162. // if there is no more roots but still
  163. // nodes in the graph we have a cycle
  164. if (roots.length === 0) {
  165. if (!graph.isEmpty()) {
  166. throw new CyclicDependencyError(graph);
  167. }
  168. break;
  169. }
  170. for (const { data } of roots) {
  171. // Repeat the check for this still being a service sync descriptor. That's because
  172. // instantiating a dependency might have side-effect and recursively trigger instantiation
  173. // so that some dependencies are now fullfilled already.
  174. const instanceOrDesc = this._getServiceInstanceOrDescriptor(data.id);
  175. if (instanceOrDesc instanceof SyncDescriptor) {
  176. // create instance and overwrite the service collections
  177. const instance = this._createServiceInstanceWithOwner(data.id, data.desc.ctor, data.desc.staticArguments, data.desc.supportsDelayedInstantiation, data._trace);
  178. this._setServiceInstance(data.id, instance);
  179. }
  180. graph.removeNode(data);
  181. }
  182. }
  183. return this._getServiceInstanceOrDescriptor(id);
  184. }
  185. _createServiceInstanceWithOwner(id, ctor, args = [], supportsDelayedInstantiation, _trace) {
  186. if (this._services.get(id) instanceof SyncDescriptor) {
  187. return this._createServiceInstance(ctor, args, supportsDelayedInstantiation, _trace);
  188. }
  189. else if (this._parent) {
  190. return this._parent._createServiceInstanceWithOwner(id, ctor, args, supportsDelayedInstantiation, _trace);
  191. }
  192. else {
  193. throw new Error(`illegalState - creating UNKNOWN service instance ${ctor.name}`);
  194. }
  195. }
  196. _createServiceInstance(ctor, args = [], _supportsDelayedInstantiation, _trace) {
  197. if (!_supportsDelayedInstantiation) {
  198. // eager instantiation
  199. return this._createInstance(ctor, args, _trace);
  200. }
  201. else {
  202. // Return a proxy object that's backed by an idle value. That
  203. // strategy is to instantiate services in our idle time or when actually
  204. // needed but not when injected into a consumer
  205. const idle = new IdleValue(() => this._createInstance(ctor, args, _trace));
  206. return new Proxy(Object.create(null), {
  207. get(target, key) {
  208. if (key in target) {
  209. return target[key];
  210. }
  211. let obj = idle.value;
  212. let prop = obj[key];
  213. if (typeof prop !== 'function') {
  214. return prop;
  215. }
  216. prop = prop.bind(obj);
  217. target[key] = prop;
  218. return prop;
  219. },
  220. set(_target, p, value) {
  221. idle.value[p] = value;
  222. return true;
  223. }
  224. });
  225. }
  226. }
  227. }
  228. class Trace {
  229. constructor(type, name) {
  230. this.type = type;
  231. this.name = name;
  232. this._start = Date.now();
  233. this._dep = [];
  234. }
  235. static traceInvocation(ctor) {
  236. return !_enableTracing ? Trace._None : new Trace(1 /* Invocation */, ctor.name || ctor.toString().substring(0, 42).replace(/\n/g, ''));
  237. }
  238. static traceCreation(ctor) {
  239. return !_enableTracing ? Trace._None : new Trace(0 /* Creation */, ctor.name);
  240. }
  241. branch(id, first) {
  242. let child = new Trace(2 /* Branch */, id.toString());
  243. this._dep.push([id, first, child]);
  244. return child;
  245. }
  246. stop() {
  247. let dur = Date.now() - this._start;
  248. Trace._totals += dur;
  249. let causedCreation = false;
  250. function printChild(n, trace) {
  251. let res = [];
  252. let prefix = new Array(n + 1).join('\t');
  253. for (const [id, first, child] of trace._dep) {
  254. if (first && child) {
  255. causedCreation = true;
  256. res.push(`${prefix}CREATES -> ${id}`);
  257. let nested = printChild(n + 1, child);
  258. if (nested) {
  259. res.push(nested);
  260. }
  261. }
  262. else {
  263. res.push(`${prefix}uses -> ${id}`);
  264. }
  265. }
  266. return res.join('\n');
  267. }
  268. let lines = [
  269. `${this.type === 0 /* Creation */ ? 'CREATE' : 'CALL'} ${this.name}`,
  270. `${printChild(1, this)}`,
  271. `DONE, took ${dur.toFixed(2)}ms (grand total ${Trace._totals.toFixed(2)}ms)`
  272. ];
  273. if (dur > 2 || causedCreation) {
  274. console.log(lines.join('\n'));
  275. }
  276. }
  277. }
  278. Trace._None = new class extends Trace {
  279. constructor() { super(-1, null); }
  280. stop() { }
  281. branch() { return this; }
  282. };
  283. Trace._totals = 0;
  284. //#endregion