@lokvis/runtime 0.2.0-beta.0 → 0.2.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/LICENSE +21 -0
- package/dist/asset-store.d.ts +5 -2
- package/dist/asset-store.d.ts.map +1 -1
- package/dist/asset-store.js +93 -3
- package/dist/asset-store.js.map +1 -1
- package/dist/batch-processor.d.ts +200 -0
- package/dist/batch-processor.d.ts.map +1 -0
- package/dist/batch-processor.js +505 -0
- package/dist/batch-processor.js.map +1 -0
- package/dist/degradation.d.ts +109 -0
- package/dist/degradation.d.ts.map +1 -0
- package/dist/degradation.js +184 -0
- package/dist/degradation.js.map +1 -0
- package/dist/history-store.d.ts +76 -0
- package/dist/history-store.d.ts.map +1 -0
- package/dist/history-store.js +109 -0
- package/dist/history-store.js.map +1 -0
- package/dist/idb-asset-store.js +1 -1
- package/dist/idb-asset-store.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/memory-guard.d.ts +109 -0
- package/dist/memory-guard.d.ts.map +1 -0
- package/dist/memory-guard.js +161 -0
- package/dist/memory-guard.js.map +1 -0
- package/dist/opfs-asset-store.d.ts +35 -4
- package/dist/opfs-asset-store.d.ts.map +1 -1
- package/dist/opfs-asset-store.js +94 -10
- package/dist/opfs-asset-store.js.map +1 -1
- package/dist/runtime.d.ts +111 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +311 -13
- package/dist/runtime.js.map +1 -1
- package/dist/types.d.ts +74 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/worker-host.d.ts +13 -0
- package/dist/worker-host.d.ts.map +1 -1
- package/dist/worker-host.js +47 -15
- package/dist/worker-host.js.map +1 -1
- package/dist/worker-protocol.d.ts +10 -1
- package/dist/worker-protocol.d.ts.map +1 -1
- package/dist/worker-protocol.js.map +1 -1
- package/dist/workflow-builder.d.ts +124 -0
- package/dist/workflow-builder.d.ts.map +1 -0
- package/dist/workflow-builder.js +240 -0
- package/dist/workflow-builder.js.map +1 -0
- package/package.json +3 -2
- package/src/asset-store.ts +103 -3
- package/src/batch-processor.ts +699 -0
- package/src/degradation.ts +241 -0
- package/src/history-store.ts +159 -0
- package/src/idb-asset-store.ts +1 -1
- package/src/index.ts +5 -0
- package/src/memory-guard.ts +202 -0
- package/src/opfs-asset-store.ts +118 -10
- package/src/runtime.ts +345 -9
- package/src/types.ts +70 -0
- package/src/worker-host.ts +56 -16
- package/src/worker-protocol.ts +11 -1
- package/src/workflow-builder.ts +303 -0
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import { MemoryGuard } from './memory-guard.js';
|
|
2
|
+
/** 批量上限超限错误(W6.2) */
|
|
3
|
+
export class BatchLimitExceededError extends Error {
|
|
4
|
+
limit;
|
|
5
|
+
requested;
|
|
6
|
+
constructor(limit, requested) {
|
|
7
|
+
super(`Batch limit exceeded: free tier allows up to ${limit} items, got ${requested}. ` +
|
|
8
|
+
'Upgrade to Pro for unlimited batch processing.');
|
|
9
|
+
this.name = 'BatchLimitExceededError';
|
|
10
|
+
this.limit = limit;
|
|
11
|
+
this.requested = requested;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** 免费版批量上限(W6.2) */
|
|
15
|
+
export const FREE_BATCH_LIMIT = 10;
|
|
16
|
+
/** 默认并发槽位 */
|
|
17
|
+
export const FREE_CONCURRENCY = 4;
|
|
18
|
+
export const PRO_CONCURRENCY = 16;
|
|
19
|
+
/** 按 MemoryPressure 收缩并发槽位 */
|
|
20
|
+
function shrinkConcurrencyByPressure(base, pressure) {
|
|
21
|
+
switch (pressure) {
|
|
22
|
+
case 'critical':
|
|
23
|
+
return 1;
|
|
24
|
+
case 'high':
|
|
25
|
+
return Math.max(1, Math.floor(base / 2));
|
|
26
|
+
case 'elevated':
|
|
27
|
+
return Math.max(2, Math.floor((base * 3) / 4));
|
|
28
|
+
default:
|
|
29
|
+
return base;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** 生成项 ID */
|
|
33
|
+
function generateItemId() {
|
|
34
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
35
|
+
return `bi_${crypto.randomUUID()}`;
|
|
36
|
+
}
|
|
37
|
+
return `bi_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
38
|
+
}
|
|
39
|
+
/** 生成 job ID */
|
|
40
|
+
function generateJobId() {
|
|
41
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
42
|
+
return `batch_${crypto.randomUUID()}`;
|
|
43
|
+
}
|
|
44
|
+
return `batch_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* BatchProcessor 实现。
|
|
48
|
+
*
|
|
49
|
+
* 不直接依赖 LokvisRuntimeImpl(避免循环引用),通过构造注入 LokvisRuntime 接口。
|
|
50
|
+
* 内部用纯 JS Map 管理作业,无 React state(可被 SDK / MCP / Node 复用)。
|
|
51
|
+
*/
|
|
52
|
+
export class BatchProcessor {
|
|
53
|
+
runtime;
|
|
54
|
+
eventBus;
|
|
55
|
+
isPro;
|
|
56
|
+
memoryGuard;
|
|
57
|
+
/** jobId → BatchJob 内部可变状态(含 private 字段) */
|
|
58
|
+
jobs = new Map();
|
|
59
|
+
/** 进度回调订阅 */
|
|
60
|
+
progressSubs = new Map();
|
|
61
|
+
constructor(opts) {
|
|
62
|
+
this.runtime = opts.runtime;
|
|
63
|
+
this.eventBus = opts.eventBus;
|
|
64
|
+
this.isPro = opts.isPro;
|
|
65
|
+
this.memoryGuard = opts.memoryGuard;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 入队批量作业。返回 job 视图(只读)。
|
|
69
|
+
* 超过免费上限时同步抛 BatchLimitExceededError,不创建 job。
|
|
70
|
+
* 空 items 抛错(避免静默产出 0 项 completed job,遮蔽调用方逻辑错误)。
|
|
71
|
+
*/
|
|
72
|
+
enqueue(options) {
|
|
73
|
+
const { items, maxRetries = 0, concurrency } = options;
|
|
74
|
+
// m3: 空数组视为调用方逻辑错误,显式抛错而非静默完成
|
|
75
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
76
|
+
throw new Error('BatchProcessor.enqueue: items cannot be empty');
|
|
77
|
+
}
|
|
78
|
+
// W6.2 批量上限:免费 10,Pro 无限
|
|
79
|
+
if (!this.isPro && items.length > FREE_BATCH_LIMIT) {
|
|
80
|
+
throw new BatchLimitExceededError(FREE_BATCH_LIMIT, items.length);
|
|
81
|
+
}
|
|
82
|
+
const jobId = generateJobId();
|
|
83
|
+
const baseConcurrency = concurrency ?? (this.isPro ? PRO_CONCURRENCY : FREE_CONCURRENCY);
|
|
84
|
+
const internal = {
|
|
85
|
+
id: jobId,
|
|
86
|
+
status: 'queued',
|
|
87
|
+
baseConcurrency,
|
|
88
|
+
maxRetries,
|
|
89
|
+
items: items.map((input, index) => ({
|
|
90
|
+
id: generateItemId(),
|
|
91
|
+
index,
|
|
92
|
+
status: 'pending',
|
|
93
|
+
source: input.source,
|
|
94
|
+
workflow: input.workflow,
|
|
95
|
+
attempts: 0,
|
|
96
|
+
maxRetries: input.maxRetries ?? maxRetries,
|
|
97
|
+
})),
|
|
98
|
+
completed: 0,
|
|
99
|
+
failed: 0,
|
|
100
|
+
startedAt: Date.now(),
|
|
101
|
+
/** schedule 重入锁(参考 W5 BatchQueue scheduleLock 修复) */
|
|
102
|
+
scheduleLock: false,
|
|
103
|
+
/** pause 时 schedule 不再补满新项 */
|
|
104
|
+
paused: false,
|
|
105
|
+
/** cancel 标记:已发起 cancel 后所有 pending → cancelled,schedule 终止 */
|
|
106
|
+
cancelled: false,
|
|
107
|
+
};
|
|
108
|
+
this.jobs.set(jobId, internal);
|
|
109
|
+
// 异步启动(不阻塞 enqueue 调用方)
|
|
110
|
+
void this.runJob(internal);
|
|
111
|
+
return this.toJobView(internal);
|
|
112
|
+
}
|
|
113
|
+
/** 列出所有作业(只读视图) */
|
|
114
|
+
list() {
|
|
115
|
+
return Array.from(this.jobs.values()).map((j) => this.toJobView(j));
|
|
116
|
+
}
|
|
117
|
+
/** 获取指定 job */
|
|
118
|
+
get(jobId) {
|
|
119
|
+
const internal = this.jobs.get(jobId);
|
|
120
|
+
return internal ? this.toJobView(internal) : undefined;
|
|
121
|
+
}
|
|
122
|
+
/** 取消整个 job:逐个取消 in-flight 项,pending 项标记 cancelled */
|
|
123
|
+
async cancel(jobId) {
|
|
124
|
+
const job = this.jobs.get(jobId);
|
|
125
|
+
if (!job)
|
|
126
|
+
return;
|
|
127
|
+
// m2: failed 也是终态,任何终态 job 不应被改写
|
|
128
|
+
if (this.isTerminal(job.status))
|
|
129
|
+
return;
|
|
130
|
+
// B1 修复:同步先标记 cancelled,使 in-flight processItem 续跑时读到
|
|
131
|
+
// cancelled=true 后短路,避免覆盖 cancelled 状态或把被取消的项误标 failed。
|
|
132
|
+
job.cancelled = true;
|
|
133
|
+
job.paused = false;
|
|
134
|
+
let cancelledCount = 0;
|
|
135
|
+
// 同步把所有 processing/pending 项标记 cancelled(processItem 续跑会读到此状态)
|
|
136
|
+
for (const item of job.items) {
|
|
137
|
+
if (item.status === 'processing' || item.status === 'pending') {
|
|
138
|
+
item.status = 'cancelled';
|
|
139
|
+
cancelledCount++;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// 异步取消所有 in-flight workflow(此时项状态已是 cancelled,
|
|
143
|
+
// processItem 续跑会因 job.cancelled 短路,不会回写其他状态)
|
|
144
|
+
for (const item of job.items) {
|
|
145
|
+
if (item.status === 'cancelled') {
|
|
146
|
+
const wfId = this.itemWorkflowId(job.id, item.id);
|
|
147
|
+
try {
|
|
148
|
+
await this.runtime.cancel(wfId);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// 取消失败不阻断,继续取消其他项
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
job.status = 'cancelled';
|
|
156
|
+
job.endedAt = Date.now();
|
|
157
|
+
this.emit({ type: 'batch:cancelled', jobId, cancelled: cancelledCount });
|
|
158
|
+
this.cleanupJobSubs(jobId);
|
|
159
|
+
}
|
|
160
|
+
/** 暂停 job:schedule 不再补满,已 in-flight 项跑完即止 */
|
|
161
|
+
async pause(jobId) {
|
|
162
|
+
const job = this.jobs.get(jobId);
|
|
163
|
+
if (!job)
|
|
164
|
+
return;
|
|
165
|
+
if (job.status !== 'running' && job.status !== 'queued')
|
|
166
|
+
return;
|
|
167
|
+
job.paused = true;
|
|
168
|
+
job.status = 'paused';
|
|
169
|
+
// m1: 发 pause 事件,UI 可订阅无需轮询
|
|
170
|
+
this.emit({ type: 'batch:paused', jobId });
|
|
171
|
+
}
|
|
172
|
+
/** 恢复 job:解除 pause,schedule 继续补满 */
|
|
173
|
+
async resume(jobId) {
|
|
174
|
+
const job = this.jobs.get(jobId);
|
|
175
|
+
if (!job)
|
|
176
|
+
return;
|
|
177
|
+
if (job.status !== 'paused')
|
|
178
|
+
return;
|
|
179
|
+
job.paused = false;
|
|
180
|
+
job.status = 'running';
|
|
181
|
+
// m1: 发 resumed 事件
|
|
182
|
+
this.emit({ type: 'batch:resumed', jobId });
|
|
183
|
+
void this.schedule(job);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 重试 job 中所有 failed 项(仅当 job 已结束)。
|
|
187
|
+
* 重置 attempts/error/status,pending 重新等待 schedule。
|
|
188
|
+
*/
|
|
189
|
+
async retryFailed(jobId) {
|
|
190
|
+
const job = this.jobs.get(jobId);
|
|
191
|
+
if (!job)
|
|
192
|
+
return;
|
|
193
|
+
if (job.status !== 'failed' && job.status !== 'completed')
|
|
194
|
+
return;
|
|
195
|
+
for (const item of job.items) {
|
|
196
|
+
if (item.status === 'failed') {
|
|
197
|
+
item.status = 'pending';
|
|
198
|
+
item.attempts = 0;
|
|
199
|
+
item.error = undefined;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
job.failed = 0;
|
|
203
|
+
job.cancelled = false;
|
|
204
|
+
job.status = 'running';
|
|
205
|
+
void this.schedule(job);
|
|
206
|
+
}
|
|
207
|
+
/** 订阅 job 进度(返回取消订阅函数) */
|
|
208
|
+
onProgress(jobId, handler) {
|
|
209
|
+
let subs = this.progressSubs.get(jobId);
|
|
210
|
+
if (!subs) {
|
|
211
|
+
subs = new Set();
|
|
212
|
+
this.progressSubs.set(jobId, subs);
|
|
213
|
+
}
|
|
214
|
+
subs.add(handler);
|
|
215
|
+
return () => {
|
|
216
|
+
subs.delete(handler);
|
|
217
|
+
if (subs.size === 0)
|
|
218
|
+
this.progressSubs.delete(jobId);
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* 等待 job 完成(completed/failed/cancelled 任一)。
|
|
223
|
+
*
|
|
224
|
+
* M5 修复:
|
|
225
|
+
* - 增加 timeoutMs(默认 5min),超时 reject,避免坏 job 永久挂起泄漏订阅
|
|
226
|
+
* - batch:completed 事件已涵盖 failed(maybeComplete 对 failed>0 的 job
|
|
227
|
+
* 同样发 batch:completed),故只需监听 completed + cancelled
|
|
228
|
+
* - resolve 后立即解绑所有订阅,避免调用方丢弃 promise 时泄漏
|
|
229
|
+
* - cancel/maybeComplete 进入终态时也会 cleanupJobSubs 兜底
|
|
230
|
+
*/
|
|
231
|
+
async waitForCompletion(jobId, timeoutMs = 5 * 60 * 1000) {
|
|
232
|
+
const job = this.jobs.get(jobId);
|
|
233
|
+
if (!job)
|
|
234
|
+
throw new Error(`Batch job not found: ${jobId}`);
|
|
235
|
+
if (this.isTerminal(job.status))
|
|
236
|
+
return this.toJobView(job);
|
|
237
|
+
return new Promise((resolve, reject) => {
|
|
238
|
+
let settled = false;
|
|
239
|
+
// 声明在前,finish 与各 handler 互相引用
|
|
240
|
+
let offCompleted;
|
|
241
|
+
let offCancelled;
|
|
242
|
+
let timer;
|
|
243
|
+
const finish = (fn) => {
|
|
244
|
+
if (settled)
|
|
245
|
+
return;
|
|
246
|
+
settled = true;
|
|
247
|
+
clearTimeout(timer);
|
|
248
|
+
offCompleted();
|
|
249
|
+
offCancelled();
|
|
250
|
+
fn();
|
|
251
|
+
};
|
|
252
|
+
offCompleted = this.eventBus.on('batch:completed', (e) => {
|
|
253
|
+
if (e.jobId !== jobId)
|
|
254
|
+
return;
|
|
255
|
+
finish(() => resolve(this.toJobView(this.jobs.get(jobId) ?? job)));
|
|
256
|
+
});
|
|
257
|
+
offCancelled = this.eventBus.on('batch:cancelled', (e) => {
|
|
258
|
+
if (e.jobId !== jobId)
|
|
259
|
+
return;
|
|
260
|
+
finish(() => resolve(this.toJobView(this.jobs.get(jobId) ?? job)));
|
|
261
|
+
});
|
|
262
|
+
timer = setTimeout(() => {
|
|
263
|
+
finish(() => reject(new Error(`BatchProcessor.waitForCompletion timed out after ${timeoutMs}ms (jobId=${jobId})`)));
|
|
264
|
+
}, timeoutMs);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
/** 清理 job 的所有进度订阅(终态时调用,避免泄漏) */
|
|
268
|
+
cleanupJobSubs(jobId) {
|
|
269
|
+
this.progressSubs.delete(jobId);
|
|
270
|
+
}
|
|
271
|
+
// ─── 内部实现 ────────────────────────────────────────
|
|
272
|
+
/** 启动 job:发 batch:started,进入 running,触发首次 schedule */
|
|
273
|
+
async runJob(job) {
|
|
274
|
+
if (job.cancelled)
|
|
275
|
+
return;
|
|
276
|
+
job.status = 'running';
|
|
277
|
+
this.emit({ type: 'batch:started', jobId: job.id, total: job.items.length });
|
|
278
|
+
await this.schedule(job);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* 调度循环:把 pending 项填入空闲槽位,直到无可用槽位或无 pending 项。
|
|
282
|
+
*
|
|
283
|
+
* 重入保护:用 scheduleLock 防止多个 schedule 调用并发导致超限(参考 W5
|
|
284
|
+
* BatchQueue.tsx 的 scheduleLock 修复:并发完成时多个 processItem 同时触发
|
|
285
|
+
* schedule 会读到相同 running 快照,导致并发超限)。
|
|
286
|
+
*/
|
|
287
|
+
async schedule(job) {
|
|
288
|
+
if (job.scheduleLock)
|
|
289
|
+
return;
|
|
290
|
+
if (job.cancelled || job.paused)
|
|
291
|
+
return;
|
|
292
|
+
if (this.isTerminal(job.status))
|
|
293
|
+
return;
|
|
294
|
+
job.scheduleLock = true;
|
|
295
|
+
try {
|
|
296
|
+
while (!job.cancelled && !job.paused) {
|
|
297
|
+
const running = job.items.filter((i) => i.status === 'processing').length;
|
|
298
|
+
const cap = this.currentConcurrency(job);
|
|
299
|
+
if (running >= cap)
|
|
300
|
+
break;
|
|
301
|
+
const next = job.items.find((i) => i.status === 'pending');
|
|
302
|
+
if (!next)
|
|
303
|
+
break;
|
|
304
|
+
// 标记 processing 并启动(不 await,让循环继续补满)
|
|
305
|
+
next.status = 'processing';
|
|
306
|
+
void this.processItem(job, next);
|
|
307
|
+
}
|
|
308
|
+
// 循环结束:检查是否所有项都已终结
|
|
309
|
+
this.maybeComplete(job);
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
job.scheduleLock = false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/** 处理单个项:导入 → run workflow → 导出 output AssetId */
|
|
316
|
+
async processItem(job, item) {
|
|
317
|
+
const start = Date.now();
|
|
318
|
+
const wfId = this.itemWorkflowId(job.id, item.id);
|
|
319
|
+
this.emit({
|
|
320
|
+
type: 'batch:item:started',
|
|
321
|
+
jobId: job.id,
|
|
322
|
+
itemId: item.id,
|
|
323
|
+
index: item.index,
|
|
324
|
+
total: job.items.length,
|
|
325
|
+
});
|
|
326
|
+
// B1 修复:cancel 已同步把 item 标为 cancelled,此处若发现 cancelled
|
|
327
|
+
// 直接短路,不发起 import/run,避免与 cancel 竞态
|
|
328
|
+
if (job.cancelled || item.status === 'cancelled') {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
let inputAssetId;
|
|
332
|
+
let result;
|
|
333
|
+
try {
|
|
334
|
+
// 1. 导入输入资产
|
|
335
|
+
inputAssetId = await this.runtime.importAsset(item.source);
|
|
336
|
+
// B1 守卫:import 期间可能被 cancel
|
|
337
|
+
if (job.cancelled) {
|
|
338
|
+
item.status = 'cancelled';
|
|
339
|
+
// 清理刚导入的 input,避免孤儿资产(cancel 期间产出的 input 不应残留)
|
|
340
|
+
void this.runtime.removeAsset(inputAssetId).catch(() => { });
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
// 2. 执行工作流(用独立 workflow id,便于 cancel)
|
|
344
|
+
const wf = { ...item.workflow, id: wfId };
|
|
345
|
+
result = await this.runtime.run(wf, [inputAssetId]);
|
|
346
|
+
// B1 守卫:run 期间可能被 cancel(cancel 已同步标 item=cancelled)
|
|
347
|
+
if (job.cancelled) {
|
|
348
|
+
item.status = 'cancelled';
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (result.status !== 'completed' || result.outputs.length === 0) {
|
|
352
|
+
throw new Error(result.error || `Workflow ${result.status}`);
|
|
353
|
+
}
|
|
354
|
+
const outputAssetId = result.outputs[0];
|
|
355
|
+
const duration = Date.now() - start;
|
|
356
|
+
// 成功
|
|
357
|
+
item.status = 'completed';
|
|
358
|
+
item.outputAssetId = outputAssetId;
|
|
359
|
+
item.duration = duration;
|
|
360
|
+
job.completed++;
|
|
361
|
+
this.emit({
|
|
362
|
+
type: 'batch:item:finished',
|
|
363
|
+
jobId: job.id,
|
|
364
|
+
itemId: item.id,
|
|
365
|
+
index: item.index,
|
|
366
|
+
total: job.items.length,
|
|
367
|
+
outputAssetId,
|
|
368
|
+
duration,
|
|
369
|
+
});
|
|
370
|
+
this.emitProgress(job);
|
|
371
|
+
}
|
|
372
|
+
catch (err) {
|
|
373
|
+
// B1 守卫:被 cancel 的 workflow 抛错不计入 failed(item 已是 cancelled)
|
|
374
|
+
if (job.cancelled) {
|
|
375
|
+
item.status = 'cancelled';
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
// 失败:判断是否还可重试
|
|
379
|
+
item.attempts++;
|
|
380
|
+
const maxRetries = item.maxRetries;
|
|
381
|
+
if (item.attempts <= maxRetries) {
|
|
382
|
+
// 重试:回 pending,schedule 会再次拉起
|
|
383
|
+
item.status = 'pending';
|
|
384
|
+
// 注意:不重置 attempts,保留累计重试次数
|
|
385
|
+
// 清理本次导入的 input asset,避免重试重新 import 时旧 input 成为孤儿
|
|
386
|
+
// (Blocker 修复:maxRetries=3 全失败原本会累积 3 个孤儿 input)
|
|
387
|
+
if (inputAssetId !== undefined) {
|
|
388
|
+
void this.runtime.removeAsset(inputAssetId).catch(() => { });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
item.status = 'failed';
|
|
393
|
+
item.error = err instanceof Error ? err : new Error(String(err));
|
|
394
|
+
job.failed++;
|
|
395
|
+
// M4:最终失败时清理已导入的 input asset,避免批量失败累积孤儿资产
|
|
396
|
+
// 占用 OPFS/IDB 空间并污染 listAssets / StatusBar 配额
|
|
397
|
+
if (inputAssetId !== undefined) {
|
|
398
|
+
void this.runtime.removeAsset(inputAssetId).catch(() => { });
|
|
399
|
+
}
|
|
400
|
+
this.emit({
|
|
401
|
+
type: 'batch:item:failed',
|
|
402
|
+
jobId: job.id,
|
|
403
|
+
itemId: item.id,
|
|
404
|
+
index: item.index,
|
|
405
|
+
total: job.items.length,
|
|
406
|
+
error: item.error,
|
|
407
|
+
attempts: item.attempts,
|
|
408
|
+
});
|
|
409
|
+
this.emitProgress(job);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
finally {
|
|
413
|
+
// 触发 schedule 补满(用 ref 打破循环依赖)
|
|
414
|
+
if (!job.cancelled && !job.paused && !this.isTerminal(job.status)) {
|
|
415
|
+
void this.schedule(job);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/** 当前并发槽位(按内存压力动态收缩) */
|
|
420
|
+
currentConcurrency(job) {
|
|
421
|
+
const pressure = this.memoryGuard?.getPressure() ?? 'low';
|
|
422
|
+
return shrinkConcurrencyByPressure(job.baseConcurrency, pressure);
|
|
423
|
+
}
|
|
424
|
+
/** 发出 progress 事件 + 通知订阅者 */
|
|
425
|
+
emitProgress(job) {
|
|
426
|
+
const payload = {
|
|
427
|
+
jobId: job.id,
|
|
428
|
+
completed: job.completed,
|
|
429
|
+
failed: job.failed,
|
|
430
|
+
total: job.items.length,
|
|
431
|
+
};
|
|
432
|
+
this.emit({
|
|
433
|
+
type: 'batch:progress',
|
|
434
|
+
...payload,
|
|
435
|
+
});
|
|
436
|
+
const subs = this.progressSubs.get(job.id);
|
|
437
|
+
if (subs) {
|
|
438
|
+
// M1 修复:遍历副本,防止 handler 内 unsubscribe/subscribe mutate 正在迭代的 Set
|
|
439
|
+
for (const fn of [...subs]) {
|
|
440
|
+
try {
|
|
441
|
+
fn(payload);
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
// 单个订阅者异常不阻断其他订阅者
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/** 检查 job 是否全部完成,如是则发 batch:completed */
|
|
450
|
+
maybeComplete(job) {
|
|
451
|
+
if (this.isTerminal(job.status))
|
|
452
|
+
return;
|
|
453
|
+
const pending = job.items.some((i) => i.status === 'pending' || i.status === 'processing');
|
|
454
|
+
if (pending)
|
|
455
|
+
return;
|
|
456
|
+
job.endedAt = Date.now();
|
|
457
|
+
job.status = job.failed > 0 ? 'failed' : 'completed';
|
|
458
|
+
// 注意:无论 job.status 是 'completed' 还是 'failed',都发 batch:completed 事件。
|
|
459
|
+
// 事件名表示"所有项已终结(含部分失败)",而非"全部成功"。
|
|
460
|
+
// 消费方应通过 payload.failed 区分;waitForCompletion 依赖此行为(只监听
|
|
461
|
+
// batch:completed + batch:cancelled 两种终态事件)。
|
|
462
|
+
this.emit({
|
|
463
|
+
type: 'batch:completed',
|
|
464
|
+
jobId: job.id,
|
|
465
|
+
total: job.items.length,
|
|
466
|
+
completed: job.completed,
|
|
467
|
+
failed: job.failed,
|
|
468
|
+
duration: job.endedAt - job.startedAt,
|
|
469
|
+
});
|
|
470
|
+
// M5:进入终态清理进度订阅,避免 waitForCompletion 调用方丢弃 promise 后泄漏
|
|
471
|
+
this.cleanupJobSubs(job.id);
|
|
472
|
+
}
|
|
473
|
+
isTerminal(status) {
|
|
474
|
+
return (status === 'completed' ||
|
|
475
|
+
status === 'cancelled' ||
|
|
476
|
+
status === 'failed');
|
|
477
|
+
}
|
|
478
|
+
/** 派生每项的 workflow id(独立于其他项,便于 cancel) */
|
|
479
|
+
itemWorkflowId(jobId, itemId) {
|
|
480
|
+
return `${jobId}__${itemId}`;
|
|
481
|
+
}
|
|
482
|
+
/** 安全 emit(无 EventBus 时降级为 no-op,便于纯单元测试) */
|
|
483
|
+
emit(event) {
|
|
484
|
+
try {
|
|
485
|
+
this.eventBus.emit(event);
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
// EventBus 异常不阻断批量逻辑
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
/** 内部可变状态 → 对外只读视图(浅拷贝 items 数组与每项) */
|
|
492
|
+
toJobView(job) {
|
|
493
|
+
return {
|
|
494
|
+
id: job.id,
|
|
495
|
+
status: job.status,
|
|
496
|
+
items: job.items.map((i) => ({ ...i })),
|
|
497
|
+
completed: job.completed,
|
|
498
|
+
failed: job.failed,
|
|
499
|
+
total: job.items.length,
|
|
500
|
+
startedAt: job.startedAt,
|
|
501
|
+
endedAt: job.endedAt,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
//# sourceMappingURL=batch-processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-processor.js","sourceRoot":"","sources":["../src/batch-processor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 能力降级阶梯(W3.4)
|
|
3
|
+
*
|
|
4
|
+
* 当 MemoryGuard 报告内存压力时,运行时需要按阶梯选择处理策略,
|
|
5
|
+
* 而非"要么全量要么崩溃"。本模块实现四级降级政策(whitepaper T1):
|
|
6
|
+
*
|
|
7
|
+
* L1-full 完整质量——压力低/中,正常处理。
|
|
8
|
+
* L2-tiled 分片处理——压力高,把输入按 tile 切分 + 中间结果溢出 OPFS,
|
|
9
|
+
* 内存里同时只持有一个 tile。
|
|
10
|
+
* L3-degraded 降级输出——压力 critical,先缩小到 maxEdge 再处理,
|
|
11
|
+
* 降低质量,避免 OOM;产出可用但非最佳。
|
|
12
|
+
* L4-reject 拒绝 + 引导——即使降级也无法安全处理(如单张图解码后即超
|
|
13
|
+
* critical 且无法再缩),抛 DegradationRejectedError 携带
|
|
14
|
+
* 用户可读的引导(换更小源图 / 关闭其他 tab / 升级浏览器)。
|
|
15
|
+
*
|
|
16
|
+
* 设计取舍:
|
|
17
|
+
* - 本模块只做"决策"(policy),不直接执行图像操作。执行由 plugin-image
|
|
18
|
+
* / executor 据 decision 调整参数后委托 engine-image。
|
|
19
|
+
* - 决策是纯函数(pickDegradation),便于单测与确定性回归。
|
|
20
|
+
* - L2 是否可走取决于操作是否支持 tiling(canTile);canvas 引擎对几何
|
|
21
|
+
* 变换可 tile,但对依赖全图的滤镜(模糊)与水印 tile 无意义 → 调用方
|
|
22
|
+
* 据操作语义传入 canTile。
|
|
23
|
+
*/
|
|
24
|
+
import type { MemoryPressure } from './memory-guard.js';
|
|
25
|
+
/** 降级阶梯等级 */
|
|
26
|
+
export type DegradationLevel = 'L1-full' | 'L2-tiled' | 'L3-degraded' | 'L4-reject';
|
|
27
|
+
/** L3 降级输出时建议的最大边长(像素)——4K,兼顾可用性与内存 */
|
|
28
|
+
export declare const DEGRADED_MAX_EDGE = 4096;
|
|
29
|
+
/** L3 降级输出时建议的质量(0-100)——70 是视觉可接受的下限 */
|
|
30
|
+
export declare const DEGRADED_QUALITY = 70;
|
|
31
|
+
/** 决策上下文(由调用方据 MemoryGuard + 操作能力构造) */
|
|
32
|
+
export interface DegradationContext {
|
|
33
|
+
/** 当前内存压力等级(来自 MemoryGuard.getPressure) */
|
|
34
|
+
pressure: MemoryPressure;
|
|
35
|
+
/** 是否可溢出中间结果到 OPFS(MemoryGuard.canSpill) */
|
|
36
|
+
canSpill: boolean;
|
|
37
|
+
/** 操作是否支持 tiling / streaming(几何变换通常 true,模糊/水印 false) */
|
|
38
|
+
canTile: boolean;
|
|
39
|
+
/** 输入字节数(用于 L4 判定:输入本身过大时即使降级也无救) */
|
|
40
|
+
inputBytes: number;
|
|
41
|
+
/** 内存预算(字节,用于 L4 引导信息) */
|
|
42
|
+
budget: number;
|
|
43
|
+
/**
|
|
44
|
+
* 解码后位图估算字节(用于 L4 判定:decode 后超 critical 且
|
|
45
|
+
* 已是 maxEdge 缩放后仍超 → reject)。可选,缺省时仅按 pressure 判。
|
|
46
|
+
*
|
|
47
|
+
* **强烈建议提供**:可通过 estimateDecodedBytes(width, height) 计算。
|
|
48
|
+
* 缺省时 pressure=critical + canSpill=true 场景会跳过 L4 判定,
|
|
49
|
+
* 直接返回 L3-degraded,可能导致超大图缩放后仍 OOM。
|
|
50
|
+
*/
|
|
51
|
+
decodedBytes?: number;
|
|
52
|
+
}
|
|
53
|
+
/** 降级决策结果 */
|
|
54
|
+
export interface DegradationDecision {
|
|
55
|
+
level: DegradationLevel;
|
|
56
|
+
/** L3 时建议缩放到的最大边长(像素);其余等级 undefined */
|
|
57
|
+
maxEdge?: number;
|
|
58
|
+
/** L3 时建议质量(0-100);其余等级 undefined */
|
|
59
|
+
quality?: number;
|
|
60
|
+
/** L2/L3 时是否把中间结果溢出到 OPFS */
|
|
61
|
+
spill: boolean;
|
|
62
|
+
/** 决策依据(便于日志 / 事件 / 调试) */
|
|
63
|
+
reason: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 根据内存压力与操作能力选择降级策略(纯函数)。
|
|
67
|
+
*
|
|
68
|
+
* 决策矩阵:
|
|
69
|
+
* pressure canTile canSpill → level
|
|
70
|
+
* low/elevated * * → L1-full
|
|
71
|
+
* high true true → L2-tiled(spill)
|
|
72
|
+
* high true false → L2-tiled(无 spill,仅靠 tile 控峰值)
|
|
73
|
+
* high false * → L3-degraded(降到 maxEdge+quality)
|
|
74
|
+
* critical * * → L3-degraded(若 decodedBytes 缩放后仍超
|
|
75
|
+
* critical → L4-reject)
|
|
76
|
+
*
|
|
77
|
+
* L4 触发条件:pressure=critical 且 decodedBytes 已按 maxEdge 缩放后
|
|
78
|
+
* 估算仍 >= budget 的 critical 阈值(0.95),即"再怎么缩也会撑爆"。
|
|
79
|
+
*
|
|
80
|
+
* **调用方注意**:当 pressure=critical 且 canSpill=true 时,若不提供
|
|
81
|
+
* decodedBytes,函数将跳过 L4 判定直接返回 L3-degraded。这意味着对于
|
|
82
|
+
* 超大单张图(无法通过 spill 分块),L3 缩放后仍可能 OOM。
|
|
83
|
+
* 建议:尽可能通过 estimateDecodedBytes(width, height) 提供 decodedBytes,
|
|
84
|
+
* 以提高 L4 判定精度,避免静默降级后崩溃。
|
|
85
|
+
*/
|
|
86
|
+
export declare function pickDegradation(ctx: DegradationContext): DegradationDecision;
|
|
87
|
+
/**
|
|
88
|
+
* L4 拒绝时抛出的错误,携带用户可读的引导建议。
|
|
89
|
+
*
|
|
90
|
+
* 引导文案面向终端用户(经 UI 展示),而非开发者堆栈。
|
|
91
|
+
*/
|
|
92
|
+
export declare class DegradationRejectedError extends Error {
|
|
93
|
+
readonly level: "L4-reject";
|
|
94
|
+
readonly inputBytes: number;
|
|
95
|
+
readonly budget: number;
|
|
96
|
+
/** 用户可读的引导建议(UI 可直接渲染) */
|
|
97
|
+
readonly guide: string[];
|
|
98
|
+
constructor(inputBytes: number, budget: number, reason: string);
|
|
99
|
+
}
|
|
100
|
+
/** 字节数 → 人类可读(MB/GB) */
|
|
101
|
+
export declare function formatBytes(bytes: number): string;
|
|
102
|
+
/**
|
|
103
|
+
* 把降级决策应用到 resize 参数(若 L3):确保输出最大边不超过 maxEdge。
|
|
104
|
+
*
|
|
105
|
+
* 调用方在 L3 时据此调整 params.width/height,再交给 engine.resize。
|
|
106
|
+
* L1/L2/L4 时原样返回 params。
|
|
107
|
+
*/
|
|
108
|
+
export declare function applyDegradationToResizeParams(params: Record<string, unknown>, decision: DegradationDecision): Record<string, unknown>;
|
|
109
|
+
//# sourceMappingURL=degradation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"degradation.d.ts","sourceRoot":"","sources":["../src/degradation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,aAAa;AACb,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,GAAG,WAAW,CAAC;AAEpF,uCAAuC;AACvC,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,yCAAyC;AACzC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,wCAAwC;AACxC,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,QAAQ,EAAE,cAAc,CAAC;IACzB,4CAA4C;IAC5C,QAAQ,EAAE,OAAO,CAAC;IAClB,yDAAyD;IACzD,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,aAAa;AACb,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,gBAAgB,CAAC;IACxB,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,kBAAkB,GAAG,mBAAmB,CAyC5E;AAmCD;;;;GAIG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,QAAQ,CAAC,KAAK,EAAG,WAAW,CAAU;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0BAA0B;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;gBAEb,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAU/D;AAgBD,wBAAwB;AACxB,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,QAAQ,EAAE,mBAAmB,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAiBzB"}
|