@spirobel/monero-wallet-api 0.5.4 → 0.5.5

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.
@@ -1 +1,2 @@
1
+ export declare function requestCoordinatorShutdown(): void;
1
2
  export declare function coordinatorMainWorker(scan_settings_path?: string, pathPrefix?: string, cpuPorts?: MessagePort[]): Promise<void>;
@@ -1,9 +1,16 @@
1
1
  import { coordinatorMainMultithreaded } from "../scanresult/scanCoordination";
2
2
  import { log } from "../../io/logging";
3
+ // one controller per coordinator worker; shutdown aborts fetch + race
4
+ let shutdownController;
5
+ export function requestCoordinatorShutdown() {
6
+ log("coordinatorMainWorker", ["shutdown requested"]);
7
+ shutdownController?.abort();
8
+ }
3
9
  export async function coordinatorMainWorker(scan_settings_path, pathPrefix, cpuPorts) {
4
10
  log("coordinatorMainWorker", "cpuPorts=" + cpuPorts?.length);
11
+ shutdownController = new AbortController();
5
12
  try {
6
- const gen = coordinatorMainMultithreaded(scan_settings_path, pathPrefix, cpuPorts);
13
+ const gen = coordinatorMainMultithreaded(scan_settings_path, pathPrefix, cpuPorts, shutdownController.signal);
7
14
  for await (const event of gen) {
8
15
  log("coordinatorMainWorker", ["event:", event]);
9
16
  if (event.type === "scan_ready") {
@@ -15,9 +22,17 @@ export async function coordinatorMainWorker(scan_settings_path, pathPrefix, cpuP
15
22
  },
16
23
  });
17
24
  }
25
+ else if (event.type === "shutdown_done") {
26
+ self.postMessage({ type: "SHUTDOWN_DONE" });
27
+ return;
28
+ }
18
29
  }
30
+ // generator returned without shutdown_done (e.g. empty)
31
+ self.postMessage({ type: "SHUTDOWN_DONE" });
19
32
  }
20
33
  catch (error) {
34
+ // still tell main thread we stopped so terminate can proceed
35
+ self.postMessage({ type: "SHUTDOWN_DONE" });
21
36
  self.postMessage({ type: "ERROR", payload: error });
22
37
  }
23
38
  }
@@ -14,8 +14,9 @@ export async function handleCpuboundScan(msg, cpu_worker_status) {
14
14
  throw new Error("[cpubound] no content msg! ( ScanLoopInput = undefined )");
15
15
  }
16
16
  if (msg === "cancel") {
17
+ // idle cancel: nothing in flight, just ack
17
18
  log("handleCpuboundScan", "cancel msg! ( ScanLoopInput = cancel )");
18
- throw new Error("[cpubound] no content msg! ( ScanLoopInput = undefined )");
19
+ return { type: "Canceled" };
19
20
  }
20
21
  log("handleCpuboundScan", "got scan msg, walletConfig=" +
21
22
  msg.walletConfig?.primary_address?.slice(0, 6));
@@ -89,7 +90,7 @@ export async function handleCpuboundScan(msg, cpu_worker_status) {
89
90
  if (cpu_worker_status.cancel) {
90
91
  cpu_worker_status.cancel = false;
91
92
  log("handleCpuboundScan", ["cpu work canceled"]);
92
- return { type: "Ready" };
93
+ return { type: "Canceled", work_uuid: item.work_uuid };
93
94
  }
94
95
  await sleep(10); // make sure the loop is not tight
95
96
  }
@@ -1,4 +1,4 @@
1
- import { coordinatorMainWorker } from "./coordinator-main";
1
+ import { coordinatorMainWorker, requestCoordinatorShutdown, } from "./coordinator-main";
2
2
  import { handleCpuboundScanTry, sendFromCpuWorker } from "./cpubound-main";
3
3
  import { log, setupLoggingPath } from "../../io/logging";
