@platformatic/runtime 3.52.3 → 3.53.0-alpha.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.
Files changed (3) hide show
  1. package/lib/runtime.js +108 -55
  2. package/package.json +15 -15
  3. package/schema.json +1 -1
package/lib/runtime.js CHANGED
@@ -2023,41 +2023,47 @@ export class Runtime extends EventEmitter {
2023
2023
  return
2024
2024
  }
2025
2025
 
2026
- // Iterate through all workers and collect health metrics
2026
+ // Collect health from all workers in parallel so that a slow ITC
2027
+ // round-trip (e.g. subprocess timeout) does not block every other worker.
2028
+ const pending = []
2027
2029
  for (const worker of this.#workers.values()) {
2028
2030
  if (worker[kWorkerStatus] !== 'started') {
2029
2031
  continue
2030
2032
  }
2031
2033
 
2032
- const id = worker[kApplicationId]
2033
- const index = worker[kWorkerId]
2034
- const errorLabel = this.#workerExtendedLabel(id, index, worker[kConfig].workers)
2034
+ pending.push((async () => {
2035
+ const id = worker[kApplicationId]
2036
+ const index = worker[kWorkerId]
2037
+ const errorLabel = this.#workerExtendedLabel(id, index, worker[kConfig].workers)
2035
2038
 
2036
- let health = null
2037
- try {
2038
- health = await this.getWorkerHealth(worker, {
2039
- previousELU: worker[kLastHealthCheckELU]
2040
- })
2041
- } catch (err) {
2042
- this.logger.error({ err }, `Failed to get health for ${errorLabel}.`)
2043
- } finally {
2044
- worker[kLastHealthCheckELU] = health?.currentELU ?? null
2045
- }
2046
-
2047
- const healthSignals = worker[kWorkerHealthSignals]?.getAll() ?? []
2039
+ let health = null
2040
+ try {
2041
+ health = await this.getWorkerHealth(worker, {
2042
+ previousELU: worker[kLastHealthCheckELU]
2043
+ })
2044
+ } catch (err) {
2045
+ this.logger.error({ err }, `Failed to get health for ${errorLabel}.`)
2046
+ } finally {
2047
+ worker[kLastHealthCheckELU] = health?.currentELU ?? null
2048
+ }
2048
2049
 
2049
- // We use emit instead of emitAndNotify to avoid sending a postMessages
2050
- // to each workers even if they are not interested in health metrics.
2051
- // No one of the known capabilities use this event yet.
2052
- this.emit('application:worker:health:metrics', {
2053
- id: worker[kId],
2054
- application: id,
2055
- worker: index,
2056
- currentHealth: health,
2057
- healthSignals
2058
- })
2050
+ const healthSignals = worker[kWorkerHealthSignals]?.getAll() ?? []
2051
+
2052
+ // We use emit instead of emitAndNotify to avoid sending a postMessages
2053
+ // to each workers even if they are not interested in health metrics.
2054
+ // No one of the known capabilities use this event yet.
2055
+ this.emit('application:worker:health:metrics', {
2056
+ id: worker[kId],
2057
+ application: id,
2058
+ worker: index,
2059
+ currentHealth: health,
2060
+ healthSignals
2061
+ })
2062
+ })())
2059
2063
  }
2060
2064
 
2065
+ await Promise.allSettled(pending)
2066
+
2061
2067
  // Reschedule the next check. We are not using .refresh() because it's more
2062
2068
  // expensive (weird).
2063
2069
  this.#healthMetricsTimer = setTimeout(collectHealthMetrics, 1000).unref()
