channel-worker 2.3.0 → 2.3.1

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.
Files changed (2) hide show
  1. package/lib/cache-server.js +65 -13
  2. package/package.json +1 -1
@@ -13,6 +13,7 @@
13
13
 
14
14
  const http = require('http');
15
15
  const url = require('url');
16
+ const { execSync } = require('child_process');
16
17
 
17
18
  class CacheServer {
18
19
  constructor(api, options = {}) {
@@ -110,25 +111,76 @@ class CacheServer {
110
111
  res.end(JSON.stringify({ success: false, message: 'not found' }));
111
112
  }
112
113
 
113
- start() {
114
- return new Promise((resolve, reject) => {
115
- this.server = http.createServer((req, res) => this._handleRequest(req, res));
116
- this.server.on('error', (err) => {
117
- if (err.code === 'EADDRINUSE') {
118
- console.error(`[cache-server] Port ${this.port} is already in use. ` +
119
- `Another daemon may be running, or a previous instance crashed and the OS hasn't released the port yet ` +
120
- `(usually clears in 30-60s). On Windows: 'netstat -ano | findstr ${this.port}' then 'taskkill /PID <pid> /F'. ` +
121
- `On Mac/Linux: 'lsof -ti:${this.port} | xargs kill -9'.`);
114
+ // Find PID(s) holding our port and kill them. Cross-platform best-effort.
115
+ // Used when bind fails with EADDRINUSE — typically a leaked previous daemon.
116
+ _killPortHolder() {
117
+ try {
118
+ if (process.platform === 'win32') {
119
+ // netstat columns: Proto Local Foreign State PID
120
+ const out = execSync(`netstat -ano -p TCP | findstr :${this.port} | findstr LISTENING`,
121
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
122
+ const pids = new Set();
123
+ for (const line of out.split('\n')) {
124
+ const m = line.trim().match(/\s(\d+)\s*$/);
125
+ if (m) pids.add(m[1]);
122
126
  }
123
- reject(err);
124
- });
125
- this.server.listen(this.port, this.host, () => {
126
- console.log(`[cache-server] Listening on http://${this.host}:${this.port} (max ${this.maxEntries} entries)`);
127
+ for (const pid of pids) {
128
+ if (Number(pid) === process.pid) continue;
129
+ console.log(`[cache-server] Killing leaked PID ${pid} holding port ${this.port}`);
130
+ try { execSync(`taskkill /PID ${pid} /F`, { stdio: 'ignore' }); } catch {}
131
+ }
132
+ return pids.size;
133
+ }
134
+ // mac/linux
135
+ const out = execSync(`lsof -ti:${this.port} -sTCP:LISTEN`,
136
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
137
+ const pids = out.split('\n').map(s => s.trim()).filter(Boolean);
138
+ for (const pid of pids) {
139
+ if (Number(pid) === process.pid) continue;
140
+ console.log(`[cache-server] Killing leaked PID ${pid} holding port ${this.port}`);
141
+ try { execSync(`kill -9 ${pid}`, { stdio: 'ignore' }); } catch {}
142
+ }
143
+ return pids.length;
144
+ } catch {
145
+ return 0;
146
+ }
147
+ }
148
+
149
+ _bindOnce() {
150
+ return new Promise((resolve, reject) => {
151
+ const server = http.createServer((req, res) => this._handleRequest(req, res));
152
+ server.on('error', (err) => reject(err));
153
+ server.listen(this.port, this.host, () => {
154
+ this.server = server;
127
155
  resolve();
128
156
  });
129
157
  });
130
158
  }
131
159
 
160
+ async start() {
161
+ try {
162
+ await this._bindOnce();
163
+ } catch (err) {
164
+ if (err.code !== 'EADDRINUSE') throw err;
165
+ console.warn(`[cache-server] Port ${this.port} busy — trying to kill leaked holder...`);
166
+ const killed = this._killPortHolder();
167
+ if (killed === 0) {
168
+ console.error(`[cache-server] No leaked process found for port ${this.port}. ` +
169
+ `Maybe TIME_WAIT — wait 30-60s and retry.`);
170
+ throw err;
171
+ }
172
+ // Wait for OS to release the port after kill, then retry once
173
+ await new Promise(r => setTimeout(r, 500));
174
+ try {
175
+ await this._bindOnce();
176
+ } catch (err2) {
177
+ console.error(`[cache-server] Still cannot bind ${this.port} after killing leaked PIDs.`);
178
+ throw err2;
179
+ }
180
+ }
181
+ console.log(`[cache-server] Listening on http://${this.host}:${this.port} (max ${this.maxEntries} entries)`);
182
+ }
183
+
132
184
  stop() {
133
185
  if (this.server) {
134
186
  this.server.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "Channel Manager worker daemon — runs on remote machines to execute video pipeline jobs",
5
5
  "main": "lib/daemon.js",
6
6
  "bin": {