4
4
  import { multisigMainWorkerCall } from "./multisig-main";
@@ -9,21 +9,32 @@ let SCAN_SETTINGS_PATH;
9
9
  let PATH_PREFIX;
10
10
  let cpuPort;
11
11
  const cpu_worker_status = { cancel: false };
12
+ // true while a scan is running so idle cancel can ack immediately
13
+ let cpu_work_in_flight = false;
12
14
  let multisig_dkg;
13
15
  export function CPU_PORT_HANDLER(pe) {
14
16
  if (pe.data === "cancel") {
15
- cpu_worker_status.cancel = true;
17
+ if (cpu_work_in_flight) {
18
+ cpu_worker_status.cancel = true;
19
+ return;
20
+ }
21
+ // idle cancel: ack right away so coordinator drain does not hang
22
+ if (cpuPort) {
23
+ sendFromCpuWorker(cpuPort, { type: "Canceled" });
24
+ }
16
25
  return;
17
26
  }
18
27
  if (!cpuPort)
19
28
  throw new Error("[cpubound] cpuPort is undefined in port.onmessage");
20
29
  log("CPU_PORT_HANDLER", "new workitem msg received");
30
+ cpu_work_in_flight = true;
21
31
  cpuPort.postMessage({
22
32
  type: "WORKSTART",
23
33
  work_uuid: pe.data.work_uuid,
24
34
  });
25
35
  handleCpuboundScanTry(pe.data, cpu_worker_status).then((result) => {
26
36
  log("CPU_PORT_HANDLER", "work finished, sending result");
37
+ cpu_work_in_flight = false;
27
38
  if (!cpuPort)
28
39
  throw new Error("[cpubound] cpuPort is undefined in port.onmessage");
29
40
  cpuPort.onmessage = CPU_PORT_HANDLER;
@@ -33,6 +44,38 @@ export function CPU_PORT_HANDLER(pe) {
33
44
  const handleMessage = async (e) => {
34
45
  const msg = e.data;
35
46
  log("handleMessage", ["msg received", msg]);
47
+ if (msg.type === "shutdown") {
48
+ // only coordinator listens for this; abort fetch + race loop
49
+ requestCoordinatorShutdown();
50
+ return;
51
+ }
52
+ if (msg.type === "heap_snapshot") {
53
+ try {
54
+ const path = typeof msg.path === "string" && msg.path.length > 0
55
+ ? msg.path
56
+ : `worker-heap-${Date.now()}.heapsnapshot`;
57
+ // @ts-ignore bun has generateHeapSnapshot; node has v8.writeHeapSnapshot
58
+ let out;
59
+ if (typeof Bun !== "undefined" && typeof Bun.generateHeapSnapshot === "function") {
60
+ const snap = Bun.generateHeapSnapshot("v8");
61
+ await Bun.write(path, typeof snap === "string" ? snap : JSON.stringify(snap));
62
+ out = path;
63
+ }
64
+ else {
65
+ throw Error("in the browser use the inspector directly to observe worker memory growth");
66
+ // const v8 = await import("node:v8");
67
+ // out = v8.writeHeapSnapshot(path);
68
+ }
69
+ self.postMessage({ type: "HEAP_SNAPSHOT_DONE", path: out ?? path });
70
+ }
71
+ catch (err) {
72
+ self.postMessage({
73
+ type: "HEAP_SNAPSHOT_ERROR",
74
+ error: err instanceof Error ? err.message : String(err),
75
+ });
76
+ }
77
+ return;
78
+ }
36
79
  if (msg.type === "setup") {
37
80
  const settingsPath = msg.scan_settings_path || SCAN_SETTINGS_STORE_NAME_DEFAULT;
38
81
  SCAN_SETTINGS_PATH = settingsPath;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spirobel/monero-wallet-api",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "main": "dist/api.js",
5
5
  "module": "dist/api.js",
6
6
  "devDependencies": {