@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 +37 -1
- package/CHANGELOG.md +19 -2
- package/lib/SimpleScheduler.d.ts +2 -1
- package/lib/SimpleScheduler.js +33 -18
- package/lib/WrappedTarget.d.ts +1 -1
- package/lib/WrappedTarget.js +14 -14
- package/lib/runners/NoOpRunner.js +3 -0
- package/lib/runners/NpmScriptRunner.d.ts +2 -0
- package/lib/runners/NpmScriptRunner.js +5 -5
- package/lib/runners/WorkerRunner.d.ts +3 -0
- package/lib/runners/WorkerRunner.js +20 -10
- package/package.json +2 -2
package/CHANGELOG.json
CHANGED
|
@@ -2,7 +2,43 @@
|
|
|
2
2
|
"name": "@lage-run/scheduler",
|
|
3
3
|
"entries": [
|
|
4
4
|
{
|
|
5
|
-
"date": "
|
|
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
|
|
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:
|
|
26
|
+
Thu, 08 Dec 2022 00:49:28 GMT
|
|
10
27
|
|
|
11
28
|
### Patches
|
|
12
29
|
|
package/lib/SimpleScheduler.d.ts
CHANGED
|
@@ -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
|
-
|
|
44
|
+
runnerPicker: TargetRunnerPicker;
|
|
44
45
|
runPromise: Promise<any>;
|
|
45
46
|
constructor(options: SimpleSchedulerOptions);
|
|
46
47
|
getTargetsByPriority(): import("@lage-run/target-graph").Target[];
|
package/lib/SimpleScheduler.js
CHANGED
|
@@ -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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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();
|
package/lib/WrappedTarget.d.ts
CHANGED
|
@@ -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;
|
package/lib/WrappedTarget.js
CHANGED
|
@@ -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("
|
|
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("
|
|
50
|
+
this.options.logger.info("", {
|
|
51
51
|
target: this.target,
|
|
52
52
|
status: "running",
|
|
53
|
-
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("
|
|
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("
|
|
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(
|
|
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, (
|
|
196
|
-
|
|
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
|
|
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
|
|
209
|
+
threadId
|
|
210
210
|
});
|
|
211
211
|
releaseStderr = ()=>{
|
|
212
212
|
releaseStderrStream();
|
|
@@ -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
|
|
15
|
-
|
|
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.
|
|
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.
|
|
24
|
+
"@lage-run/scheduler-types": "^0.3.0",
|
|
25
25
|
"monorepo-scripts": "*"
|
|
26
26
|
},
|
|
27
27
|
"publishConfig": {
|