@@ -2109,53 +2115,45 @@ export class Runtime extends EventEmitter {
2109
2115
  worker[kHealthCheckTimer] = setTimeout(async () => {
2110
2116
  if (worker[kWorkerStatus] !== 'started') return
2111
2117
 
2112
- if (lastHealthMetrics) {
2113
- const health = lastHealthMetrics.currentHealth
2114
- const memoryUsage = health.heapUsed / maxHeapTotal
2115
- const unhealthy = health.elu > maxELU || memoryUsage > maxHeapUsed
2118
+ // No health data received yet — reschedule and wait.
2119
+ if (!lastHealthMetrics) {
2120
+ worker[kHealthCheckTimer].refresh()
2121
+ return
2122
+ }
2123
+
2124
+ const health = lastHealthMetrics.currentHealth
2125
+
2126
+ // When health collection failed (threw) or timed out, currentHealth is
2127
+ // null. Treat this as an unhealthy check so that a stuck worker that
2128
+ // cannot even report its own health is eventually replaced.
2129
+ if (!health) {
2130
+ unhealthyChecks++
2131
+
2132
+ this.logger.error(
2133
+ `Health collection failed for the ${errorLabel}. ` +
2134
+ `Unhealthy check ${unhealthyChecks}/${maxUnhealthyChecks}.`
2135
+ )
2116
2136
 
2117
2137
  this.emit('application:worker:health', {
2118
2138
  id: worker[kId],
2119
2139
  application: id,
2120
2140
  worker: index,
2121
- currentHealth: health,
2122
- unhealthy,
2141
+ currentHealth: null,
2142
+ unhealthy: true,
2123
2143
  healthConfig
2124
2144
  })
2125
2145
 
2126
- if (health.elu > maxELU) {
2127
- this.logger.error(
2128
- `The ${errorLabel} has an ELU of ${(health.elu * 100).toFixed(2)} %, ` +
2129
- `above the maximum allowed usage of ${(maxELU * 100).toFixed(2)} %.`
2130
- )
2131
- }
2132
-
2133
- if (memoryUsage > maxHeapUsed) {
2134
- this.logger.error(
2135
- `The ${errorLabel} is using ${(memoryUsage * 100).toFixed(2)} % of the memory, ` +
2136
- `above the maximum allowed usage of ${(maxHeapUsed * 100).toFixed(2)} %.`
2137
- )
2138
- }
2139
-
2140
- if (unhealthy) {
2141
- unhealthyChecks++
2142
- } else {
2143
- unhealthyChecks = 0
2144
- }
2145
-
2146
2146
  if (unhealthyChecks === maxUnhealthyChecks) {
2147
2147
  try {
2148
2148
  this.emitAndNotify('application:worker:unhealthy', { application: id, worker: index })
2149
2149
 
2150
2150
  this.logger.error(
2151
- { elu: health.elu, maxELU, memoryUsage: health.heapUsed, maxMemoryUsage: maxHeapUsed },
2152
- `The ${errorLabel} is unhealthy. Replacing it ...`
2151
+ `The ${errorLabel} is unhealthy (health collection failed). Replacing it ...`
2153
2152
  )
2154
2153
 
2155
2154
  await this.#replaceWorker(config, applicationConfig, workersCount, id, index, worker)
2156
2155
  } catch (e) {
2157
2156
  this.logger.error(
2158
- { elu: health.elu, maxELU, memoryUsage: health.heapUsed, maxMemoryUsage: maxHeapUsed },
2159
2157
  `Cannot replace the ${errorLabel}. Forcefully terminating it ...`
2160
2158
  )
2161
2159
 
@@ -2164,6 +2162,61 @@ export class Runtime extends EventEmitter {
2164
2162
  } else {
2165
2163
  worker[kHealthCheckTimer].refresh()
2166
2164
  }
2165
+ return
2166
+ }
2167
+
2168
+ const memoryUsage = health.heapUsed != null ? health.heapUsed / maxHeapTotal : 0
2169
+ const unhealthy = health.elu > maxELU || memoryUsage > maxHeapUsed
2170
+
2171
+ this.emit('application:worker:health', {
2172
+ id: worker[kId],
2173
+ application: id,
2174
+ worker: index,
2175
+ currentHealth: health,
2176
+ unhealthy,
2177
+ healthConfig
2178
+ })
2179
+
2180
+ if (health.elu > maxELU) {
2181
+ this.logger.error(
2182
+ `The ${errorLabel} has an ELU of ${(health.elu * 100).toFixed(2)} %, ` +
2183
+ `above the maximum allowed usage of ${(maxELU * 100).toFixed(2)} %.`
2184
+ )
2185
+ }
2186
+
2187
+ if (memoryUsage > maxHeapUsed) {
2188
+ this.logger.error(
2189
+ `The ${errorLabel} is using ${(memoryUsage * 100).toFixed(2)} % of the memory, ` +
2190
+ `above the maximum allowed usage of ${(maxHeapUsed * 100).toFixed(2)} %.`
2191
+ )
2192
+ }
2193
+
2194
+ if (unhealthy) {
2195
+ unhealthyChecks++
2196
+ } else {
2197
+ unhealthyChecks = 0
2198
+ }
2199
+
2200
+ if (unhealthyChecks === maxUnhealthyChecks) {
2201
+ try {
2202
+ this.emitAndNotify('application:worker:unhealthy', { application: id, worker: index })
2203
+
2204
+ this.logger.error(
2205
+ { elu: health.elu, maxELU, memoryUsage: health.heapUsed, maxMemoryUsage: maxHeapUsed },
2206
+ `The ${errorLabel} is unhealthy. Replacing it ...`
2207
+ )
2208
+
2209
+ await this.#replaceWorker(config, applicationConfig, workersCount, id, index, worker)
2210
+ } catch (e) {
2211
+ this.logger.error(
2212
+ { elu: health.elu, maxELU, memoryUsage: health.heapUsed, maxMemoryUsage: maxHeapUsed },
2213
+ `Cannot replace the ${errorLabel}. Forcefully terminating it ...`
2214
+ )
2215
+
2216
+ worker.terminate()
2217
+ }
2218
+ } else {
2219
+ worker[kHealthCheckTimer].refresh()
2167
2220
  }
2168
2221
  }, interval).unref()
2169
2222
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "3.52.3",
3
+ "version": "3.53.0-alpha.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -36,14 +36,14 @@
36
36
  "typescript": "^5.5.4",
37
37
  "undici-oidc-interceptor": "^0.5.0",
38
38
  "why-is-node-running": "^2.2.2",
39
- "@platformatic/composer": "3.52.3",
40
- "@platformatic/db": "3.52.3",
41
- "@platformatic/gateway": "3.52.3",
42
- "@platformatic/node": "3.52.3",
43
- "@platformatic/service": "3.52.3",
44
- "@platformatic/sql-mapper": "3.52.3",
45
- "@platformatic/sql-graphql": "3.52.3",
46
- "@platformatic/wattpm-pprof-capture": "3.52.3"
39
+ "@platformatic/composer": "3.53.0-alpha.0",
40
+ "@platformatic/gateway": "3.53.0-alpha.0",
41
+ "@platformatic/db": "3.53.0-alpha.0",
42
+ "@platformatic/service": "3.53.0-alpha.0",
43
+ "@platformatic/sql-graphql": "3.53.0-alpha.0",
44
+ "@platformatic/node": "3.53.0-alpha.0",
45
+ "@platformatic/sql-mapper": "3.53.0-alpha.0",
46
+ "@platformatic/wattpm-pprof-capture": "3.53.0-alpha.0"
47
47
  },
48
48
  "dependencies": {
49
49
  "@fastify/accepts": "^5.0.0",
@@ -73,12 +73,12 @@
73
73
  "undici": "^7.0.0",
74
74
  "undici-thread-interceptor": "^1.3.1",
75
75
  "ws": "^8.16.0",
76
- "@platformatic/basic": "3.52.3",
77
- "@platformatic/foundation": "3.52.3",
78
- "@platformatic/generators": "3.52.3",
79
- "@platformatic/metrics": "3.52.3",
80
- "@platformatic/telemetry": "3.52.3",
81
- "@platformatic/itc": "3.52.3"
76
+ "@platformatic/foundation": "3.53.0-alpha.0",
77
+ "@platformatic/basic": "3.53.0-alpha.0",
78
+ "@platformatic/generators": "3.53.0-alpha.0",
79
+ "@platformatic/metrics": "3.53.0-alpha.0",
80
+ "@platformatic/itc": "3.53.0-alpha.0",
81
+ "@platformatic/telemetry": "3.53.0-alpha.0"
82
82
  },
83
83
  "engines": {
84
84
  "node": ">=22.19.0"
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.52.3.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.53.0-alpha.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Runtime Config",
5
5
  "type": "object",