channel-worker 2.5.1 → 2.5.3
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/lib/command-poller.js +55 -52
- package/package.json +1 -1
package/lib/command-poller.js
CHANGED
|
@@ -1336,52 +1336,28 @@ class CommandPoller {
|
|
|
1336
1336
|
} catch (e) {
|
|
1337
1337
|
if (this.config.verbose) console.warn('[scene-dispatch] getMyWorker failed, using default:', e.message);
|
|
1338
1338
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
delete this._profileLastActivity[r.nst_profile_id];
|
|
1362
|
-
if (r.name) delete this._profileLastActivity[r.name.toLowerCase()];
|
|
1363
|
-
const idx = runningRenderers.indexOf(r);
|
|
1364
|
-
if (idx >= 0) runningRenderers.splice(idx, 1);
|
|
1365
|
-
} catch (e) {
|
|
1366
|
-
console.warn(`[scene-dispatch] Failed to close excess ${r.name}: ${e.message}`);
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
// Count how many running profiles are actually free (no commands)
|
|
1372
|
-
let freeRunning = 0;
|
|
1373
|
-
for (const r of runningRenderers) {
|
|
1374
|
-
try {
|
|
1375
|
-
const c = await this.api.rendererHasCommands(r.nst_profile_id);
|
|
1376
|
-
if (c === 0) freeRunning++;
|
|
1377
|
-
} catch {}
|
|
1378
|
-
}
|
|
1379
|
-
// Only launch new profiles if queue has more work than free profiles
|
|
1380
|
-
const needNew = Math.max(0, queueCount - freeRunning);
|
|
1381
|
-
const neededLaunches = Math.min(parallelLimit - runningRenderers.length, stillOffline.length, needNew);
|
|
1382
|
-
for (let li = 0; li < neededLaunches; li++) {
|
|
1383
|
-
const toLaunch = stillOffline[li];
|
|
1384
|
-
console.log(`[scene-dispatch] Launching ${toLaunch.name} (running: ${runningRenderers.length + li}/${parallelLimit})`);
|
|
1339
|
+
// Treat 'paused' renderers as NOT eligible to launch — operator clicked
|
|
1340
|
+
// Pause on the Renderers tab; profile should stay closed until Resume.
|
|
1341
|
+
const isPaused = (r) => r && r.health_state === 'paused'
|
|
1342
|
+
&& (!r.pause_until || new Date(r.pause_until).getTime() > Date.now());
|
|
1343
|
+
const stillOffline = renderers.filter(r => !runningRenderers.includes(r) && !isPaused(r));
|
|
1344
|
+
const pausedCount = renderers.filter(isPaused).length;
|
|
1345
|
+
console.log(`[scene-dispatch] running=${runningRenderers.length} cap=${parallelLimit} (flowkit=${flowkitQ} dom=${domQ}) offline=${stillOffline.length} paused=${pausedCount} queue=${queueCount} names=[${runningRenderers.map(r=>r.name)}]`);
|
|
1346
|
+
|
|
1347
|
+
// Launch policy: keep ALL non-paused owned renderers ONLINE whenever
|
|
1348
|
+
// there's any queue work — they take turns claiming via the API's
|
|
1349
|
+
// per-worker concurrency cap (Worker.parallel_limit). The cap limits
|
|
1350
|
+
// CONCURRENT CLAIMS, not the number of profiles kept open: e.g. with
|
|
1351
|
+
// parallel_limit=1 + 2 renderers veo01/veo02, both browsers stay open
|
|
1352
|
+
// and alternate scene claims. Operator pauses a specific renderer if
|
|
1353
|
+
// they want it closed (e.g. while solving captcha).
|
|
1354
|
+
//
|
|
1355
|
+
// Old "Close excess profiles" + parallelLimit-bounded launch logic was
|
|
1356
|
+
// removed — they conflated "concurrent claims cap" with "max launched"
|
|
1357
|
+
// and forced one of the renderers to sit unlaunched when cap < count.
|
|
1358
|
+
const eligibleToLaunch = queueCount > 0 ? stillOffline : [];
|
|
1359
|
+
for (const toLaunch of eligibleToLaunch) {
|
|
1360
|
+
console.log(`[scene-dispatch] Launching ${toLaunch.name} (queue=${queueCount} running=${runningRenderers.length})`);
|
|
1385
1361
|
try {
|
|
1386
1362
|
await this._launchRendererProfile(toLaunch);
|
|
1387
1363
|
runningRenderers.push(toLaunch);
|
|
@@ -1392,6 +1368,26 @@ class CommandPoller {
|
|
|
1392
1368
|
}
|
|
1393
1369
|
}
|
|
1394
1370
|
|
|
1371
|
+
// Close any currently-running renderers that just became paused —
|
|
1372
|
+
// operator clicked Pause while a profile was open. Skip if it has an
|
|
1373
|
+
// in-flight command (let it finish naturally); cleanup happens on the
|
|
1374
|
+
// next poll cycle.
|
|
1375
|
+
for (const r of [...runningRenderers]) {
|
|
1376
|
+
if (!isPaused(r)) continue;
|
|
1377
|
+
try {
|
|
1378
|
+
const c = await this.api.rendererHasCommands(r.nst_profile_id);
|
|
1379
|
+
if (c > 0) continue;
|
|
1380
|
+
console.log(`[scene-dispatch] Closing paused renderer ${r.name}`);
|
|
1381
|
+
await this.nst.stopProfile(r.nst_profile_id);
|
|
1382
|
+
delete this._profileLastActivity[r.nst_profile_id];
|
|
1383
|
+
if (r.name) delete this._profileLastActivity[r.name.toLowerCase()];
|
|
1384
|
+
const idx = runningRenderers.indexOf(r);
|
|
1385
|
+
if (idx >= 0) runningRenderers.splice(idx, 1);
|
|
1386
|
+
} catch (e) {
|
|
1387
|
+
console.warn(`[scene-dispatch] Failed to close paused ${r.name}: ${e.message}`);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1395
1391
|
if (runningRenderers.length === 0) {
|
|
1396
1392
|
this._dispatching = false;
|
|
1397
1393
|
return;
|
|
@@ -1438,17 +1434,24 @@ class CommandPoller {
|
|
|
1438
1434
|
// two daemons would happily double-launch and corrupt cookies / kill
|
|
1439
1435
|
// sessions for the underlying Google/FB accounts.
|
|
1440
1436
|
if (renderer.nst_profile_id && this.api.leaseRenderer) {
|
|
1437
|
+
// api.leaseRenderer returns json.data on success (a {nst_profile_id,
|
|
1438
|
+
// leased_at, lessee} envelope) and THROWS on success=false. So:
|
|
1439
|
+
// - throw → another daemon holds the lease OR API rejected → skip
|
|
1440
|
+
// - return → lease acquired → proceed with NST launch
|
|
1441
|
+
// Previous bug: checked `leaseRes.success !== true` but `.success` is
|
|
1442
|
+
// already stripped from json.data → always falsy → every launch was
|
|
1443
|
+
// wrongfully denied with reason="unknown".
|
|
1441
1444
|
try {
|
|
1442
1445
|
const leaseRes = await this.api.leaseRenderer(renderer.nst_profile_id);
|
|
1443
|
-
if (!leaseRes || leaseRes.
|
|
1444
|
-
|
|
1445
|
-
console.warn(`[scene-dispatch] LEASE DENIED for ${renderer.name || renderer.nst_profile_id}: ${reason} — skipping launch`);
|
|
1446
|
+
if (!leaseRes || !leaseRes.nst_profile_id) {
|
|
1447
|
+
console.warn(`[scene-dispatch] lease returned no data for ${renderer.name || renderer.nst_profile_id} — skipping launch (API may be older)`);
|
|
1446
1448
|
return;
|
|
1447
1449
|
}
|
|
1448
1450
|
} catch (e) {
|
|
1449
|
-
//
|
|
1450
|
-
//
|
|
1451
|
-
|
|
1451
|
+
// Either API said success=false (lease held by peer / unowned /
|
|
1452
|
+
// stale rules failed) OR network failure. Either way, do NOT
|
|
1453
|
+
// launch — better idle than collision.
|
|
1454
|
+
console.warn(`[scene-dispatch] LEASE DENIED for ${renderer.name || renderer.nst_profile_id}: ${e.message} — skipping launch`);
|
|
1452
1455
|
return;
|
|
1453
1456
|
}
|
|
1454
1457
|
}
|