plum-e2e 2.6.5 → 2.6.6

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.
@@ -250,6 +250,40 @@ function stopNode(id, fallbackPort = null) {
250
250
  return signalled;
251
251
  }
252
252
 
253
+ /**
254
+ * Frees a TCP port by killing whatever process is bound to it, so Start can't
255
+ * fail with EADDRINUSE against a stale/orphaned process this manager lost
256
+ * track of (e.g. one that ignored SIGTERM from a previous stop, or was never
257
+ * in the registry to begin with). SIGTERM first, escalating to SIGKILL if the
258
+ * process is still alive after a short grace period.
259
+ *
260
+ * Returns true if a process was found (and signalled), false if the port was
261
+ * already free.
262
+ */
263
+ async function killPort(port) {
264
+ let pid = findPidOnPort(port);
265
+ if (!pid) return false;
266
+
267
+ try {
268
+ process.kill(pid, 'SIGTERM');
269
+ } catch {}
270
+
271
+ const deadline = Date.now() + 2000;
272
+ while (Date.now() < deadline && isAlive(pid)) {
273
+ await new Promise((resolve) => setTimeout(resolve, 150));
274
+ }
275
+
276
+ pid = findPidOnPort(port);
277
+ if (pid && isAlive(pid)) {
278
+ try {
279
+ process.kill(pid, 'SIGKILL');
280
+ } catch {}
281
+ await new Promise((resolve) => setTimeout(resolve, 200));
282
+ }
283
+
284
+ return true;
285
+ }
286
+
253
287
  module.exports = {
254
288
  BACKEND_DIR,
255
289
  LOGS_DIR,
@@ -260,6 +294,7 @@ module.exports = {
260
294
  isLocalUrl,
261
295
  parsePort,
262
296
  findPidOnPort,
297
+ killPort,
263
298
  pruneDead,
264
299
  statusOf,
265
300
  prepareEnv,
@@ -40,7 +40,8 @@ const {
40
40
  prepareEnv,
41
41
  startNode,
42
42
  stopNode,
43
- findPidOnPort
43
+ findPidOnPort,
44
+ killPort
44
45
  } = runnerProcess;
45
46
  const { generateToken, registerWithPrimary, detectLanIp } = nodeRegister;
46
47
 
@@ -206,12 +207,18 @@ async function runAction(r) {
206
207
  const port = parsePort(r.url);
207
208
 
208
209
  if (action === 'start') {
210
+ const s = clack.spinner();
211
+ s.start(`Freeing port ${port}...`);
212
+ const killed = await killPort(Number(port));
213
+ s.stop(killed ? pc.dim(`Freed port ${port}`) : pc.dim(`Port ${port} was already free`));
214
+
209
215
  prepareNodeEnv();
210
216
  const entry = startNode({ id: r.id, port, token: r.token });
211
217
  clack.log.success(pc.green(`Started "${r.name}" on port ${port} (pid ${entry.pid})`));
212
218
  } else if (action === 'stop') {
213
219
  if (r.managed) {
214
220
  const ok = stopNode(r.id);
221
+ await killPort(Number(port));
215
222
  clack.log.success(
216
223
  ok ? pc.green(`Stopped "${r.name}"`) : pc.dim(`"${r.name}" was not running`)
217
224
  );
@@ -223,6 +230,11 @@ async function runAction(r) {
223
230
  s.stop(pc.green(`Stopped "${r.name}"`));
224
231
  } catch (e) {
225
232
  s.stop(pc.red(`Could not stop "${r.name}": ${e.message}`));
233
+ } finally {
234
+ // Belt-and-suspenders: if this runner happens to be local (or
235
+ // the network shutdown call above silently failed), make sure
236
+ // nothing is left bound to its port.
237
+ await killPort(Number(port));
226
238
  }
227
239
  }
228
240
  } else if (action === 'restart') {
@@ -230,7 +242,7 @@ async function runAction(r) {
230
242
  const s = clack.spinner();
231
243
  s.start(`Restarting "${r.name}"...`);
232
244
  stopNode(r.id);
233
- await new Promise((resolve) => setTimeout(resolve, 600));
245
+ await killPort(Number(port));
234
246
  const entry = startNode({ id: r.id, port, token: r.token });
235
247
  s.stop(pc.green(`Restarted "${r.name}" (pid ${entry.pid})`));
236
248
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plum-e2e",
3
- "version": "2.6.5",
3
+ "version": "2.6.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/silverlunah/plum.git"