@blamejs/exceptd-skills 0.9.1
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/AGENTS.md +232 -0
- package/ARCHITECTURE.md +267 -0
- package/CHANGELOG.md +616 -0
- package/CONTEXT.md +203 -0
- package/LICENSE +200 -0
- package/NOTICE +82 -0
- package/README.md +307 -0
- package/SECURITY.md +73 -0
- package/agents/README.md +81 -0
- package/agents/report-generator.md +156 -0
- package/agents/skill-updater.md +102 -0
- package/agents/source-validator.md +119 -0
- package/agents/threat-researcher.md +149 -0
- package/bin/exceptd.js +183 -0
- package/data/_indexes/_meta.json +88 -0
- package/data/_indexes/activity-feed.json +362 -0
- package/data/_indexes/catalog-summaries.json +229 -0
- package/data/_indexes/chains.json +7135 -0
- package/data/_indexes/currency.json +359 -0
- package/data/_indexes/did-ladders.json +451 -0
- package/data/_indexes/frequency.json +2072 -0
- package/data/_indexes/handoff-dag.json +476 -0
- package/data/_indexes/jurisdiction-clocks.json +967 -0
- package/data/_indexes/jurisdiction-map.json +536 -0
- package/data/_indexes/recipes.json +319 -0
- package/data/_indexes/section-offsets.json +3656 -0
- package/data/_indexes/stale-content.json +14 -0
- package/data/_indexes/summary-cards.json +1736 -0
- package/data/_indexes/theater-fingerprints.json +381 -0
- package/data/_indexes/token-budget.json +2137 -0
- package/data/_indexes/trigger-table.json +1374 -0
- package/data/_indexes/xref.json +818 -0
- package/data/atlas-ttps.json +282 -0
- package/data/cve-catalog.json +496 -0
- package/data/cwe-catalog.json +1017 -0
- package/data/d3fend-catalog.json +738 -0
- package/data/dlp-controls.json +1039 -0
- package/data/exploit-availability.json +67 -0
- package/data/framework-control-gaps.json +1255 -0
- package/data/global-frameworks.json +2913 -0
- package/data/rfc-references.json +324 -0
- package/data/zeroday-lessons.json +377 -0
- package/keys/public.pem +3 -0
- package/lib/framework-gap.js +328 -0
- package/lib/job-queue.js +195 -0
- package/lib/lint-skills.js +536 -0
- package/lib/prefetch.js +372 -0
- package/lib/refresh-external.js +713 -0
- package/lib/schemas/cve-catalog.schema.json +151 -0
- package/lib/schemas/manifest.schema.json +106 -0
- package/lib/schemas/skill-frontmatter.schema.json +113 -0
- package/lib/scoring.js +149 -0
- package/lib/sign.js +197 -0
- package/lib/ttp-mapper.js +80 -0
- package/lib/validate-catalog-meta.js +198 -0
- package/lib/validate-cve-catalog.js +213 -0
- package/lib/validate-indexes.js +83 -0
- package/lib/validate-package.js +162 -0
- package/lib/validate-vendor.js +85 -0
- package/lib/verify.js +216 -0
- package/lib/worker-pool.js +84 -0
- package/manifest-snapshot.json +1833 -0
- package/manifest.json +2108 -0
- package/orchestrator/README.md +124 -0
- package/orchestrator/dispatcher.js +140 -0
- package/orchestrator/event-bus.js +146 -0
- package/orchestrator/index.js +874 -0
- package/orchestrator/pipeline.js +201 -0
- package/orchestrator/scanner.js +327 -0
- package/orchestrator/scheduler.js +137 -0
- package/package.json +113 -0
- package/sbom.cdx.json +158 -0
- package/scripts/audit-cross-skill.js +261 -0
- package/scripts/audit-perf.js +160 -0
- package/scripts/bootstrap.js +205 -0
- package/scripts/build-indexes.js +721 -0
- package/scripts/builders/activity-feed.js +79 -0
- package/scripts/builders/catalog-summaries.js +67 -0
- package/scripts/builders/currency.js +109 -0
- package/scripts/builders/cwe-chains.js +105 -0
- package/scripts/builders/did-ladders.js +149 -0
- package/scripts/builders/frequency.js +89 -0
- package/scripts/builders/jurisdiction-clocks.js +126 -0
- package/scripts/builders/recipes.js +159 -0
- package/scripts/builders/section-offsets.js +162 -0
- package/scripts/builders/stale-content.js +171 -0
- package/scripts/builders/summary-cards.js +166 -0
- package/scripts/builders/theater-fingerprints.js +198 -0
- package/scripts/builders/token-budget.js +96 -0
- package/scripts/check-manifest-snapshot.js +217 -0
- package/scripts/predeploy.js +267 -0
- package/scripts/refresh-manifest-snapshot.js +57 -0
- package/scripts/refresh-sbom.js +222 -0
- package/skills/age-gates-child-safety/skill.md +456 -0
- package/skills/ai-attack-surface/skill.md +282 -0
- package/skills/ai-c2-detection/skill.md +440 -0
- package/skills/ai-risk-management/skill.md +311 -0
- package/skills/api-security/skill.md +287 -0
- package/skills/attack-surface-pentest/skill.md +381 -0
- package/skills/cloud-security/skill.md +384 -0
- package/skills/compliance-theater/skill.md +365 -0
- package/skills/container-runtime-security/skill.md +379 -0
- package/skills/coordinated-vuln-disclosure/skill.md +473 -0
- package/skills/defensive-countermeasure-mapping/skill.md +300 -0
- package/skills/dlp-gap-analysis/skill.md +337 -0
- package/skills/email-security-anti-phishing/skill.md +206 -0
- package/skills/exploit-scoring/skill.md +331 -0
- package/skills/framework-gap-analysis/skill.md +374 -0
- package/skills/fuzz-testing-strategy/skill.md +313 -0
- package/skills/global-grc/skill.md +564 -0
- package/skills/identity-assurance/skill.md +272 -0
- package/skills/incident-response-playbook/skill.md +546 -0
- package/skills/kernel-lpe-triage/skill.md +303 -0
- package/skills/mcp-agent-trust/skill.md +326 -0
- package/skills/mlops-security/skill.md +325 -0
- package/skills/ot-ics-security/skill.md +340 -0
- package/skills/policy-exception-gen/skill.md +437 -0
- package/skills/pqc-first/skill.md +546 -0
- package/skills/rag-pipeline-security/skill.md +294 -0
- package/skills/researcher/skill.md +310 -0
- package/skills/sector-energy/skill.md +409 -0
- package/skills/sector-federal-government/skill.md +302 -0
- package/skills/sector-financial/skill.md +398 -0
- package/skills/sector-healthcare/skill.md +373 -0
- package/skills/security-maturity-tiers/skill.md +464 -0
- package/skills/skill-update-loop/skill.md +463 -0
- package/skills/supply-chain-integrity/skill.md +318 -0
- package/skills/threat-model-currency/skill.md +404 -0
- package/skills/threat-modeling-methodology/skill.md +312 -0
- package/skills/webapp-security/skill.md +281 -0
- package/skills/zeroday-gap-learn/skill.md +350 -0
- package/vendor/blamejs/LICENSE +201 -0
- package/vendor/blamejs/README.md +54 -0
- package/vendor/blamejs/_PROVENANCE.json +54 -0
- package/vendor/blamejs/retry.js +335 -0
- package/vendor/blamejs/worker-pool.js +418 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* vendor/blamejs/worker-pool.js
|
|
4
|
+
*
|
|
5
|
+
* VENDORED — flattened and stripped from blamejs@1442f17 / lib/worker-pool.js.
|
|
6
|
+
* Apache-2.0 (see vendor/blamejs/LICENSE). Provenance: vendor/blamejs/_PROVENANCE.json.
|
|
7
|
+
*
|
|
8
|
+
* Surface preserved:
|
|
9
|
+
* - create(scriptPath, opts) → { run, drain, terminate, stats }
|
|
10
|
+
* - bounded concurrency (`size`), default max(2, cpus)
|
|
11
|
+
* - bounded in-memory queue (`maxQueueDepth`, default 1024, cap 1_048_576)
|
|
12
|
+
* - per-task timeout (`taskTimeoutMs`, default 5min, cap 1h)
|
|
13
|
+
* - per-task message/transferList contract; worker reply envelope { ok, result } or { ok:false, message }
|
|
14
|
+
* - worker recycle on uncaught error / timeout / exit
|
|
15
|
+
* - drain() resolves when no in-flight + queue empty
|
|
16
|
+
*
|
|
17
|
+
* Stripped vs. upstream:
|
|
18
|
+
* - WorkerPoolError class — replaced with vanilla Error tagged with a `code` string
|
|
19
|
+
* (e.g. "workerpool/queue-full")
|
|
20
|
+
* - validate-opts dependency — replaced with inline option-whitelist + type checks
|
|
21
|
+
* - numeric-bounds dependency — replaced with two inline predicates
|
|
22
|
+
* - audit event sink — `_emitAudit` is a no-op stub (audit chain isn't part of exceptd)
|
|
23
|
+
* - constants C.BYTES.kib / C.TIME.minutes — replaced with literal int values
|
|
24
|
+
*
|
|
25
|
+
* Worker contract (operator-supplied script at scriptPath, unchanged):
|
|
26
|
+
*
|
|
27
|
+
* var { parentPort } = require("node:worker_threads");
|
|
28
|
+
* parentPort.on("message", function (msg) {
|
|
29
|
+
* try {
|
|
30
|
+
* var result = doWork(msg);
|
|
31
|
+
* parentPort.postMessage({ ok: true, result: result });
|
|
32
|
+
* } catch (e) {
|
|
33
|
+
* parentPort.postMessage({ ok: false, message: e.message });
|
|
34
|
+
* }
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* Do NOT hand-edit. To re-vendor, copy the upstream file, re-apply the
|
|
38
|
+
* strip rules above, refresh sha256 in _PROVENANCE.json, then re-run
|
|
39
|
+
* `npm run validate-vendor`.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
var os = require("node:os");
|
|
43
|
+
var path = require("node:path");
|
|
44
|
+
|
|
45
|
+
var MIN_SIZE = 1;
|
|
46
|
+
var MAX_SIZE = 256;
|
|
47
|
+
var DEFAULT_MAX_QUEUE_DEPTH = 1024;
|
|
48
|
+
var MAX_QUEUE_DEPTH_CAP = 1048576;
|
|
49
|
+
var DEFAULT_TASK_TIMEOUT_MS = 5 * 60 * 1000; // 5 min
|
|
50
|
+
var MAX_TASK_TIMEOUT_MS = 60 * 60 * 1000; // 1 hour
|
|
51
|
+
|
|
52
|
+
function _err(code, message) {
|
|
53
|
+
var e = new Error(message);
|
|
54
|
+
e.code = code;
|
|
55
|
+
return e;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function _isPositiveInt(v) {
|
|
59
|
+
return typeof v === "number" && Number.isFinite(v) && Number.isInteger(v) && v > 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function _requireNonEmptyString(v, label, code) {
|
|
63
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
64
|
+
throw _err(code, label + ": must be a non-empty string");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function _validateOptsWhitelist(opts, allowed, label) {
|
|
69
|
+
if (opts === null || opts === undefined) return;
|
|
70
|
+
if (typeof opts !== "object") {
|
|
71
|
+
throw _err("workerpool/bad-opts", label + ": opts must be a plain object");
|
|
72
|
+
}
|
|
73
|
+
for (var k in opts) {
|
|
74
|
+
if (Object.prototype.hasOwnProperty.call(opts, k) && allowed.indexOf(k) === -1) {
|
|
75
|
+
throw _err("workerpool/bad-opts", label + ": unknown opt \"" + k + "\"; allowed: " + allowed.join(", "));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function _validateScriptPath(scriptPath) {
|
|
81
|
+
_requireNonEmptyString(scriptPath, "workerPool.create: scriptPath", "workerpool/bad-script-path");
|
|
82
|
+
if (!path.isAbsolute(scriptPath)) {
|
|
83
|
+
throw _err("workerpool/bad-script-path",
|
|
84
|
+
"workerPool.create: scriptPath must be an absolute path; got " + JSON.stringify(scriptPath));
|
|
85
|
+
}
|
|
86
|
+
if (/^data:/i.test(scriptPath) || /^eval:/i.test(scriptPath)) {
|
|
87
|
+
throw _err("workerpool/bad-script-path",
|
|
88
|
+
"workerPool.create: scriptPath must be a filesystem path, not an eval/data URL");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function _emitAudit(_action, _outcome, _metadata) {
|
|
93
|
+
// No-op stub — upstream uses an audit chain that exceptd does not have.
|
|
94
|
+
// Preserved as a function so the rest of the file matches upstream shape.
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function create(scriptPath, opts) {
|
|
98
|
+
opts = opts || {};
|
|
99
|
+
_validateOptsWhitelist(opts, ["size", "onExit", "maxQueueDepth", "taskTimeoutMs"], "workerPool.create");
|
|
100
|
+
_validateScriptPath(scriptPath);
|
|
101
|
+
|
|
102
|
+
var defaultSize = Math.max(2, (os.cpus() || []).length || 2);
|
|
103
|
+
var size = (opts.size === undefined) ? defaultSize : opts.size;
|
|
104
|
+
if (!_isPositiveInt(size) || size < MIN_SIZE || size > MAX_SIZE) {
|
|
105
|
+
throw _err("workerpool/bad-size",
|
|
106
|
+
"workerPool.create: opts.size must be a positive finite integer in [" +
|
|
107
|
+
MIN_SIZE + ".." + MAX_SIZE + "]");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
var maxQueueDepth = (opts.maxQueueDepth === undefined) ? DEFAULT_MAX_QUEUE_DEPTH : opts.maxQueueDepth;
|
|
111
|
+
if (!_isPositiveInt(maxQueueDepth) || maxQueueDepth > MAX_QUEUE_DEPTH_CAP) {
|
|
112
|
+
throw _err("workerpool/bad-max-queue-depth",
|
|
113
|
+
"workerPool.create: opts.maxQueueDepth must be a positive finite integer <= " + MAX_QUEUE_DEPTH_CAP);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
var taskTimeoutMs = (opts.taskTimeoutMs === undefined) ? DEFAULT_TASK_TIMEOUT_MS : opts.taskTimeoutMs;
|
|
117
|
+
if (!_isPositiveInt(taskTimeoutMs) || taskTimeoutMs > MAX_TASK_TIMEOUT_MS) {
|
|
118
|
+
throw _err("workerpool/bad-task-timeout",
|
|
119
|
+
"workerPool.create: opts.taskTimeoutMs must be a positive finite integer <= " + MAX_TASK_TIMEOUT_MS);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
var onExit = opts.onExit;
|
|
123
|
+
if (onExit !== undefined && onExit !== null && typeof onExit !== "function") {
|
|
124
|
+
throw _err("workerpool/bad-on-exit", "workerPool.create: opts.onExit must be a function");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
var workerThreads;
|
|
128
|
+
try { workerThreads = require("node:worker_threads"); }
|
|
129
|
+
catch (_e) {
|
|
130
|
+
throw _err("workerpool/no-worker-threads",
|
|
131
|
+
"workerPool.create: node:worker_threads is unavailable in this runtime");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
var workerSlots = [];
|
|
135
|
+
var workerSeq = 0;
|
|
136
|
+
var taskSeq = 0;
|
|
137
|
+
var queue = [];
|
|
138
|
+
var totalTasks = 0;
|
|
139
|
+
var totalErrors = 0;
|
|
140
|
+
var terminated = false;
|
|
141
|
+
var drainResolvers = [];
|
|
142
|
+
|
|
143
|
+
function _spawnWorker() {
|
|
144
|
+
var id = ++workerSeq;
|
|
145
|
+
var worker;
|
|
146
|
+
try {
|
|
147
|
+
worker = new workerThreads.Worker(scriptPath);
|
|
148
|
+
} catch (eSpawn) {
|
|
149
|
+
_emitAudit("workerpool.spawn.failed", "failure", {
|
|
150
|
+
scriptPath: scriptPath,
|
|
151
|
+
message: (eSpawn && eSpawn.message) || String(eSpawn),
|
|
152
|
+
});
|
|
153
|
+
throw _err("workerpool/spawn-failed",
|
|
154
|
+
"workerPool.create: failed to spawn worker: " + (eSpawn && eSpawn.message));
|
|
155
|
+
}
|
|
156
|
+
var slot = {
|
|
157
|
+
id: id,
|
|
158
|
+
worker: worker,
|
|
159
|
+
busy: false,
|
|
160
|
+
currentTaskId: null,
|
|
161
|
+
currentTimer: null,
|
|
162
|
+
currentTask: null,
|
|
163
|
+
};
|
|
164
|
+
worker.on("message", function (msg) { _onWorkerMessage(slot, msg); });
|
|
165
|
+
worker.on("error", function (err) { _onWorkerError(slot, err); });
|
|
166
|
+
worker.on("exit", function (code) { _onWorkerExit(slot, code); });
|
|
167
|
+
workerSlots.push(slot);
|
|
168
|
+
_emitAudit("workerpool.created", "success", { workerId: id, size: size });
|
|
169
|
+
return slot;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function _findIdleSlot() {
|
|
173
|
+
for (var i = 0; i < workerSlots.length; i += 1) {
|
|
174
|
+
if (!workerSlots[i].busy && !workerSlots[i].recycling) return workerSlots[i];
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function _dispatch(slot, task) {
|
|
180
|
+
slot.busy = true;
|
|
181
|
+
slot.currentTaskId = task.id;
|
|
182
|
+
slot.currentTask = task;
|
|
183
|
+
slot.currentTimer = setTimeout(function () {
|
|
184
|
+
_onTaskTimeout(slot);
|
|
185
|
+
}, taskTimeoutMs);
|
|
186
|
+
if (slot.currentTimer && typeof slot.currentTimer.unref === "function") {
|
|
187
|
+
slot.currentTimer.unref();
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
slot.worker.postMessage(task.message, task.transferList || undefined);
|
|
191
|
+
} catch (ePost) {
|
|
192
|
+
_finishTask(slot, true,
|
|
193
|
+
_err("workerpool/post-failed", "workerPool.run: postMessage failed: " + (ePost && ePost.message)));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function _drainQueue() {
|
|
198
|
+
while (!terminated && queue.length > 0) {
|
|
199
|
+
var slot = _findIdleSlot();
|
|
200
|
+
if (!slot) return;
|
|
201
|
+
var task = queue.shift();
|
|
202
|
+
_dispatch(slot, task);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function _finishTask(slot, isError, payloadOrError) {
|
|
207
|
+
var task = slot.currentTask;
|
|
208
|
+
if (!task) return;
|
|
209
|
+
if (slot.currentTimer) { clearTimeout(slot.currentTimer); slot.currentTimer = null; }
|
|
210
|
+
slot.busy = false;
|
|
211
|
+
slot.currentTaskId = null;
|
|
212
|
+
slot.currentTask = null;
|
|
213
|
+
totalTasks += 1;
|
|
214
|
+
if (isError) {
|
|
215
|
+
totalErrors += 1;
|
|
216
|
+
task.reject(payloadOrError);
|
|
217
|
+
} else {
|
|
218
|
+
task.resolve(payloadOrError);
|
|
219
|
+
}
|
|
220
|
+
_maybeResolveDrain();
|
|
221
|
+
_drainQueue();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function _onWorkerMessage(slot, msg) {
|
|
225
|
+
if (!slot.currentTask) return;
|
|
226
|
+
if (!msg || typeof msg !== "object" || typeof msg.ok !== "boolean") {
|
|
227
|
+
_emitAudit("workerpool.task.failed", "failure", {
|
|
228
|
+
workerId: slot.id, taskId: slot.currentTaskId, reason: "workerpool/worker-bad-message",
|
|
229
|
+
});
|
|
230
|
+
_finishTask(slot, true,
|
|
231
|
+
_err("workerpool/worker-bad-message",
|
|
232
|
+
"workerPool: worker reply was not { ok, ... } envelope-shaped"));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (msg.ok) {
|
|
236
|
+
_emitAudit("workerpool.task.completed", "success", {
|
|
237
|
+
workerId: slot.id, taskId: slot.currentTaskId,
|
|
238
|
+
});
|
|
239
|
+
_finishTask(slot, false, msg.result);
|
|
240
|
+
} else {
|
|
241
|
+
_emitAudit("workerpool.task.failed", "failure", {
|
|
242
|
+
workerId: slot.id, taskId: slot.currentTaskId,
|
|
243
|
+
reason: "workerpool/task-failed",
|
|
244
|
+
message: msg.message || "",
|
|
245
|
+
});
|
|
246
|
+
_finishTask(slot, true,
|
|
247
|
+
_err("workerpool/task-failed",
|
|
248
|
+
"workerPool: worker reported failure: " + (msg.message || "(no message)")));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function _onWorkerError(slot, err) {
|
|
253
|
+
var failingTask = slot.currentTask;
|
|
254
|
+
_emitAudit("workerpool.task.failed", "failure", {
|
|
255
|
+
workerId: slot.id, taskId: slot.currentTaskId,
|
|
256
|
+
reason: "workerpool/worker-error",
|
|
257
|
+
message: (err && err.message) || String(err),
|
|
258
|
+
});
|
|
259
|
+
if (failingTask) {
|
|
260
|
+
_finishTask(slot, true,
|
|
261
|
+
_err("workerpool/worker-error",
|
|
262
|
+
"workerPool: worker errored: " + (err && err.message ? err.message : String(err))));
|
|
263
|
+
}
|
|
264
|
+
_recycleWorker(slot);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function _onWorkerExit(slot, code) {
|
|
268
|
+
var failingTask = slot.currentTask;
|
|
269
|
+
if (failingTask) {
|
|
270
|
+
_emitAudit("workerpool.task.failed", "failure", {
|
|
271
|
+
workerId: slot.id, taskId: slot.currentTaskId,
|
|
272
|
+
reason: "workerpool/worker-exit", code: code,
|
|
273
|
+
});
|
|
274
|
+
_finishTask(slot, true,
|
|
275
|
+
_err("workerpool/worker-exit",
|
|
276
|
+
"workerPool: worker exited (code " + code + ") mid-task"));
|
|
277
|
+
}
|
|
278
|
+
_emitAudit("workerpool.terminated", "success", { workerId: slot.id, code: code });
|
|
279
|
+
if (typeof onExit === "function") {
|
|
280
|
+
try { onExit(code, slot.id); } catch (_e) { /* operator hook best-effort */ }
|
|
281
|
+
}
|
|
282
|
+
var idx = workerSlots.indexOf(slot);
|
|
283
|
+
if (idx !== -1) workerSlots.splice(idx, 1);
|
|
284
|
+
if (!terminated && workerSlots.length < size) {
|
|
285
|
+
try { _spawnWorker(); } catch (_e) { /* already audited */ }
|
|
286
|
+
_drainQueue();
|
|
287
|
+
} else {
|
|
288
|
+
_maybeResolveDrain();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function _onTaskTimeout(slot) {
|
|
293
|
+
var taskId = slot.currentTaskId;
|
|
294
|
+
_emitAudit("workerpool.task.timeout", "failure", {
|
|
295
|
+
workerId: slot.id, taskId: taskId, taskTimeoutMs: taskTimeoutMs,
|
|
296
|
+
});
|
|
297
|
+
var failingTask = slot.currentTask;
|
|
298
|
+
if (failingTask) {
|
|
299
|
+
_finishTask(slot, true,
|
|
300
|
+
_err("workerpool/timeout",
|
|
301
|
+
"workerPool: task " + taskId + " exceeded taskTimeoutMs=" + taskTimeoutMs));
|
|
302
|
+
}
|
|
303
|
+
_recycleWorker(slot);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function _recycleWorker(slot) {
|
|
307
|
+
slot.busy = true;
|
|
308
|
+
slot.recycling = true;
|
|
309
|
+
try { slot.worker.terminate(); } catch (_e) { /* best-effort */ }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function _maybeResolveDrain() {
|
|
313
|
+
if (drainResolvers.length === 0) return;
|
|
314
|
+
var anyBusy = false;
|
|
315
|
+
for (var i = 0; i < workerSlots.length; i += 1) {
|
|
316
|
+
if (workerSlots[i].busy) { anyBusy = true; break; }
|
|
317
|
+
}
|
|
318
|
+
if (anyBusy || queue.length > 0) return;
|
|
319
|
+
var pending = drainResolvers.splice(0, drainResolvers.length);
|
|
320
|
+
for (var j = 0; j < pending.length; j += 1) {
|
|
321
|
+
try { pending[j](); } catch (_e) { /* best-effort */ }
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function run(message, transferList) {
|
|
326
|
+
if (terminated) {
|
|
327
|
+
return Promise.reject(_err("workerpool/terminated", "workerPool.run: pool has been terminated"));
|
|
328
|
+
}
|
|
329
|
+
if (transferList !== undefined && transferList !== null && !Array.isArray(transferList)) {
|
|
330
|
+
return Promise.reject(_err("workerpool/bad-transfer-list",
|
|
331
|
+
"workerPool.run: transferList must be an array if supplied"));
|
|
332
|
+
}
|
|
333
|
+
if (queue.length >= maxQueueDepth) {
|
|
334
|
+
return Promise.reject(_err("workerpool/queue-full",
|
|
335
|
+
"workerPool.run: queue is full (depth=" + queue.length + " >= maxQueueDepth=" + maxQueueDepth + ")"));
|
|
336
|
+
}
|
|
337
|
+
var taskId = ++taskSeq;
|
|
338
|
+
return new Promise(function (resolve, reject) {
|
|
339
|
+
var task = {
|
|
340
|
+
id: taskId,
|
|
341
|
+
message: message,
|
|
342
|
+
transferList: transferList || null,
|
|
343
|
+
resolve: resolve,
|
|
344
|
+
reject: reject,
|
|
345
|
+
};
|
|
346
|
+
var slot = _findIdleSlot();
|
|
347
|
+
if (slot) {
|
|
348
|
+
_dispatch(slot, task);
|
|
349
|
+
} else {
|
|
350
|
+
queue.push(task);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function drain() {
|
|
356
|
+
return new Promise(function (resolve) {
|
|
357
|
+
var anyBusy = false;
|
|
358
|
+
for (var i = 0; i < workerSlots.length; i += 1) {
|
|
359
|
+
if (workerSlots[i].busy) { anyBusy = true; break; }
|
|
360
|
+
}
|
|
361
|
+
if (!anyBusy && queue.length === 0) { resolve(); return; }
|
|
362
|
+
drainResolvers.push(resolve);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function terminate() {
|
|
367
|
+
terminated = true;
|
|
368
|
+
var pending = queue.splice(0, queue.length);
|
|
369
|
+
for (var i = 0; i < pending.length; i += 1) {
|
|
370
|
+
try {
|
|
371
|
+
pending[i].reject(_err("workerpool/terminated",
|
|
372
|
+
"workerPool.terminate: task aborted before dispatch"));
|
|
373
|
+
} catch (_e) { /* best-effort */ }
|
|
374
|
+
}
|
|
375
|
+
var promises = [];
|
|
376
|
+
for (var j = 0; j < workerSlots.length; j += 1) {
|
|
377
|
+
var slot = workerSlots[j];
|
|
378
|
+
if (slot.currentTimer) { clearTimeout(slot.currentTimer); slot.currentTimer = null; }
|
|
379
|
+
try { promises.push(slot.worker.terminate()); }
|
|
380
|
+
catch (_e) { /* best-effort */ }
|
|
381
|
+
}
|
|
382
|
+
return Promise.all(promises).then(function () { /* swallow */ });
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function stats() {
|
|
386
|
+
var busy = 0;
|
|
387
|
+
for (var i = 0; i < workerSlots.length; i += 1) {
|
|
388
|
+
if (workerSlots[i].busy) busy += 1;
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
size: workerSlots.length,
|
|
392
|
+
busy: busy,
|
|
393
|
+
idle: workerSlots.length - busy,
|
|
394
|
+
queued: queue.length,
|
|
395
|
+
totalTasks: totalTasks,
|
|
396
|
+
totalErrors: totalErrors,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
for (var k = 0; k < size; k += 1) _spawnWorker();
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
run: run,
|
|
404
|
+
drain: drain,
|
|
405
|
+
terminate: terminate,
|
|
406
|
+
stats: stats,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
module.exports = {
|
|
411
|
+
create: create,
|
|
412
|
+
MIN_SIZE: MIN_SIZE,
|
|
413
|
+
MAX_SIZE: MAX_SIZE,
|
|
414
|
+
DEFAULT_MAX_QUEUE_DEPTH: DEFAULT_MAX_QUEUE_DEPTH,
|
|
415
|
+
MAX_QUEUE_DEPTH_CAP: MAX_QUEUE_DEPTH_CAP,
|
|
416
|
+
DEFAULT_TASK_TIMEOUT_MS: DEFAULT_TASK_TIMEOUT_MS,
|
|
417
|
+
MAX_TASK_TIMEOUT_MS: MAX_TASK_TIMEOUT_MS,
|
|
418
|
+
};
|