@matterbridge/thread 3.6.1-dev-20260310-6341dee → 3.6.1-dev-20260311-7da5ff8

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.
@@ -140,7 +140,7 @@ export class BroadcastServer extends EventEmitter {
140
140
  if (message.timestamp === undefined) {
141
141
  message.timestamp = Date.now();
142
142
  }
143
- if (message.dst === this.name || message.dst === 'all') {
143
+ if (message.dst === this.name) {
144
144
  message.dst = message.src;
145
145
  }
146
146
  message.src = this.name;
@@ -1,8 +1,10 @@
1
1
  import { Worker } from 'node:worker_threads';
2
+ import type { WorkerData } from '@matterbridge/types';
2
3
  import { LogLevel } from 'node-ansi-logger';
3
4
  export declare class ThreadsManager {
4
5
  private debug;
5
6
  private verbose;
7
+ private tracker;
6
8
  private log;
7
9
  static logLevel: LogLevel;
8
10
  private server;
@@ -14,7 +16,7 @@ export declare class ThreadsManager {
14
16
  destroy(): void;
15
17
  private msgHandler;
16
18
  private intervalHandler;
17
- runThread(name: string, workerData?: Record<string, boolean | number | string | object>, argv?: string[], env?: NodeJS.ProcessEnv, execArgv?: string[], pipedOutput?: boolean): Worker;
19
+ runThread(name: string, workerData?: WorkerData, argv?: string[], env?: NodeJS.ProcessEnv, execArgv?: string[], pipedOutput?: boolean): Worker;
18
20
  resolvePath(fileName: string): string;
19
21
  createESMWorker(name: string, relativePath: string, workerData?: Record<string, boolean | number | string | object>, argv?: string[], env?: NodeJS.ProcessEnv, execArgv?: string[], pipedOutput?: boolean): Worker;
20
22
  }
@@ -10,6 +10,7 @@ import { BroadcastServer } from './broadcastServer.js';
10
10
  export class ThreadsManager {
11
11
  debug;
12
12
  verbose;
13
+ tracker;
13
14
  log;
14
15
  static logLevel;
15
16
  server;
@@ -25,6 +26,7 @@ export class ThreadsManager {
25
26
  constructor(intervalMs = 10_000) {
26
27
  this.debug = hasParameter('debug') || hasParameter('verbose') || hasParameter('debug-threads') || hasParameter('verbose-threads');
27
28
  this.verbose = hasParameter('verbose') || hasParameter('verbose-threads');
29
+ this.tracker = hasParameter('tracker') || hasParameter('tracker-threads');
28
30
  this.log = new AnsiLogger({
29
31
  logName: 'ThreadsManager',
30
32
  logNameColor: MAGENTA,
@@ -97,17 +99,18 @@ export class ThreadsManager {
97
99
  if (threadInfo.worker) {
98
100
  throw new Error(`Thread ${name} is already running with thread ID ${threadInfo.worker.threadId}`);
99
101
  }
100
- this.log.info(`Starting thread ${threadInfo.name} from path: ${threadInfo.path} type ${threadInfo.type}...`);
102
+ const path = this.resolvePath(threadInfo.path);
103
+ this.log.debug(`Starting thread ${threadInfo.name} from path ${path} type ${threadInfo.type}...`);
101
104
  threadInfo.lastStarted = undefined;
102
105
  threadInfo.lastStopped = undefined;
103
106
  threadInfo.lastDuration = undefined;
104
- threadInfo.worker = this.createESMWorker(threadInfo.name, this.resolvePath(threadInfo.path), { ...workerData, debug: this.debug, verbose: this.verbose, logLevel: this.log.logLevel }, argv, env, execArgv, pipedOutput);
107
+ threadInfo.worker = this.createESMWorker(threadInfo.name, path, { ...workerData, debug: this.debug, verbose: this.verbose, logLevel: this.log.logLevel }, argv, env, execArgv, pipedOutput);
105
108
  const worker = threadInfo.worker;
106
109
  worker.once('online', () => {
107
110
  threadInfo.runCount = (threadInfo.runCount ?? 0) + 1;
108
111
  threadInfo.lastStarted = Date.now();
109
112
  threadInfo.lastSeen = Date.now();
110
- this.log.info(`Thread ${threadInfo.name} is online started at ${new Date(threadInfo.lastStarted).toISOString()} with thread ID ${worker.threadId}`);
113
+ this.log.debug(`Thread ${threadInfo.name} is online started at ${new Date(threadInfo.lastStarted).toISOString()} with thread id ${worker.threadId}`);
111
114
  });
112
115
  worker.once('exit', () => {
113
116
  const stoppedAt = Date.now();
@@ -115,18 +118,19 @@ export class ThreadsManager {
115
118
  threadInfo.lastDuration = Math.max(0, stoppedAt - (threadInfo.lastStarted ?? stoppedAt));
116
119
  threadInfo.worker = undefined;
117
120
  threadInfo.lastSeen = Date.now();
118
- this.log.info(`Thread ${threadInfo.name} has exited at ${new Date(threadInfo.lastStopped).toISOString()} after running for ${threadInfo.lastDuration} ms`);
121
+ this.log.debug(`Thread ${threadInfo.name} has exited at ${new Date(threadInfo.lastStopped).toISOString()} after running for ${threadInfo.lastDuration} ms`);
119
122
  });
120
123
  worker.on('message', (message) => {
121
124
  threadInfo.lastSeen = Date.now();
122
- this.log.info(`Thread ${threadInfo.name} sent a message at ${new Date().toISOString()} after running for ${threadInfo.lastDuration} ms: ${debugStringify(message)}`);
125
+ if (this.verbose)
126
+ this.log.debug(`Thread ${threadInfo.name} sent a message at ${new Date().toISOString()}: ${debugStringify(message)}`);
123
127
  if (message.type === 'log') {
124
128
  AnsiLogger.create({ logName: threadInfo.name, logNameColor: MAGENTA, logTimestampFormat: 4, logLevel: this.log.logLevel }).log(message.logLevel, message.message);
125
129
  }
126
130
  });
127
131
  worker.on('messageerror', () => {
128
132
  threadInfo.errorCount = (threadInfo.errorCount ?? 0) + 1;
129
- this.log.error(`Thread ${threadInfo.name} encountered a message error at ${new Date().toISOString()} after running for ${threadInfo.lastDuration} ms`);
133
+ this.log.error(`Thread ${threadInfo.name} encountered a message error at ${new Date().toISOString()}`);
130
134
  });
131
135
  worker.once('error', () => {
132
136
  threadInfo.errorCount = (threadInfo.errorCount ?? 0) + 1;
@@ -136,7 +140,7 @@ export class ThreadsManager {
136
140
  threadInfo.worker = undefined;
137
141
  this.log.error(`Thread ${threadInfo.name} encountered an error at ${new Date(threadInfo.lastStopped).toISOString()} after running for ${threadInfo.lastDuration} ms`);
138
142
  });
139
- this.log.info(`Started thread ${threadInfo.name} from path: ${threadInfo.path} type ${threadInfo.type}...`);
143
+ this.log.debug(`Started thread ${threadInfo.name} from path ${path} type ${threadInfo.type} with thread id ${worker.threadId}`);
140
144
  return threadInfo.worker;
141
145
  }
142
146
  resolvePath(fileName) {
@@ -154,7 +158,7 @@ export class ThreadsManager {
154
158
  createESMWorker(name, relativePath, workerData, argv, env, execArgv, pipedOutput = false) {
155
159
  const fileURL = pathToFileURL(resolve(relativePath));
156
160
  const options = {
157
- workerData: { ...workerData, threadName: name, debug: this.debug, verbose: this.verbose, logLevel: this.log.logLevel },
161
+ workerData: { ...workerData, threadName: name, debug: this.debug, verbose: this.verbose, logLevel: this.log.logLevel, tracker: this.tracker },
158
162
  type: 'module',
159
163
  name,
160
164
  argv: argv ?? process.argv.slice(2),
@@ -164,7 +168,7 @@ export class ThreadsManager {
164
168
  stderr: pipedOutput,
165
169
  };
166
170
  if (this.verbose)
167
- this.log.debug(`Creating ESM Worker ${name} with file URL: ${fileURL.href} and options: ${debugStringify(options)}`);
171
+ this.log.debug(`Creating ESM Worker ${name} with file URL ${fileURL.href} and options ${debugStringify(options)}`);
168
172
  return new Worker(fileURL, options);
169
173
  }
170
174
  }
@@ -1,7 +1,7 @@
1
1
  import { inspectError } from '@matterbridge/utils/error';
2
2
  import { checkUpdates } from './checkUpdates.js';
3
3
  import { WorkerWrapper } from './workerWrapper.js';
4
- new WorkerWrapper('GlobalPrefix', async (worker) => {
4
+ new WorkerWrapper('CheckUpdates', async (worker) => {
5
5
  worker.logger("info", `Starting check updates...`);
6
6
  let success = false;
7
7
  try {
@@ -1,12 +1,29 @@
1
- import { workerData } from 'node:worker_threads';
1
+ import { isSpawnWorkerData } from '@matterbridge/types';
2
2
  import { spawnCommand } from './spawnCommand.js';
3
3
  import { WorkerWrapper } from './workerWrapper.js';
4
- new WorkerWrapper('GlobalPrefix', async (worker) => {
5
- worker.logger("info", `Starting spawn command ${workerData.command} with args ${workerData.args.join(' ')} and package command ${workerData.packageCommand} for package ${workerData.packageName}...`);
6
- const success = await spawnCommand(workerData.command, workerData.args, workerData.packageCommand, workerData.packageName);
4
+ new WorkerWrapper('SpawnCommand', async (worker) => {
5
+ if (!isSpawnWorkerData(worker.workerData)) {
6
+ worker.logger("error", `SpawnCommand invalid parameters`);
7
+ return false;
8
+ }
9
+ worker.logger("info", `Starting spawn command ${worker.workerData.command} with args ${worker.workerData.args.join(' ')} and package command ${worker.workerData.packageCommand} for package ${worker.workerData.packageName}...`);
10
+ const success = await spawnCommand(worker.workerData.command, worker.workerData.args, worker.workerData.packageCommand, worker.workerData.packageName);
7
11
  if (success)
8
- worker.logger("info", `Spawn command ${workerData.command} with args ${workerData.args.join(' ')} executed successfully`);
12
+ worker.logger("info", `Spawn command ${worker.workerData.command} with args ${worker.workerData.args.join(' ')} executed successfully`);
9
13
  else
10
- worker.logger("error", `Spawn command ${workerData.command} with args ${workerData.args.join(' ')} failed`);
14
+ worker.logger("error", `Spawn command ${worker.workerData.command} with args ${worker.workerData.args.join(' ')} failed`);
15
+ worker.server.respond({
16
+ type: 'manager_spawn_response',
17
+ src: `manager`,
18
+ dst: 'all',
19
+ id: worker.server.getUniqueId(),
20
+ result: {
21
+ command: worker.workerData.command,
22
+ args: worker.workerData.args,
23
+ packageCommand: worker.workerData.packageCommand,
24
+ packageName: worker.workerData.packageName,
25
+ success,
26
+ },
27
+ });
11
28
  return success;
12
29
  });
@@ -1,13 +1,17 @@
1
- import type { ParentPortMessage } from '@matterbridge/types';
1
+ import type { ParentPortMessage, ThreadNames, WorkerData } from '@matterbridge/types';
2
+ import type { Tracker } from '@matterbridge/utils/tracker';
2
3
  import { AnsiLogger, LogLevel } from 'node-ansi-logger';
3
4
  import { BroadcastServer } from './broadcastServer.js';
4
5
  export declare class WorkerWrapper {
5
- name: string;
6
+ name: ThreadNames;
6
7
  debug: boolean;
7
8
  verbose: boolean;
9
+ useTracker: boolean;
8
10
  log: AnsiLogger;
9
11
  server: BroadcastServer;
10
- constructor(name: string, callback: (worker: WorkerWrapper) => Promise<boolean>);
12
+ workerData: WorkerData | null;
13
+ tracker: Tracker | undefined;
14
+ constructor(name: ThreadNames, callback: (worker: WorkerWrapper) => Promise<boolean>);
11
15
  destroy(success: boolean): void;
12
16
  parentPost(message: ParentPortMessage): void;
13
17
  parentLog(logName: string | undefined, logLevel: LogLevel, message: string): void;
@@ -1,6 +1,8 @@
1
- import { inspect } from 'node:util';
1
+ if (process.argv.includes('--loader') || process.argv.includes('-loader'))
2
+ console.log('\u001B[32mWorkerWrapper loaded.\u001B[40;0m');
2
3
  import { isMainThread, parentPort, threadId, workerData } from 'node:worker_threads';
3
4
  import { hasParameter } from '@matterbridge/utils/cli';
5
+ import { formatBytes } from '@matterbridge/utils/format';
4
6
  import { AnsiLogger, debugStringify, MAGENTA } from 'node-ansi-logger';
5
7
  import { BroadcastServer } from './broadcastServer.js';
6
8
  import { ThreadsManager } from './threadsManager.js';
@@ -8,40 +10,60 @@ export class WorkerWrapper {
8
10
  name;
9
11
  debug = hasParameter('debug') || hasParameter('verbose') || hasParameter('debug-threads') || hasParameter('verbose-threads');
10
12
  verbose = hasParameter('verbose') || hasParameter('verbose-threads');
13
+ useTracker = hasParameter('tracker') || hasParameter('tracker-threads');
11
14
  log;
12
15
  server;
16
+ workerData = workerData;
17
+ tracker;
13
18
  constructor(name, callback) {
14
19
  this.name = name;
20
+ if (this.workerData) {
21
+ this.debug = this.workerData.debug || this.debug;
22
+ this.verbose = this.workerData.verbose || this.verbose;
23
+ this.useTracker = this.workerData.tracker || this.useTracker;
24
+ }
25
+ if (this.useTracker) {
26
+ void import('@matterbridge/utils/tracker')
27
+ .then(({ Tracker }) => {
28
+ this.tracker = new Tracker(`Thread${this.name}`, this.debug, this.verbose);
29
+ this.tracker.start();
30
+ return undefined;
31
+ })
32
+ .catch((err) => {
33
+ if (this.debug)
34
+ console.error(`WorkerWrapper ${this.name}: failed to load Tracker`, err);
35
+ return undefined;
36
+ });
37
+ }
15
38
  this.log = new AnsiLogger({
16
- logName: name,
39
+ logName: this.name,
17
40
  logNameColor: MAGENTA,
18
41
  logTimestampFormat: 4,
19
42
  logLevel: this.debug ? "debug" : "info",
20
43
  });
21
44
  this.server = new BroadcastServer('matterbridge', this.log);
22
- if (!isMainThread && parentPort) {
45
+ if (!isMainThread && parentPort && this.workerData) {
23
46
  parentPort.on('message', (message) => {
24
47
  if (this.debug)
25
- this.log.debug(`Worker ${workerData.threadName}:${threadId} received message from parent: ${debugStringify(message)}`);
48
+ this.log.debug(`Worker ${this.name}:${threadId} received message from parent: ${debugStringify(message)}`);
26
49
  switch (message.type) {
27
50
  case 'ping':
28
- this.parentLog(workerData.threadName, "debug", `Worker ${workerData.threadName}:${threadId} received ping message type from parent: ${debugStringify(message)}`);
29
- this.parentPost({ type: 'pong', threadId, threadName: workerData.threadName });
30
- this.parentLog(workerData.threadName, "debug", `Worker ${workerData.threadName}:${threadId} sent pong message type to parent: ${debugStringify(message)}`);
51
+ this.parentLog(this.name, "debug", `Worker ${this.name}:${threadId} received ping message type from parent: ${debugStringify(message)}`);
52
+ this.parentPost({ type: 'pong', threadId, threadName: this.name });
53
+ this.parentLog(this.name, "debug", `Worker ${this.name}:${threadId} sent pong message type to parent: ${debugStringify(message)}`);
31
54
  break;
32
55
  case 'pong':
33
- this.parentLog(workerData.threadName, "debug", `Worker ${workerData.threadName}:${threadId} received pong message type from parent: ${debugStringify(message)}`);
56
+ this.parentLog(this.name, "debug", `Worker ${this.name}:${threadId} received pong message type from parent: ${debugStringify(message)}`);
34
57
  break;
35
58
  default:
36
- this.parentLog(workerData.threadName, "warn", `Worker ${workerData.threadName}:${threadId} received unknown message type from parent: ${debugStringify(message)}`);
59
+ this.parentLog(this.name, "warn", `Worker ${this.name}:${threadId} received unknown message type from parent: ${debugStringify(message)}`);
37
60
  }
38
61
  });
39
62
  }
40
- if (!isMainThread && parentPort && workerData) {
41
- name = workerData.threadName;
42
- this.parentPost({ type: 'init', threadId, threadName: workerData.threadName, success: true });
63
+ if (!isMainThread && parentPort && this.workerData) {
64
+ this.parentPost({ type: 'init', threadId, threadName: this.name, memoryUsage: process.memoryUsage(), success: true });
43
65
  if (this.debug)
44
- this.parentLog(name, "info", `Worker ${workerData.threadName}:${threadId} initialized.`);
66
+ this.parentLog(this.name, "info", `Worker ${this.name}:${threadId} initialized.`);
45
67
  }
46
68
  if (this.verbose)
47
69
  this.logWorkerInfo(this.log, false);
@@ -51,28 +73,30 @@ export class WorkerWrapper {
51
73
  });
52
74
  }
53
75
  destroy(success) {
76
+ if (this.tracker)
77
+ this.tracker.stop();
54
78
  this.server.close();
55
- if (!isMainThread && parentPort && workerData) {
79
+ if (!isMainThread && parentPort && this.workerData) {
56
80
  if (this.debug)
57
- this.parentLog(this.name, "info", `Worker ${workerData.threadName}:${threadId} exiting with success: ${success}.`);
58
- this.parentPost({ type: 'exit', threadId, threadName: workerData.threadName, success });
81
+ this.parentLog(this.name, "info", `Worker ${this.name}:${threadId} exiting with success: ${success}.`);
82
+ this.parentPost({ type: 'exit', threadId, threadName: this.name, memoryUsage: process.memoryUsage(), success });
59
83
  parentPort.close();
60
84
  }
61
85
  }
62
86
  parentPost(message) {
63
87
  if (!parentPort)
64
- throw new Error(`WorkerServer ${workerData.threadName}: parentPort is not available.`);
88
+ throw new Error(`WorkerServer ${this.name}: parentPort is not available.`);
65
89
  parentPort.postMessage(message);
66
90
  if (this.debug)
67
- this.log.debug(`Worker ${workerData.threadName}:${threadId} sent message to parent: ${debugStringify(message)}`);
91
+ this.log.debug(`Worker ${this.name}:${threadId} sent message to parent: ${debugStringify(message)}`);
68
92
  }
69
93
  parentLog(logName, logLevel, message) {
70
94
  if (!parentPort)
71
- throw new Error(`WorkerServer ${workerData.threadName}: parentPort is not available.`);
72
- const logMessage = { type: 'log', threadId, threadName: workerData.threadName, logName, logLevel, message };
95
+ throw new Error(`WorkerServer ${this.name}: parentPort is not available.`);
96
+ const logMessage = { type: 'log', threadId, threadName: this.name, logName, logLevel, message };
73
97
  parentPort.postMessage(logMessage);
74
98
  if (this.debug)
75
- this.log.debug(`Worker ${workerData.threadName}:${threadId} sent log to parent: ${logName} ${logLevel} ${message}`);
99
+ this.log.debug(`Worker ${this.name}:${threadId} sent log to parent: ${logName} ${logLevel} ${message}`);
76
100
  }
77
101
  logger(level, message) {
78
102
  if (!isMainThread && parentPort)
@@ -81,11 +105,12 @@ export class WorkerWrapper {
81
105
  AnsiLogger.create({ logName: this.name, logNameColor: MAGENTA, logTimestampFormat: 4, logLevel: ThreadsManager.logLevel }).log(level, message);
82
106
  }
83
107
  logWorkerInfo(log, logEnv = false) {
84
- log.debug(`${isMainThread ? 'Main thread' : 'Worker thread'}: ${workerData?.threadName}:${threadId} Pid: ${process.pid}`);
108
+ log.debug(`${isMainThread ? 'Main thread' : 'Worker thread'}: ${this.name}:${threadId} Pid: ${process.pid}`);
85
109
  log.debug(`ParentPort: ${parentPort ? 'active' : 'not active'}`);
86
- log.debug(`WorkerData: ${workerData ? debugStringify(workerData) : 'none'}`);
110
+ log.debug(`WorkerData: ${this.workerData ? debugStringify(this.workerData) : 'none'}`);
87
111
  const argv = process.argv.slice(2);
88
112
  log.debug(`Argv: ${argv.length ? argv.join(' ') : 'none'}`);
89
- log.debug(`Env: ${logEnv ? inspect(process.env, true, 10, true) : 'not logged'}`);
113
+ log.debug(`Env: ${logEnv ? debugStringify(process.env) : 'not logged'}`);
114
+ log.debug(`Memory: ${formatBytes(process.memoryUsage().heapTotal)} total, ${formatBytes(process.memoryUsage().heapUsed)} used`);
90
115
  }
91
116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/thread",
3
- "version": "3.6.1-dev-20260310-6341dee",
3
+ "version": "3.6.1-dev-20260311-7da5ff8",
4
4
  "description": "Matterbridge thread library",
5
5
  "author": "https://github.com/Luligu",
6
6
  "homepage": "https://matterbridge.io/",
@@ -69,8 +69,8 @@
69
69
  "CHANGELOG.md"
70
70
  ],
71
71
  "dependencies": {
72
- "@matterbridge/types": "3.6.1-dev-20260310-6341dee",
73
- "@matterbridge/utils": "3.6.1-dev-20260310-6341dee",
72
+ "@matterbridge/types": "3.6.1-dev-20260311-7da5ff8",
73
+ "@matterbridge/utils": "3.6.1-dev-20260311-7da5ff8",
74
74
  "node-ansi-logger": "3.2.0"
75
75
  }
76
76
  }