@jeffusion/bungee-core 3.2.1 → 3.3.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/dist/master.js +291 -3
- package/dist/worker.js +28568 -8914
- package/package.json +2 -2
- package/dist/master.d.ts +0 -1
- package/dist/worker.d.ts +0 -21
package/dist/master.js
CHANGED
|
@@ -16534,6 +16534,228 @@ var init_plugin_permissions = __esm(() => {
|
|
|
16534
16534
|
};
|
|
16535
16535
|
});
|
|
16536
16536
|
|
|
16537
|
+
// src/plugin-runtime-multi-worker-convergence.ts
|
|
16538
|
+
class PluginRuntimeMultiWorkerConvergenceTracker {
|
|
16539
|
+
convergenceDeadlineMs;
|
|
16540
|
+
workers = new Map;
|
|
16541
|
+
targetGeneration = null;
|
|
16542
|
+
startedAt = null;
|
|
16543
|
+
deadlineAt = null;
|
|
16544
|
+
constructor(convergenceDeadlineMs = 5000) {
|
|
16545
|
+
this.convergenceDeadlineMs = convergenceDeadlineMs;
|
|
16546
|
+
}
|
|
16547
|
+
upsertWorker(workerId, pid = null) {
|
|
16548
|
+
const existing = this.workers.get(workerId);
|
|
16549
|
+
if (existing) {
|
|
16550
|
+
existing.pid = pid;
|
|
16551
|
+
if (existing.baseStatus === "stopped") {
|
|
16552
|
+
existing.baseStatus = "starting";
|
|
16553
|
+
}
|
|
16554
|
+
existing.updatedAt = Date.now();
|
|
16555
|
+
return;
|
|
16556
|
+
}
|
|
16557
|
+
this.workers.set(workerId, {
|
|
16558
|
+
workerId,
|
|
16559
|
+
pid,
|
|
16560
|
+
baseStatus: "starting",
|
|
16561
|
+
servingGeneration: null,
|
|
16562
|
+
lastRequestedGeneration: null,
|
|
16563
|
+
lastAppliedGeneration: null,
|
|
16564
|
+
failureGeneration: null,
|
|
16565
|
+
error: null,
|
|
16566
|
+
updatedAt: Date.now()
|
|
16567
|
+
});
|
|
16568
|
+
}
|
|
16569
|
+
recordWorkerReady(workerId, generation, pid = null) {
|
|
16570
|
+
const worker = this.getOrCreateWorker(workerId, pid);
|
|
16571
|
+
worker.pid = pid ?? worker.pid;
|
|
16572
|
+
worker.baseStatus = "ready";
|
|
16573
|
+
worker.servingGeneration = generation;
|
|
16574
|
+
worker.lastAppliedGeneration = generation;
|
|
16575
|
+
worker.error = null;
|
|
16576
|
+
worker.updatedAt = Date.now();
|
|
16577
|
+
}
|
|
16578
|
+
beginGeneration(targetGeneration, workerIds) {
|
|
16579
|
+
this.targetGeneration = targetGeneration;
|
|
16580
|
+
this.startedAt = Date.now();
|
|
16581
|
+
this.deadlineAt = this.startedAt + Math.max(0, this.convergenceDeadlineMs);
|
|
16582
|
+
for (const workerId of workerIds ?? Array.from(this.workers.keys())) {
|
|
16583
|
+
const worker = this.getOrCreateWorker(workerId);
|
|
16584
|
+
worker.lastRequestedGeneration = targetGeneration;
|
|
16585
|
+
worker.failureGeneration = worker.failureGeneration === targetGeneration ? null : worker.failureGeneration;
|
|
16586
|
+
worker.error = worker.failureGeneration === targetGeneration ? null : worker.error;
|
|
16587
|
+
if (worker.baseStatus !== "stopped") {
|
|
16588
|
+
worker.baseStatus = "reconciling";
|
|
16589
|
+
}
|
|
16590
|
+
worker.updatedAt = Date.now();
|
|
16591
|
+
}
|
|
16592
|
+
}
|
|
16593
|
+
recordWorkerSuccess(workerId, generation, servingGeneration = generation, pid = null) {
|
|
16594
|
+
const worker = this.getOrCreateWorker(workerId, pid);
|
|
16595
|
+
worker.pid = pid ?? worker.pid;
|
|
16596
|
+
worker.baseStatus = "ready";
|
|
16597
|
+
worker.lastAppliedGeneration = generation;
|
|
16598
|
+
worker.servingGeneration = servingGeneration;
|
|
16599
|
+
worker.failureGeneration = null;
|
|
16600
|
+
worker.error = null;
|
|
16601
|
+
worker.updatedAt = Date.now();
|
|
16602
|
+
}
|
|
16603
|
+
recordWorkerFailure(workerId, generation, error, servingGeneration, pid = null) {
|
|
16604
|
+
const worker = this.getOrCreateWorker(workerId, pid);
|
|
16605
|
+
worker.pid = pid ?? worker.pid;
|
|
16606
|
+
worker.baseStatus = "failed";
|
|
16607
|
+
worker.lastAppliedGeneration = generation;
|
|
16608
|
+
worker.failureGeneration = generation;
|
|
16609
|
+
worker.servingGeneration = servingGeneration;
|
|
16610
|
+
worker.error = error;
|
|
16611
|
+
worker.updatedAt = Date.now();
|
|
16612
|
+
}
|
|
16613
|
+
recordWorkerStopped(workerId) {
|
|
16614
|
+
const worker = this.getOrCreateWorker(workerId);
|
|
16615
|
+
worker.baseStatus = "stopped";
|
|
16616
|
+
worker.updatedAt = Date.now();
|
|
16617
|
+
}
|
|
16618
|
+
getStatusReport(now = Date.now()) {
|
|
16619
|
+
const targetGeneration = this.targetGeneration;
|
|
16620
|
+
const deadlineExceeded = this.deadlineAt !== null && now > this.deadlineAt;
|
|
16621
|
+
const workers = Array.from(this.workers.values()).sort((left, right) => left.workerId - right.workerId).map((worker) => {
|
|
16622
|
+
const status2 = deriveWorkerStatus(worker, targetGeneration);
|
|
16623
|
+
return {
|
|
16624
|
+
workerId: worker.workerId,
|
|
16625
|
+
pid: worker.pid,
|
|
16626
|
+
status: status2,
|
|
16627
|
+
servingGeneration: worker.servingGeneration,
|
|
16628
|
+
lastRequestedGeneration: worker.lastRequestedGeneration,
|
|
16629
|
+
lastAppliedGeneration: worker.lastAppliedGeneration,
|
|
16630
|
+
failureGeneration: worker.failureGeneration,
|
|
16631
|
+
error: worker.error,
|
|
16632
|
+
updatedAt: worker.updatedAt ? new Date(worker.updatedAt).toISOString() : null
|
|
16633
|
+
};
|
|
16634
|
+
});
|
|
16635
|
+
const failedWorkers = workers.filter((worker) => worker.status === "failed").map((worker) => worker.workerId);
|
|
16636
|
+
const staleWorkers = workers.filter((worker) => worker.status === "stale" || worker.status === "failed" && targetGeneration !== null && worker.servingGeneration !== null && worker.servingGeneration < targetGeneration).map((worker) => worker.workerId);
|
|
16637
|
+
const pendingWorkers = workers.filter((worker) => worker.status === "reconciling" || worker.status === "starting" || worker.status === "ready").map((worker) => worker.workerId);
|
|
16638
|
+
const convergedWorkers = workers.filter((worker) => worker.status === "converged").map((worker) => worker.workerId);
|
|
16639
|
+
let status = "idle";
|
|
16640
|
+
let convergedGeneration = null;
|
|
16641
|
+
if (targetGeneration !== null) {
|
|
16642
|
+
if (failedWorkers.length > 0 || deadlineExceeded && (staleWorkers.length > 0 || pendingWorkers.length > 0)) {
|
|
16643
|
+
status = "failed";
|
|
16644
|
+
} else if (workers.length > 0 && convergedWorkers.length === workers.length) {
|
|
16645
|
+
status = "converged";
|
|
16646
|
+
convergedGeneration = targetGeneration;
|
|
16647
|
+
} else {
|
|
16648
|
+
status = "pending";
|
|
16649
|
+
}
|
|
16650
|
+
}
|
|
16651
|
+
return {
|
|
16652
|
+
targetGeneration,
|
|
16653
|
+
convergedGeneration,
|
|
16654
|
+
startedAt: this.startedAt ? new Date(this.startedAt).toISOString() : null,
|
|
16655
|
+
deadlineAt: this.deadlineAt ? new Date(this.deadlineAt).toISOString() : null,
|
|
16656
|
+
status,
|
|
16657
|
+
failedWorkers,
|
|
16658
|
+
staleWorkers,
|
|
16659
|
+
pendingWorkers,
|
|
16660
|
+
convergedWorkers,
|
|
16661
|
+
workers
|
|
16662
|
+
};
|
|
16663
|
+
}
|
|
16664
|
+
getOrCreateWorker(workerId, pid = null) {
|
|
16665
|
+
const existing = this.workers.get(workerId);
|
|
16666
|
+
if (existing) {
|
|
16667
|
+
if (pid !== null) {
|
|
16668
|
+
existing.pid = pid;
|
|
16669
|
+
}
|
|
16670
|
+
return existing;
|
|
16671
|
+
}
|
|
16672
|
+
const worker = {
|
|
16673
|
+
workerId,
|
|
16674
|
+
pid,
|
|
16675
|
+
baseStatus: "starting",
|
|
16676
|
+
servingGeneration: null,
|
|
16677
|
+
lastRequestedGeneration: null,
|
|
16678
|
+
lastAppliedGeneration: null,
|
|
16679
|
+
failureGeneration: null,
|
|
16680
|
+
error: null,
|
|
16681
|
+
updatedAt: null
|
|
16682
|
+
};
|
|
16683
|
+
this.workers.set(workerId, worker);
|
|
16684
|
+
return worker;
|
|
16685
|
+
}
|
|
16686
|
+
}
|
|
16687
|
+
|
|
16688
|
+
class PluginRuntimeMultiWorkerCoordinator {
|
|
16689
|
+
tracker;
|
|
16690
|
+
pollIntervalMs;
|
|
16691
|
+
requestedGeneration = 0;
|
|
16692
|
+
constructor(tracker = new PluginRuntimeMultiWorkerConvergenceTracker, pollIntervalMs = 50) {
|
|
16693
|
+
this.tracker = tracker;
|
|
16694
|
+
this.pollIntervalMs = pollIntervalMs;
|
|
16695
|
+
}
|
|
16696
|
+
noteWorkerReady(workerId, generation, pid = null) {
|
|
16697
|
+
this.requestedGeneration = Math.max(this.requestedGeneration, generation ?? 0);
|
|
16698
|
+
this.tracker.recordWorkerReady(workerId, generation, pid);
|
|
16699
|
+
}
|
|
16700
|
+
noteWorkerStarting(workerId, pid = null) {
|
|
16701
|
+
this.tracker.upsertWorker(workerId, pid);
|
|
16702
|
+
}
|
|
16703
|
+
noteWorkerStopped(workerId) {
|
|
16704
|
+
this.tracker.recordWorkerStopped(workerId);
|
|
16705
|
+
}
|
|
16706
|
+
noteWorkerReconcileSuccess(workerId, generation, servingGeneration = generation, pid = null) {
|
|
16707
|
+
this.requestedGeneration = Math.max(this.requestedGeneration, generation);
|
|
16708
|
+
this.tracker.recordWorkerSuccess(workerId, generation, servingGeneration, pid);
|
|
16709
|
+
}
|
|
16710
|
+
noteWorkerReconcileFailure(workerId, generation, error, servingGeneration, pid = null) {
|
|
16711
|
+
this.requestedGeneration = Math.max(this.requestedGeneration, generation);
|
|
16712
|
+
this.tracker.recordWorkerFailure(workerId, generation, error, servingGeneration, pid);
|
|
16713
|
+
}
|
|
16714
|
+
getStatusReport(now = Date.now()) {
|
|
16715
|
+
return this.tracker.getStatusReport(now);
|
|
16716
|
+
}
|
|
16717
|
+
async dispatchReconcile(workers) {
|
|
16718
|
+
const activeWorkers = workers.filter((worker) => worker.status === "ready");
|
|
16719
|
+
const targetGeneration = this.requestedGeneration + 1;
|
|
16720
|
+
this.requestedGeneration = targetGeneration;
|
|
16721
|
+
this.tracker.beginGeneration(targetGeneration, activeWorkers.map((worker) => worker.workerId));
|
|
16722
|
+
const command = {
|
|
16723
|
+
command: "reconcile-plugin-runtime",
|
|
16724
|
+
generation: targetGeneration
|
|
16725
|
+
};
|
|
16726
|
+
for (const worker of activeWorkers) {
|
|
16727
|
+
worker.send?.(command);
|
|
16728
|
+
}
|
|
16729
|
+
if (activeWorkers.length === 0) {
|
|
16730
|
+
return this.tracker.getStatusReport();
|
|
16731
|
+
}
|
|
16732
|
+
return await new Promise((resolve) => {
|
|
16733
|
+
const poller = setInterval(() => {
|
|
16734
|
+
const report = this.tracker.getStatusReport();
|
|
16735
|
+
if (report.status === "converged" || report.status === "failed") {
|
|
16736
|
+
clearInterval(poller);
|
|
16737
|
+
resolve(report);
|
|
16738
|
+
}
|
|
16739
|
+
}, this.pollIntervalMs);
|
|
16740
|
+
});
|
|
16741
|
+
}
|
|
16742
|
+
}
|
|
16743
|
+
function deriveWorkerStatus(worker, targetGeneration) {
|
|
16744
|
+
if (worker.baseStatus === "stopped" || worker.baseStatus === "failed") {
|
|
16745
|
+
return worker.baseStatus;
|
|
16746
|
+
}
|
|
16747
|
+
if (targetGeneration === null) {
|
|
16748
|
+
return worker.baseStatus;
|
|
16749
|
+
}
|
|
16750
|
+
if (worker.servingGeneration === targetGeneration) {
|
|
16751
|
+
return "converged";
|
|
16752
|
+
}
|
|
16753
|
+
if (worker.servingGeneration !== null && worker.servingGeneration < targetGeneration) {
|
|
16754
|
+
return "stale";
|
|
16755
|
+
}
|
|
16756
|
+
return worker.baseStatus;
|
|
16757
|
+
}
|
|
16758
|
+
|
|
16537
16759
|
// src/master.ts
|
|
16538
16760
|
var exports_master = {};
|
|
16539
16761
|
import path7 from "path";
|
|
@@ -16547,6 +16769,7 @@ class Master {
|
|
|
16547
16769
|
isReloading = false;
|
|
16548
16770
|
reloadTimeout = null;
|
|
16549
16771
|
pluginStorageCleanupService = null;
|
|
16772
|
+
pluginRuntimeConvergenceCoordinator = new PluginRuntimeMultiWorkerCoordinator;
|
|
16550
16773
|
constructor() {
|
|
16551
16774
|
this.loadAndStart();
|
|
16552
16775
|
this.watchConfig();
|
|
@@ -16601,6 +16824,14 @@ class Master {
|
|
|
16601
16824
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
16602
16825
|
}
|
|
16603
16826
|
}
|
|
16827
|
+
const startupConvergence = await this.reconcilePluginRuntimeAcrossWorkers();
|
|
16828
|
+
logger.info({
|
|
16829
|
+
targetGeneration: startupConvergence.targetGeneration,
|
|
16830
|
+
convergedGeneration: startupConvergence.convergedGeneration,
|
|
16831
|
+
status: startupConvergence.status,
|
|
16832
|
+
failedWorkers: startupConvergence.failedWorkers,
|
|
16833
|
+
staleWorkers: startupConvergence.staleWorkers
|
|
16834
|
+
}, "Initial multi-worker plugin runtime convergence completed");
|
|
16604
16835
|
logger.info("All workers have been started.");
|
|
16605
16836
|
}
|
|
16606
16837
|
forkWorker(workerId) {
|
|
@@ -16626,10 +16857,12 @@ class Master {
|
|
|
16626
16857
|
process: workerProcess,
|
|
16627
16858
|
workerId,
|
|
16628
16859
|
status: "starting",
|
|
16860
|
+
pluginRuntimeGeneration: null,
|
|
16629
16861
|
exitPromise,
|
|
16630
16862
|
exitResolve
|
|
16631
16863
|
};
|
|
16632
16864
|
this.workers.set(workerId, workerInfo);
|
|
16865
|
+
this.pluginRuntimeConvergenceCoordinator.noteWorkerStarting(workerId, workerProcess.pid ?? null);
|
|
16633
16866
|
const startupTimeout = setTimeout(() => {
|
|
16634
16867
|
logger.error(`Worker #${workerId} (PID: ${workerProcess.pid}) failed to start within timeout`);
|
|
16635
16868
|
workerProcess.kill();
|
|
@@ -16646,6 +16879,7 @@ class Master {
|
|
|
16646
16879
|
}
|
|
16647
16880
|
this.workers.delete(workerId);
|
|
16648
16881
|
workerInfo.status = "stopped";
|
|
16882
|
+
this.pluginRuntimeConvergenceCoordinator.noteWorkerStopped(workerId);
|
|
16649
16883
|
if (exitResolve) {
|
|
16650
16884
|
exitResolve();
|
|
16651
16885
|
}
|
|
@@ -16655,12 +16889,30 @@ class Master {
|
|
|
16655
16889
|
});
|
|
16656
16890
|
workerProcess.on("message", (message) => {
|
|
16657
16891
|
clearTimeout(startupTimeout);
|
|
16658
|
-
if (message.
|
|
16892
|
+
if ("command" in message && message.command === "cluster-reconcile-plugin-runtime") {
|
|
16893
|
+
this.handleClusterPluginRuntimeReconcileRequest(workerInfo, message);
|
|
16894
|
+
return;
|
|
16895
|
+
}
|
|
16896
|
+
if (!("status" in message)) {
|
|
16897
|
+
return;
|
|
16898
|
+
}
|
|
16899
|
+
const workerMessage = message;
|
|
16900
|
+
if (workerMessage.status === "ready") {
|
|
16659
16901
|
logger.info(`Worker #${workerId} (PID: ${workerProcess.pid}) reported ready.`);
|
|
16660
16902
|
workerInfo.status = "ready";
|
|
16903
|
+
workerInfo.pluginRuntimeGeneration = workerMessage.pluginRuntime.generation;
|
|
16904
|
+
this.pluginRuntimeConvergenceCoordinator.noteWorkerReady(workerId, workerMessage.pluginRuntime.generation, workerMessage.pid);
|
|
16661
16905
|
resolve(true);
|
|
16662
|
-
} else if (
|
|
16663
|
-
|
|
16906
|
+
} else if (workerMessage.status === "plugin-runtime-reconcile-complete") {
|
|
16907
|
+
workerInfo.pluginRuntimeGeneration = workerMessage.servingGeneration;
|
|
16908
|
+
this.pluginRuntimeConvergenceCoordinator.noteWorkerReconcileSuccess(workerId, workerMessage.generation, workerMessage.servingGeneration, workerMessage.pid);
|
|
16909
|
+
this.logPluginRuntimeConvergenceStatus();
|
|
16910
|
+
} else if (workerMessage.status === "plugin-runtime-reconcile-failed") {
|
|
16911
|
+
workerInfo.pluginRuntimeGeneration = workerMessage.servingGeneration;
|
|
16912
|
+
this.pluginRuntimeConvergenceCoordinator.noteWorkerReconcileFailure(workerId, workerMessage.generation, workerMessage.error, workerMessage.servingGeneration, workerMessage.pid);
|
|
16913
|
+
this.logPluginRuntimeConvergenceStatus();
|
|
16914
|
+
} else if (workerMessage.status === "error") {
|
|
16915
|
+
const errorMsg = workerMessage.error || "Unknown error";
|
|
16664
16916
|
logger.error(`Worker #${workerId} (PID: ${workerProcess.pid}) reported an error: ${errorMsg}`);
|
|
16665
16917
|
workerProcess.kill();
|
|
16666
16918
|
this.workers.delete(workerId);
|
|
@@ -16680,6 +16932,42 @@ class Master {
|
|
|
16680
16932
|
}
|
|
16681
16933
|
});
|
|
16682
16934
|
}
|
|
16935
|
+
async reconcilePluginRuntimeAcrossWorkers() {
|
|
16936
|
+
return await this.pluginRuntimeConvergenceCoordinator.dispatchReconcile(Array.from(this.workers.values()).map((workerInfo) => ({
|
|
16937
|
+
workerId: workerInfo.workerId,
|
|
16938
|
+
status: workerInfo.status,
|
|
16939
|
+
send: workerInfo.process.send?.bind(workerInfo.process)
|
|
16940
|
+
})));
|
|
16941
|
+
}
|
|
16942
|
+
async handleClusterPluginRuntimeReconcileRequest(requester, message) {
|
|
16943
|
+
try {
|
|
16944
|
+
const convergence = await this.reconcilePluginRuntimeAcrossWorkers();
|
|
16945
|
+
requester.process.send?.({
|
|
16946
|
+
status: "cluster-plugin-runtime-reconcile-result",
|
|
16947
|
+
requestId: message.requestId,
|
|
16948
|
+
convergence
|
|
16949
|
+
});
|
|
16950
|
+
} catch (error) {
|
|
16951
|
+
requester.process.send?.({
|
|
16952
|
+
status: "cluster-plugin-runtime-reconcile-result",
|
|
16953
|
+
requestId: message.requestId,
|
|
16954
|
+
convergence: null,
|
|
16955
|
+
error: error instanceof Error ? error.message : String(error)
|
|
16956
|
+
});
|
|
16957
|
+
}
|
|
16958
|
+
}
|
|
16959
|
+
logPluginRuntimeConvergenceStatus() {
|
|
16960
|
+
const report = this.pluginRuntimeConvergenceCoordinator.getStatusReport();
|
|
16961
|
+
logger.info({
|
|
16962
|
+
targetGeneration: report.targetGeneration,
|
|
16963
|
+
convergedGeneration: report.convergedGeneration,
|
|
16964
|
+
status: report.status,
|
|
16965
|
+
failedWorkers: report.failedWorkers,
|
|
16966
|
+
staleWorkers: report.staleWorkers,
|
|
16967
|
+
pendingWorkers: report.pendingWorkers,
|
|
16968
|
+
workers: report.workers
|
|
16969
|
+
}, "Plugin runtime multi-worker convergence status updated");
|
|
16970
|
+
}
|
|
16683
16971
|
watchConfig() {
|
|
16684
16972
|
fs7.watch(CONFIG_PATH, (eventType) => {
|
|
16685
16973
|
if (eventType === "change") {
|