@matterbridge/thread 3.9.3-dev-20260628-fa05360 → 3.9.3-dev-20260630-684e0d2

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.
@@ -11,36 +11,18 @@ export function takeDockerVersionWarning() {
11
11
  async function httpsGetJson(url, headers, timeoutMs) {
12
12
  const https = await import('node:https');
13
13
  return new Promise((resolve, reject) => {
14
- let settled = false;
15
- let req;
16
- let timeoutId;
17
- const rejectOnce = (error) => {
18
- if (settled)
19
- return;
20
- settled = true;
21
- clearTimeout(timeoutId);
22
- reject(error);
23
- };
24
- const resolveOnce = (value) => {
25
- if (settled)
26
- return;
27
- settled = true;
28
- clearTimeout(timeoutId);
29
- resolve(value);
30
- };
31
- const timeoutError = new Error(`Request timed out after ${timeoutMs / 1000} seconds`);
32
- timeoutId = setTimeout(() => {
33
- req?.destroy?.(timeoutError);
34
- rejectOnce(timeoutError);
14
+ const controller = new AbortController();
15
+ const timeoutId = setTimeout(() => {
16
+ controller.abort();
17
+ reject(new Error(`Request timed out after ${timeoutMs / 1000} seconds`));
35
18
  }, timeoutMs).unref();
36
- req = https.get(url, { headers }, (res) => {
19
+ const req = https.get(url, { headers, signal: controller.signal }, (res) => {
37
20
  let data = '';
38
21
  const statusCode = res.statusCode ?? 0;
39
22
  if (statusCode >= 300 && statusCode < 400) {
40
23
  const locationHeader = Array.isArray(res.headers.location) ? res.headers.location[0] : res.headers.location;
41
24
  if (locationHeader) {
42
25
  clearTimeout(timeoutId);
43
- settled = true;
44
26
  res.resume();
45
27
  const redirectedUrl = new URL(locationHeader, url).toString();
46
28
  let redirectedHeaders = headers;
@@ -59,8 +41,9 @@ async function httpsGetJson(url, headers, timeoutMs) {
59
41
  }
60
42
  }
61
43
  if (statusCode < 200 || statusCode >= 300) {
44
+ clearTimeout(timeoutId);
62
45
  res.resume();
63
- rejectOnce(new Error(`Failed to fetch data. Status code: ${statusCode}`));
46
+ reject(new Error(`Failed to fetch data. Status code: ${statusCode}`));
64
47
  return;
65
48
  }
66
49
  res.on('data', (chunk) => {
@@ -68,15 +51,18 @@ async function httpsGetJson(url, headers, timeoutMs) {
68
51
  });
69
52
  res.on('end', () => {
70
53
  try {
71
- resolveOnce(JSON.parse(data));
54
+ clearTimeout(timeoutId);
55
+ resolve(JSON.parse(data));
72
56
  }
73
57
  catch (error) {
74
- rejectOnce(new Error(`Failed to parse response JSON: ${getErrorMessage(error)}`));
58
+ clearTimeout(timeoutId);
59
+ reject(new Error(`Failed to parse response JSON: ${getErrorMessage(error)}`));
75
60
  }
76
61
  });
77
62
  });
78
63
  req.on('error', (error) => {
79
- rejectOnce(new Error(`Request failed: ${getErrorMessage(error)}`));
64
+ clearTimeout(timeoutId);
65
+ reject(new Error(`Request failed: ${getErrorMessage(error)}`));
80
66
  });
81
67
  });
82
68
  }
@@ -12,9 +12,11 @@ export declare class ThreadsManager {
12
12
  private interval;
13
13
  private intervalMs;
14
14
  private threads;
15
+ private terminateWorkers;
15
16
  constructor(intervalMs?: number);
16
17
  destroy(): void;
17
18
  private msgHandler;
19
+ private terminateExitedWorkers;
18
20
  private intervalHandler;
19
21
  runThread(name: string, workerData?: WorkerData, argv?: string[], env?: NodeJS.ProcessEnv, execArgv?: string[], pipedOutput?: boolean): Worker;
20
22
  runInMainThread(name: string, workerData?: WorkerData | null): Promise<boolean>;
@@ -5,6 +5,7 @@ import { Worker } from 'node:worker_threads';
5
5
  import { hasParameter } from '@matterbridge/utils/cli';
6
6
  import { getErrorMessage } from '@matterbridge/utils/error';
7
7
  import { logModuleLoaded } from '@matterbridge/utils/loader';
8
+ import { fireAndForget } from '@matterbridge/utils/wait';
8
9
  import { AnsiLogger, CYAN, db, debugStringify, ft, MAGENTA, wr } from 'node-ansi-logger';
9
10
  import { BroadcastServer } from './broadcastServer.js';
10
11
  logModuleLoaded('ThreadsManager');
@@ -26,6 +27,7 @@ export class ThreadsManager {
26
27
  { name: 'ArchiveCommand', path: 'workerArchiveCommand.js', type: 'worker' },
27
28
  { name: 'DockerVersion', path: 'workerDockerVersion.js', type: 'worker' },
28
29
  ];
30
+ terminateWorkers = new Set();
29
31
  constructor(intervalMs = 60_000) {
30
32
  this.debug = hasParameter('debug') || hasParameter('verbose') || hasParameter('debug-threads') || hasParameter('verbose-threads');
31
33
  this.verbose = hasParameter('verbose') || hasParameter('verbose-threads');
@@ -47,6 +49,7 @@ export class ThreadsManager {
47
49
  }
48
50
  destroy() {
49
51
  clearInterval(this.interval);
52
+ this.terminateExitedWorkers();
50
53
  this.server.off('broadcast_message', this.boundMsgHandler);
51
54
  this.server.close();
52
55
  if (this.verbose)
@@ -83,10 +86,26 @@ export class ThreadsManager {
83
86
  }
84
87
  }
85
88
  }
89
+ terminateExitedWorkers() {
90
+ if (this.terminateWorkers.size > 0) {
91
+ this.log.debug(`Terminating ${this.terminateWorkers.size} workers that have exited...`);
92
+ for (const worker of this.terminateWorkers) {
93
+ try {
94
+ this.log.debug(`Terminating worker with thread id ${worker.threadId}...`);
95
+ fireAndForget(worker.terminate(), this.log, `Failed to terminate worker with thread id ${worker.threadId}`);
96
+ }
97
+ catch (error) {
98
+ this.log.error(`Failed to terminate worker with thread id ${worker.threadId}: ${getErrorMessage(error)}`);
99
+ }
100
+ }
101
+ this.terminateWorkers.clear();
102
+ }
103
+ }
86
104
  intervalHandler() {
87
105
  for (const thread of this.threads) {
88
106
  this.log.debug(`Thread ${thread.name} running: ${thread.worker ? 'yes' : 'no'}, lastSeen: ${thread.lastSeen ? new Date(thread.lastSeen).toISOString() : 'never'}, runs: ${thread.runCount ?? 0}, errors: ${thread.errorCount ?? 0}`);
89
107
  }
108
+ this.terminateExitedWorkers();
90
109
  for (const thread of this.threads) {
91
110
  if (thread.worker && Date.now() - (thread.lastSeen ?? 0) > this.intervalMs) {
92
111
  const msg = { type: 'ping', threadId: thread.worker.threadId, threadName: thread.name };
@@ -128,6 +147,7 @@ export class ThreadsManager {
128
147
  threadInfo.worker = undefined;
129
148
  }
130
149
  this.log.debug(`Thread ${threadInfo.name} has exited at ${new Date(now).toISOString()}`);
150
+ this.terminateWorkers.delete(worker);
131
151
  });
132
152
  worker.on('message', (message) => {
133
153
  const now = Date.now();
@@ -148,6 +168,7 @@ export class ThreadsManager {
148
168
  if (!message.success) {
149
169
  threadInfo.errorCount = (threadInfo.errorCount ?? 0) + 1;
150
170
  }
171
+ this.terminateWorkers.add(worker);
151
172
  threadInfo.worker = undefined;
152
173
  this.log.debug(`Thread ${threadInfo.name} has exited at ${new Date(now).toISOString()} with thread id ${worker.threadId} after running for ${threadInfo.lastDuration} ms`);
153
174
  }
@@ -165,6 +186,7 @@ export class ThreadsManager {
165
186
  threadInfo.lastDuration = Math.max(0, now - (threadInfo.lastStarted ?? now));
166
187
  threadInfo.errorCount = (threadInfo.errorCount ?? 0) + 1;
167
188
  threadInfo.worker = undefined;
189
+ this.terminateWorkers.add(worker);
168
190
  this.log.error(`Thread ${threadInfo.name} encountered an error at ${new Date(now).toISOString()} after running for ${threadInfo.lastDuration} ms: ${getErrorMessage(error)}`);
169
191
  });
170
192
  this.log.debug(`Started thread ${threadInfo.name} from path ${path} type ${threadInfo.type} with thread id ${worker.threadId}`);
@@ -145,6 +145,7 @@ export class WorkerWrapper {
145
145
  this.destroy(false);
146
146
  }
147
147
  safeParentLog(level, message) {
148
+ this.log.error(`Worker ${this.name}: ${message}`);
148
149
  if (this.destroyed)
149
150
  return;
150
151
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/thread",
3
- "version": "3.9.3-dev-20260628-fa05360",
3
+ "version": "3.9.3-dev-20260630-684e0d2",
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.9.3-dev-20260628-fa05360",
73
- "@matterbridge/utils": "3.9.3-dev-20260628-fa05360",
72
+ "@matterbridge/types": "3.9.3-dev-20260630-684e0d2",
73
+ "@matterbridge/utils": "3.9.3-dev-20260630-684e0d2",
74
74
  "@zip.js/zip.js": "2.8.26",
75
75
  "node-ansi-logger": "3.3.0"
76
76
  }