@platformatic/runtime 3.44.0 → 3.46.0

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.
package/lib/runtime.js CHANGED
@@ -117,6 +117,7 @@ export class Runtime extends EventEmitter {
117
117
  #concurrency
118
118
  #entrypointId
119
119
  #url
120
+ #entrypointPort
120
121
 
121
122
  #healthMetricsTimer
122
123
 
@@ -157,6 +158,7 @@ export class Runtime extends EventEmitter {
157
158
  this.#applications = new Map()
158
159
  this.#workers = new RoundRobinMap()
159
160
  this.#url = undefined
161
+ this.#entrypointPort = undefined
160
162
  this.#channelCreationHook = createChannelCreationHook(this.#config)
161
163
  this.#meshInterceptor = createThreadInterceptor({
162
164
  domain: '.plt.local',
@@ -680,6 +682,14 @@ export class Runtime extends EventEmitter {
680
682
  if (this.#restartingApplications.has(id)) {
681
683
  return
682
684
  }
685
+
686
+ // Wait for the runtime to be fully started before attempting a restart.
687
+ // Restarting an application while the runtime is still starting causes
688
+ // races between the start and stop ITC commands in the worker thread.
689
+ if (this.#status === 'starting') {
690
+ await once(this, 'started')
691
+ }
692
+
683
693
  this.#restartingApplications.add(id)
684
694
 
685
695
  try {
@@ -1852,6 +1862,13 @@ export class Runtime extends EventEmitter {
1852
1862
  })
1853
1863
 
1854
1864
  worker[kITC].on('request:restart', async () => {
1865
+ // Do not restart applications that are not fully started yet or when the runtime is still starting.
1866
+ // The gateway sends request:restart when it receives application:added events,
1867
+ // which can arrive while the worker is still in the starting phase.
1868
+ if (this.#status !== 'started' || worker[kWorkerStatus] !== 'started') {
1869
+ return
1870
+ }
1871
+
1855
1872
  try {
1856
1873
  await this.restartApplication(applicationId)
1857
1874
  } catch (e) {
@@ -2153,6 +2170,20 @@ export class Runtime extends EventEmitter {
2153
2170
 
2154
2171
  if (workerUrl) {
2155
2172
  this.#url = workerUrl
2173
+
2174
+ // Pin the entrypoint port so that subsequent restarts (especially with
2175
+ // stopBeforeStart when reuseTcpPorts is false) bind to the same port
2176
+ // instead of getting a new random one.
2177
+ if (applicationConfig.entrypoint) {
2178
+ try {
2179
+ const boundPort = Number(new URL(workerUrl).port)
2180
+ if (boundPort) {
2181
+ this.#entrypointPort = boundPort
2182
+ }
2183
+ } catch {
2184
+ // URL parsing failed, leave unchanged
2185
+ }
2186
+ }
2156
2187
  }
2157
2188
 
2158
2189
  // Wait for the interceptor to be ready
@@ -2379,6 +2410,16 @@ export class Runtime extends EventEmitter {
2379
2410
  applicationConfig.entrypoint &&
2380
2411
  (config.reuseTcpPorts === false || applicationConfig.reuseTcpPorts === false || !features.node.reusePort)
2381
2412
 
2413
+ // When we must stop before start (no reusePort available), pin the entrypoint
2414
+ // port in the config so the replacement worker binds to the same port.
2415
+ // We only do this for stopBeforeStart because when reusePort is available
2416
+ // the new worker starts alongside the old one and must use port 0 to avoid
2417
+ // SO_REUSEPORT routing requests to the stale old worker.
2418
+ let configForNewWorker = config
2419
+ if (stopBeforeStart && this.#entrypointPort && config.server) {
2420
+ configForNewWorker = { ...config, server: { ...config.server, port: this.#entrypointPort } }
2421
+ }
2422
+
2382
2423
  if (stopBeforeStart) {
2383
2424
  await this.#removeWorker(workersCount, applicationId, index, worker, silent, label)
2384
2425
  }
@@ -2388,8 +2429,8 @@ export class Runtime extends EventEmitter {
2388
2429
  this.logger.debug(`Preparing to start a replacement for ${label} ...`)
2389
2430
  }
2390
2431
 
2391
- // Create a new worker
2392
- newWorker = await this.#setupWorker(config, applicationConfig, workersCount, applicationId, index, false)
2432
+ // Create a new worker (use configForNewWorker which may have a pinned port for stopBeforeStart)
2433
+ newWorker = await this.#setupWorker(configForNewWorker, applicationConfig, workersCount, applicationId, index, false)
2393
2434
 
2394
2435
  // Make sure the runtime hasn't been stopped in the meanwhile
2395
2436
  if (this.#status !== 'started') {
@@ -2397,7 +2438,7 @@ export class Runtime extends EventEmitter {
2397
2438
  }
2398
2439
 
2399
2440
  // Add the worker to the mesh
2400
- await this.#startWorker(config, applicationConfig, workersCount, applicationId, index, false, 0, newWorker, true)
2441
+ await this.#startWorker(configForNewWorker, applicationConfig, workersCount, applicationId, index, false, 0, newWorker, true)
2401
2442
 
2402
2443
  // Make sure the runtime hasn't been stopped in the meanwhile
2403
2444
  if (this.#status !== 'started') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "3.44.0",
3
+ "version": "3.46.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -35,14 +35,14 @@
35
35
  "typescript": "^5.5.4",
36
36
  "undici-oidc-interceptor": "^0.5.0",
37
37
  "why-is-node-running": "^2.2.2",
38
- "@platformatic/composer": "3.44.0",
39
- "@platformatic/db": "3.44.0",
40
- "@platformatic/gateway": "3.44.0",
41
- "@platformatic/service": "3.44.0",
42
- "@platformatic/node": "3.44.0",
43
- "@platformatic/sql-graphql": "3.44.0",
44
- "@platformatic/sql-mapper": "3.44.0",
45
- "@platformatic/wattpm-pprof-capture": "3.44.0"
38
+ "@platformatic/composer": "3.46.0",
39
+ "@platformatic/db": "3.46.0",
40
+ "@platformatic/gateway": "3.46.0",
41
+ "@platformatic/sql-graphql": "3.46.0",
42
+ "@platformatic/sql-mapper": "3.46.0",
43
+ "@platformatic/node": "3.46.0",
44
+ "@platformatic/service": "3.46.0",
45
+ "@platformatic/wattpm-pprof-capture": "3.46.0"
46
46
  },
47
47
  "dependencies": {
48
48
  "@fastify/accepts": "^5.0.0",
@@ -72,12 +72,12 @@
72
72
  "undici": "^7.0.0",
73
73
  "undici-thread-interceptor": "^1.3.1",
74
74
  "ws": "^8.16.0",
75
- "@platformatic/basic": "3.44.0",
76
- "@platformatic/foundation": "3.44.0",
77
- "@platformatic/itc": "3.44.0",
78
- "@platformatic/generators": "3.44.0",
79
- "@platformatic/metrics": "3.44.0",
80
- "@platformatic/telemetry": "3.44.0"
75
+ "@platformatic/basic": "3.46.0",
76
+ "@platformatic/foundation": "3.46.0",
77
+ "@platformatic/generators": "3.46.0",
78
+ "@platformatic/itc": "3.46.0",
79
+ "@platformatic/metrics": "3.46.0",
80
+ "@platformatic/telemetry": "3.46.0"
81
81
  },
82
82
  "engines": {
83
83
  "node": ">=22.19.0"
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.44.0.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.46.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Runtime Config",
5
5
  "type": "object",