@lage-run/scheduler 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.json CHANGED
@@ -2,7 +2,43 @@
2
2
  "name": "@lage-run/scheduler",
3
3
  "entries": [
4
4
  {
5
- "date": "Thu, 08 Dec 2022 00:49:16 GMT",
5
+ "date": "Wed, 18 Jan 2023 18:18:13 GMT",
6
+ "tag": "@lage-run/scheduler_v0.8.0",
7
+ "version": "0.8.0",
8
+ "comments": {
9
+ "minor": [
10
+ {
11
+ "author": "kchau@microsoft.com",
12
+ "package": "@lage-run/scheduler",
13
+ "commit": "71b5b1452ee25148a637894d1bf99cf1cc6a80a7",
14
+ "comment": "Using lage for prune and clear on cache and get reporter in shape for ADO"
15
+ },
16
+ {
17
+ "author": "beachball",
18
+ "package": "@lage-run/scheduler",
19
+ "comment": "Bump @lage-run/scheduler-types to v0.3.0",
20
+ "commit": "71b5b1452ee25148a637894d1bf99cf1cc6a80a7"
21
+ }
22
+ ]
23
+ }
24
+ },
25
+ {
26
+ "date": "Thu, 05 Jan 2023 00:40:50 GMT",
27
+ "tag": "@lage-run/scheduler_v0.7.3",
28
+ "version": "0.7.3",
29
+ "comments": {
30
+ "patch": [
31
+ {
32
+ "author": "kchau@microsoft.com",
33
+ "package": "@lage-run/scheduler",
34
+ "commit": "961843bef658c51312e02498554ad338e467e1a7",
35
+ "comment": "fixing progress bar to not be a bottleneck"
36
+ }
37
+ ]
38
+ }
39
+ },
40
+ {
41
+ "date": "Thu, 08 Dec 2022 00:49:28 GMT",
6
42
  "tag": "@lage-run/scheduler_v0.7.2",
7
43
  "version": "0.7.2",
8
44
  "comments": {
package/CHANGELOG.md CHANGED
@@ -1,12 +1,29 @@
1
1
  # Change Log - @lage-run/scheduler
2
2
 
3
- This log was last generated on Thu, 08 Dec 2022 00:49:16 GMT and should not be manually modified.
3
+ This log was last generated on Wed, 18 Jan 2023 18:18:13 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## 0.8.0
8
+
9
+ Wed, 18 Jan 2023 18:18:13 GMT
10
+
11
+ ### Minor changes
12
+
13
+ - Using lage for prune and clear on cache and get reporter in shape for ADO (kchau@microsoft.com)
14
+ - Bump @lage-run/scheduler-types to v0.3.0
15
+
16
+ ## 0.7.3
17
+
18
+ Thu, 05 Jan 2023 00:40:50 GMT
19
+
20
+ ### Patches
21
+
22
+ - fixing progress bar to not be a bottleneck (kchau@microsoft.com)
23
+
7
24
  ## 0.7.2
8
25
 
9
- Thu, 08 Dec 2022 00:49:16 GMT
26
+ Thu, 08 Dec 2022 00:49:28 GMT
10
27
 
11
28
  ### Patches
12
29
 
@@ -1,6 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="global" />
3
3
  import { WrappedTarget } from "./WrappedTarget.js";
4
+ import { TargetRunnerPicker } from "./runners/TargetRunnerPicker.js";
4
5
  import type { CacheProvider, TargetHasher } from "@lage-run/cache";
5
6
  import type { Logger } from "@lage-run/logger";
6
7
  import type { TargetGraph } from "@lage-run/target-graph";
@@ -40,7 +41,7 @@ export declare class SimpleScheduler implements TargetScheduler {
40
41
  abortController: AbortController;
41
42
  abortSignal: AbortSignal;
42
43
  pool: Pool;
43
- workerIds: number[];
44
+ runnerPicker: TargetRunnerPicker;
44
45
  runPromise: Promise<any>;
45
46
  constructor(options: SimpleSchedulerOptions);
46
47
  getTargetsByPriority(): import("@lage-run/target-graph").Target[];
@@ -11,6 +11,7 @@ const _formatBytesJs = require("./formatBytes.js");
11
11
  const _categorizeTargetRunsJs = require("./categorizeTargetRuns.js");
12
12
  const _targetGraph = require("@lage-run/target-graph");
13
13
  const _wrappedTargetJs = require("./WrappedTarget.js");
14
+ const _targetRunnerPickerJs = require("./runners/TargetRunnerPicker.js");
14
15
  function _checkPrivateRedeclaration(obj, privateCollection) {
15
16
  if (privateCollection.has(obj)) {
16
17
  throw new TypeError("Cannot initialize the same private elements twice on an object");
@@ -136,23 +137,29 @@ class SimpleScheduler {
136
137
  }
137
138
  }
138
139
  getReadyTargets() {
139
- const readyTargets = [];
140
+ const readyTargets = new Set();
140
141
  for (const target of this.getTargetsByPriority()){
142
+ // Skip the start target
141
143
  if (target.id === (0, _targetGraph.getStartTargetId)()) {
142
144
  continue;
143
145
  }
144
146
  const targetRun = this.targetRuns.get(target.id);
145
- const targetDeps = targetRun.target.dependencies;
146
- // filter all dependencies for those that are "ready"
147
- const ready = targetDeps.every((dep)=>{
148
- const fromTarget = this.targetRuns.get(dep);
149
- return fromTarget.successful || dep === (0, _targetGraph.getStartTargetId)();
150
- });
151
- if (ready && targetRun.status === "pending") {
152
- readyTargets.push(targetRun);
147
+ // If the target is already running, then we can't run it again
148
+ if (targetRun.status === "pending") {
149
+ const targetDeps = targetRun.target.dependencies;
150
+ // Target is only ready when all its deps are "successful" or that it is a root target
151
+ const ready = targetDeps.every((dep)=>{
152
+ const fromTarget = this.targetRuns.get(dep);
153
+ return fromTarget.successful || dep === (0, _targetGraph.getStartTargetId)();
154
+ });
155
+ if (ready) {
156
+ readyTargets.add(targetRun);
157
+ }
153
158
  }
154
159
  }
155
- return readyTargets;
160
+ return [
161
+ ...readyTargets
162
+ ];
156
163
  }
157
164
  isAllDone() {
158
165
  for (const t of this.targetRuns.values()){
@@ -220,23 +227,33 @@ class SimpleScheduler {
220
227
  },
221
228
  workerIdleMemoryLimit: options.workerIdleMemoryLimit
222
229
  });
223
- this.workerIds = Array(options.concurrency).fill(0).map((_, idx)=>idx + 1);
230
+ this.runnerPicker = new _targetRunnerPickerJs.TargetRunnerPicker(options.runners);
224
231
  }
225
232
  }
226
233
  async function generateTargetRunPromise(target) {
227
234
  let runError;
228
- const threadId = this.workerIds.shift();
229
- target.threadId = threadId;
230
235
  if (target.result && target.successful && !this.rerunTargets.has(target.target.id)) {
231
236
  await target.result;
232
237
  } else {
233
238
  // This do-while loop only runs again if something causes this target to rerun (asynchronously triggering a re-run)
234
239
  do {
235
240
  this.rerunTargets.delete(target.target.id);
241
+ target.onQueued();
236
242
  try {
237
- await target.run();
238
- } catch (e) {
239
- runError = e;
243
+ let shouldRun = true;
244
+ try {
245
+ const runner = await this.runnerPicker.pick(target.target);
246
+ shouldRun = await runner.shouldRun(target.target);
247
+ } catch (e) {
248
+ // pass - default to run anyway
249
+ }
250
+ if (shouldRun) {
251
+ await target.run();
252
+ } else {
253
+ target.status = "skipped";
254
+ }
255
+ } catch (e1) {
256
+ runError = e1;
240
257
  }
241
258
  }while (this.rerunTargets.has(target.target.id))
242
259
  // if a continue option is set, this merely records what errors have been encountered
@@ -248,8 +265,6 @@ async function generateTargetRunPromise(target) {
248
265
  }
249
266
  }
250
267
  }
251
- this.workerIds.unshift(threadId);
252
- this.workerIds.sort();
253
268
  this.logProgress();
254
269
  // finally do another round of scheduling to run next round of targets
255
270
  await this.scheduleReadyTargets();
@@ -43,7 +43,7 @@ export declare class WrappedTarget implements TargetRun {
43
43
  constructor(options: WrappedTargetOptions);
44
44
  onQueued(): void;
45
45
  onAbort(): void;
46
- onStart(): void;
46
+ onStart(threadId: number): void;
47
47
  onComplete(): void;
48
48
  onFail(): void;
49
49
  onSkipped(hash: string | null): void;
@@ -37,27 +37,27 @@ class WrappedTarget {
37
37
  onAbort() {
38
38
  this.status = "aborted";
39
39
  this.duration = process.hrtime(this.startTime);
40
- this.options.logger.info("aborted", {
40
+ this.options.logger.info("", {
41
41
  target: this.target,
42
42
  status: "aborted",
43
43
  threadId: this.threadId
44
44
  });
45
45
  }
46
- onStart() {
46
+ onStart(threadId) {
47
47
  if (this.status !== "running") {
48
48
  this.status = "running";
49
49
  this.startTime = process.hrtime();
50
- this.options.logger.info("running", {
50
+ this.options.logger.info("", {
51
51
  target: this.target,
52
52
  status: "running",
53
- threadId: this.threadId
53
+ threadId
54
54
  });
55
55
  }
56
56
  }
57
57
  onComplete() {
58
58
  this.status = "success";
59
59
  this.duration = process.hrtime(this.startTime);
60
- this.options.logger.info("success", {
60
+ this.options.logger.info("", {
61
61
  target: this.target,
62
62
  status: "success",
63
63
  duration: this.duration,
@@ -67,7 +67,7 @@ class WrappedTarget {
67
67
  onFail() {
68
68
  this.status = "failed";
69
69
  this.duration = process.hrtime(this.startTime);
70
- this.options.logger.info("failed", {
70
+ this.options.logger.info("", {
71
71
  target: this.target,
72
72
  status: "failed",
73
73
  duration: this.duration,
@@ -80,7 +80,7 @@ class WrappedTarget {
80
80
  onSkipped(hash) {
81
81
  this.status = "skipped";
82
82
  this.duration = process.hrtime(this.startTime);
83
- this.options.logger.info(`skipped`, {
83
+ this.options.logger.info("", {
84
84
  target: this.target,
85
85
  status: "skipped",
86
86
  duration: this.duration,
@@ -120,10 +120,9 @@ class WrappedTarget {
120
120
  }
121
121
  async run() {
122
122
  const { target , logger , shouldCache , abortController } = this.options;
123
- this.onQueued();
124
123
  const abortSignal = abortController.signal;
125
124
  if (abortSignal.aborted) {
126
- this.onStart();
125
+ this.onStart(0);
127
126
  this.onAbort();
128
127
  return;
129
128
  }
@@ -135,7 +134,7 @@ class WrappedTarget {
135
134
  logger.verbose(`hash: ${hash}, cache hit? ${cacheHit}`, {
136
135
  target
137
136
  });
138
- this.onStart();
137
+ this.onStart(0);
139
138
  const cachedOutputFile = (0, _getLageOutputCacheLocationJs.getLageOutputCacheLocation)(this.target, hash ?? "");
140
139
  if (_fs.default.existsSync(cachedOutputFile)) {
141
140
  const cachedOutput = _fs.default.createReadStream(cachedOutputFile, "utf8");
@@ -192,13 +191,14 @@ class WrappedTarget {
192
191
  const bufferStderr = (0, _bufferTransformJs.bufferTransform)();
193
192
  this.result = pool.exec({
194
193
  target
195
- }, target.weight ?? 1, (_worker, stdout, stderr)=>{
196
- this.onStart();
194
+ }, target.weight ?? 1, (worker, stdout, stderr)=>{
195
+ const threadId = worker.threadId;
196
+ this.onStart(threadId);
197
197
  stdout.pipe(bufferStdout.transform);
198
198
  stderr.pipe(bufferStderr.transform);
199
199
  const releaseStdoutStream = logger.stream(_logger.LogLevel.verbose, stdout, {
200
200
  target,
201
- threadId: this.threadId
201
+ threadId
202
202
  });
203
203
  releaseStdout = ()=>{
204
204
  releaseStdoutStream();
@@ -206,7 +206,7 @@ class WrappedTarget {
206
206
  };
207
207
  const releaseStderrStream = logger.stream(_logger.LogLevel.verbose, stderr, {
208
208
  target,
209
- threadId: this.threadId
209
+ threadId
210
210
  });
211
211
  releaseStderr = ()=>{
212
212
  releaseStderrStream();
@@ -7,6 +7,9 @@ Object.defineProperty(exports, "NoOpRunner", {
7
7
  get: ()=>NoOpRunner
8
8
  });
9
9
  const NoOpRunner = {
10
+ async shouldRun () {
11
+ return true;
12
+ },
10
13
  async run () {
11
14
  // pass
12
15
  }
@@ -1,4 +1,5 @@
1
1
  import type { TargetRunner, TargetRunnerOptions } from "@lage-run/scheduler-types";
2
+ import type { Target } from "@lage-run/target-graph";
2
3
  export interface NpmScriptRunnerOptions {
3
4
  taskArgs: string[];
4
5
  nodeOptions: string;
@@ -29,5 +30,6 @@ export declare class NpmScriptRunner implements TargetRunner {
29
30
  private getNpmArgs;
30
31
  private hasNpmScript;
31
32
  private validateOptions;
33
+ shouldRun(target: Target): Promise<boolean>;
32
34
  run(runOptions: TargetRunnerOptions): Promise<void>;
33
35
  }
@@ -26,22 +26,22 @@ class NpmScriptRunner {
26
26
  const task = target.options?.script ?? target.task;
27
27
  const packageJsonPath = (0, _path.join)(target.cwd, "package.json");
28
28
  const packageJson = JSON.parse(await (0, _promises.readFile)(packageJsonPath, "utf8"));
29
- return packageJson.scripts?.[task];
29
+ return !!packageJson.scripts?.[task];
30
30
  }
31
31
  validateOptions(options) {
32
32
  if (!(0, _fs.existsSync)(options.npmCmd)) {
33
33
  throw new Error(`NPM Script Runner: ${this.options.npmCmd} does not exist`);
34
34
  }
35
35
  }
36
+ async shouldRun(target) {
37
+ // By convention, do not run anything if there is no script for this task defined in package.json (counts as "success")
38
+ return await this.hasNpmScript(target);
39
+ }
36
40
  async run(runOptions) {
37
41
  const { target , weight , abortSignal } = runOptions;
38
42
  const { nodeOptions , npmCmd , taskArgs } = this.options;
39
43
  const task = target.options?.script ?? target.task;
40
44
  let childProcess;
41
- // By convention, do not run anything if there is no script for this task defined in package.json (counts as "success")
42
- if (!await this.hasNpmScript(target)) {
43
- return;
44
- }
45
45
  /**
46
46
  * Handling abort signal from the abort controller. Gracefully kills the process,
47
47
  * will be handled by exit handler separately to resolve the promise.
@@ -1,4 +1,5 @@
1
1
  import type { TargetRunner, TargetRunnerOptions } from "@lage-run/scheduler-types";
2
+ import type { Target } from "@lage-run/target-graph";
2
3
  export interface WorkerRunnerOptions {
3
4
  taskArgs: string[];
4
5
  }
@@ -41,5 +42,7 @@ export declare class WorkerRunner implements TargetRunner {
41
42
  private options;
42
43
  static gracefulKillTimeout: number;
43
44
  constructor(options: WorkerRunnerOptions);
45
+ shouldRun(target: Target): Promise<boolean>;
44
46
  run(runOptions: TargetRunnerOptions): Promise<void>;
47
+ getScriptModule(target: Target): Promise<any>;
45
48
  }
@@ -8,19 +8,18 @@ Object.defineProperty(exports, "WorkerRunner", {
8
8
  });
9
9
  const _url = require("url");
10
10
  class WorkerRunner {
11
+ async shouldRun(target) {
12
+ const scriptModule = await this.getScriptModule(target);
13
+ if (typeof scriptModule.shouldRun === "function") {
14
+ return await scriptModule.shouldRun(target);
15
+ }
16
+ return true;
17
+ }
11
18
  async run(runOptions) {
12
19
  const { target , weight , abortSignal } = runOptions;
13
20
  const { taskArgs } = this.options;
14
- const scriptFile = target.options?.worker ?? target.options?.script;
15
- if (!scriptFile) {
16
- throw new Error('WorkerRunner: "script" configuration is required - e.g. { type: "worker", script: "./worker.js" }');
17
- }
18
- let importScript = scriptFile;
19
- if (!importScript.startsWith("file://")) {
20
- importScript = (0, _url.pathToFileURL)(importScript).toString();
21
- }
22
- const scriptModule = await import(importScript);
23
- const runFn = typeof scriptModule.default === "function" ? scriptModule.default : scriptModule;
21
+ const scriptModule = await this.getScriptModule(target);
22
+ const runFn = typeof scriptModule.run === "function" ? scriptModule.run : typeof scriptModule.default === "function" ? scriptModule.default : scriptModule;
24
23
  if (typeof runFn !== "function") {
25
24
  throw new Error("WorkerRunner: worker script must export a function; you likely need to use `module.exports = function() {...}`");
26
25
  }
@@ -31,6 +30,17 @@ class WorkerRunner {
31
30
  abortSignal
32
31
  });
33
32
  }
33
+ async getScriptModule(target) {
34
+ const scriptFile = target.options?.worker ?? target.options?.script;
35
+ if (!scriptFile) {
36
+ throw new Error('WorkerRunner: "script" configuration is required - e.g. { type: "worker", script: "./worker.js" }');
37
+ }
38
+ let importScript = scriptFile;
39
+ if (!importScript.startsWith("file://")) {
40
+ importScript = (0, _url.pathToFileURL)(importScript).toString();
41
+ }
42
+ return await import(importScript);
43
+ }
34
44
  constructor(options){
35
45
  this.options = options;
36
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lage-run/scheduler",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "Scheduler for Lage",
5
5
  "repository": {
6
6
  "url": "https://github.com/microsoft/lage"
@@ -21,7 +21,7 @@
21
21
  "@lage-run/worker-threads-pool": "^0.5.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@lage-run/scheduler-types": "^0.2.10",
24
+ "@lage-run/scheduler-types": "^0.3.0",
25
25
  "monorepo-scripts": "*"
26
26
  },
27
27
  "publishConfig": {