1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const tslib_1 = require("tslib");
|
4 | const lifecycle_1 = require("@stoplight/lifecycle");
|
5 | const immer_1 = require("immer");
|
6 | const lodash_1 = require("lodash");
|
7 | const mobx_1 = require("mobx");
|
8 | const neo_async_1 = require("neo-async");
|
9 | const errorReporter_1 = require("../errorReporter");
|
10 | const scheduler_1 = require("../scheduler");
|
11 | const types_1 = require("../types");
|
12 | function createScheduler(props) {
|
13 | return new Scheduler(props);
|
14 | }
|
15 | exports.createScheduler = createScheduler;
|
16 | class Scheduler {
|
17 | constructor(props) {
|
18 | this._queued = {};
|
19 | this._handlers = handlerMap();
|
20 | this._running = {};
|
21 | this.mightBeStuck = false;
|
22 | this.queue = (task) => {
|
23 | if (!this._handlers[task.op].length)
|
24 | return Promise.resolve();
|
25 | const taskId = computeTaskId(task);
|
26 | let queued;
|
27 | if (taskId) {
|
28 | queued = this._queued[taskId];
|
29 | }
|
30 | if (!queued) {
|
31 | queued = new Promise((resolve, reject) => {
|
32 | this._queue.push(task, priorityMap[task.op] || 99, err => {
|
33 | if (taskId) {
|
34 | delete this._queued[taskId];
|
35 | }
|
36 | if (err) {
|
37 | errorReporter_1.errorReporter.reportError({
|
38 | code: types_1.GraphiteErrorCode.Generic,
|
39 | message: `Error running task ${task.op} ${err}`,
|
40 | trace: task.trace,
|
41 | nodeId: task.nodeId,
|
42 | });
|
43 | reject(err);
|
44 | }
|
45 | resolve();
|
46 | });
|
47 | });
|
48 | if (taskId) {
|
49 | this._queued[taskId] = queued;
|
50 | }
|
51 | }
|
52 | return queued;
|
53 | };
|
54 | this.queueAll = (tasks) => {
|
55 | return tasks.map(task => this.queue(task));
|
56 | };
|
57 | this.registerHandler = (op, handler) => {
|
58 | this._handlers[op].push(handler);
|
59 | return lifecycle_1.createDisposable(() => {
|
60 | const idx = this._handlers[op].indexOf(handler);
|
61 | if (idx >= 0) {
|
62 | this._handlers[op].splice(idx, 1);
|
63 | }
|
64 | });
|
65 | };
|
66 | this.drain = () => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
67 | yield this.drainQueue();
|
68 | yield mobx_1.when(() => !this.isBusy);
|
69 | });
|
70 | this._run = (task) => {
|
71 | const node = this._graph.getNodeById(task.nodeId);
|
72 | if (!node) {
|
73 | throw new Error(`Cannot find node with id ${task.nodeId} for task ${scheduler_1.GraphTaskOp[task.op]}.`);
|
74 | }
|
75 | const handler = findHandler(this._handlers[task.op], node);
|
76 | if (!handler)
|
77 | return;
|
78 | return handler.run(node, {
|
79 | task,
|
80 | getNodeById: this._graph.getNodeById,
|
81 | getNodeByUri: this._graph.getNodeByUri,
|
82 | getNodesByType: this._graph.getNodesByType,
|
83 | moveNode: this._graph.moveNode,
|
84 | applyPatch: patch => {
|
85 | return this._graph.applyPatch({
|
86 | operations: patch.operations,
|
87 | trace: Object.assign({}, patch.trace, { sourceOp: task.op }),
|
88 | });
|
89 | },
|
90 | removeNode: (id, trace = {}) => {
|
91 | return this._graph.removeNode(id, Object.assign({}, trace, { sourceOp: task.op }));
|
92 | },
|
93 | addNode: (props, trace = {}) => {
|
94 | return this._graph.addNode(props, Object.assign({}, trace, { sourceOp: task.op }));
|
95 | },
|
96 | setSourceNodeProp: (id, prop, value, trace = {}) => {
|
97 | return this._graph.setSourceNodeProp(id, prop, value, Object.assign({}, trace, { sourceOp: task.op }));
|
98 | },
|
99 | patchSourceNodeProp: (id, prop, parsed, trace = {}) => {
|
100 | return this._graph.patchSourceNodeProp(id, prop, parsed, Object.assign({}, trace, { sourceOp: task.op }));
|
101 | },
|
102 | reportError: (nodeId, error, trace = {}) => {
|
103 | return this._graph.reportError(nodeId, error, immer_1.default(trace, source => {
|
104 | source.sourceOp = task.op;
|
105 | }));
|
106 | },
|
107 | setSourceNodeDiagnostics: (id, diagnostics, trace = {}) => {
|
108 | return this._graph.setSourceNodeDiagnostics(id, handler.id, diagnostics, Object.assign({}, trace, { sourceOp: task.op }));
|
109 | },
|
110 | runTask: (t) => {
|
111 | return this.run(t);
|
112 | },
|
113 | resolver: this._resolver,
|
114 | });
|
115 | };
|
116 | this.drainQueue = () => {
|
117 | return new Promise((resolve, reject) => {
|
118 | if (this.queueLength()) {
|
119 | this._queue.drain = resolve;
|
120 | this._queue.error = reject;
|
121 | }
|
122 | else {
|
123 | resolve();
|
124 | }
|
125 | });
|
126 | };
|
127 | this._runQueueTask = (task, cb) => {
|
128 | const taskId = computeTaskId(task);
|
129 | this._running[taskId] = true;
|
130 | try {
|
131 | const result = this._run(task);
|
132 | if (result instanceof Promise) {
|
133 | result
|
134 | .then(r => {
|
135 | this._handleRunResult(r);
|
136 | delete this._running[taskId];
|
137 | cb();
|
138 | })
|
139 | .catch(cb)
|
140 | .then(() => delete this._running[taskId]);
|
141 | }
|
142 | else {
|
143 | this._handleRunResult(result);
|
144 | delete this._running[taskId];
|
145 | setTimeout(cb, 0);
|
146 | }
|
147 | }
|
148 | catch (e) {
|
149 | delete this._running[taskId];
|
150 | cb(e);
|
151 | }
|
152 | };
|
153 | this._handleRunResult = (result) => {
|
154 | if (result) {
|
155 | this.queueAll(result);
|
156 | }
|
157 | };
|
158 | this._graph = props.graph;
|
159 | this._queue = neo_async_1.priorityQueue(this._runQueueTask, 1);
|
160 | this._resolver = props.resolver;
|
161 | mobx_1.reaction(() => this.queuedCount, mobx_1.action(() => {
|
162 | this.mightBeStuck = false;
|
163 | if (this._mightBeStuckTimeout)
|
164 | clearTimeout(this._mightBeStuckTimeout);
|
165 | this._mightBeStuckTimeout = setTimeout(mobx_1.action(() => {
|
166 | this.mightBeStuck = this.queuedCount !== 0;
|
167 | this._mightBeStuckTimeout = void 0;
|
168 | }), 5000);
|
169 | }));
|
170 | }
|
171 | get isBusy() {
|
172 | return this.queuedCount !== 0;
|
173 | }
|
174 | get queuedCount() {
|
175 | return lodash_1.size(this._queued);
|
176 | }
|
177 | snapshot() {
|
178 | const tasks = [];
|
179 | let task = lodash_1.get(this._queue, '_tasks.head');
|
180 | while (task !== null && typeof task === 'object') {
|
181 | tasks.push(task.data);
|
182 | task = task.next;
|
183 | }
|
184 | const runningKeys = Object.keys(this._running);
|
185 | const running = [];
|
186 | for (const key of runningKeys) {
|
187 | const [op, id] = key.split(':');
|
188 | let node;
|
189 | try {
|
190 | node = this._graph.getNodeById(id);
|
191 | }
|
192 | catch (e) {
|
193 | }
|
194 | running.push({
|
195 | id,
|
196 | op: Number(op),
|
197 | type: node && node.type,
|
198 | spec: node && node.spec,
|
199 | path: node && node.path,
|
200 | language: node && node.language,
|
201 | });
|
202 | }
|
203 | return {
|
204 | running,
|
205 | queued: Object.keys(this._queued),
|
206 | tasks,
|
207 | };
|
208 | }
|
209 | queueLength() {
|
210 | return this._queue.length();
|
211 | }
|
212 | run(task) {
|
213 | const taskId = computeTaskId(task);
|
214 | const queued = this._queued[taskId];
|
215 | if (queued && this._running[taskId]) {
|
216 | return queued;
|
217 | }
|
218 | this._queue.remove((t) => taskId === computeTaskId(t));
|
219 | const taskPromise = new Promise((resolve, reject) => this._runQueueTask(task, err => {
|
220 | if (taskId) {
|
221 | delete this._queued[taskId];
|
222 | }
|
223 | if (err) {
|
224 | console.error(`Error running task ${task.op}`, err);
|
225 | reject(err);
|
226 | }
|
227 | resolve();
|
228 | }));
|
229 | this._queued[taskId] = taskPromise;
|
230 | return taskPromise;
|
231 | }
|
232 | }
|
233 | tslib_1.__decorate([
|
234 | mobx_1.observable,
|
235 | tslib_1.__metadata("design:type", Object)
|
236 | ], Scheduler.prototype, "_queued", void 0);
|
237 | tslib_1.__decorate([
|
238 | mobx_1.observable,
|
239 | tslib_1.__metadata("design:type", Object)
|
240 | ], Scheduler.prototype, "mightBeStuck", void 0);
|
241 | tslib_1.__decorate([
|
242 | mobx_1.computed,
|
243 | tslib_1.__metadata("design:type", Object),
|
244 | tslib_1.__metadata("design:paramtypes", [])
|
245 | ], Scheduler.prototype, "isBusy", null);
|
246 | tslib_1.__decorate([
|
247 | mobx_1.computed,
|
248 | tslib_1.__metadata("design:type", Object),
|
249 | tslib_1.__metadata("design:paramtypes", [])
|
250 | ], Scheduler.prototype, "queuedCount", null);
|
251 | function computeTaskId(task) {
|
252 | return `${task.op}:${task.nodeId}`;
|
253 | }
|
254 | const priorityMap = {
|
255 | [scheduler_1.GraphTaskOp.SerializeSourceNode]: 1,
|
256 | [scheduler_1.GraphTaskOp.DeserializeSourceNode]: 1,
|
257 | [scheduler_1.GraphTaskOp.DiffRawToParsed]: 1,
|
258 | [scheduler_1.GraphTaskOp.ComputeSourceMap]: 2,
|
259 | [scheduler_1.GraphTaskOp.ReadSourceNode]: 3,
|
260 | [scheduler_1.GraphTaskOp.WriteSourceNode]: 3,
|
261 | [scheduler_1.GraphTaskOp.DeleteSourceNode]: 3,
|
262 | [scheduler_1.GraphTaskOp.MoveSourceNode]: 3,
|
263 | [scheduler_1.GraphTaskOp.ResolveSourceNode]: 4,
|
264 | [scheduler_1.GraphTaskOp.TransformParsed]: 5,
|
265 | [scheduler_1.GraphTaskOp.ValidateSourceNode]: 6,
|
266 | };
|
267 | const handlerMap = () => ({
|
268 | [scheduler_1.GraphTaskOp.SerializeSourceNode]: [],
|
269 | [scheduler_1.GraphTaskOp.DeserializeSourceNode]: [],
|
270 | [scheduler_1.GraphTaskOp.DiffRawToParsed]: [],
|
271 | [scheduler_1.GraphTaskOp.ComputeSourceMap]: [],
|
272 | [scheduler_1.GraphTaskOp.ReadSourceNode]: [],
|
273 | [scheduler_1.GraphTaskOp.WriteSourceNode]: [],
|
274 | [scheduler_1.GraphTaskOp.DeleteSourceNode]: [],
|
275 | [scheduler_1.GraphTaskOp.TransformParsed]: [],
|
276 | [scheduler_1.GraphTaskOp.MoveSourceNode]: [],
|
277 | [scheduler_1.GraphTaskOp.ResolveSourceNode]: [],
|
278 | [scheduler_1.GraphTaskOp.ValidateSourceNode]: [],
|
279 | });
|
280 | const findHandler = (handlers, node) => {
|
281 | return handlers.find(h => h.selector(node));
|
282 | };
|
283 |
|
\ | No newline at end of file |