@colyseus/tools 0.16.16 → 0.16.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colyseus/tools",
3
- "version": "0.16.16",
3
+ "version": "0.16.17",
4
4
  "description": "Simplify the development and production settings for your Colyseus project.",
5
5
  "input": "./src/index.ts",
6
6
  "main": "./build/index.js",
@@ -49,13 +49,15 @@
49
49
  "devDependencies": {
50
50
  "@types/cors": "^2.8.10",
51
51
  "@types/dotenv": "^8.2.0",
52
+ "pm2": "^6.0.14",
53
+ "mocha": "^11.7.5",
52
54
  "uwebsockets-express": "^1.1.10",
53
- "@colyseus/redis-presence": "^0.16.4",
54
- "@colyseus/redis-driver": "^0.16.1",
55
- "@colyseus/core": "^0.16.24",
56
55
  "@colyseus/bun-websockets": "^0.16.5",
57
- "@colyseus/uwebsockets-transport": "^0.16.10",
58
- "@colyseus/ws-transport": "^0.16.5"
56
+ "@colyseus/core": "^0.16.24",
57
+ "@colyseus/ws-transport": "^0.16.5",
58
+ "@colyseus/redis-driver": "^0.16.1",
59
+ "@colyseus/redis-presence": "^0.16.4",
60
+ "@colyseus/uwebsockets-transport": "^0.16.10"
59
61
  },
60
62
  "peerDependencies": {
61
63
  "@colyseus/core": "0.16.x",
@@ -73,6 +75,7 @@
73
75
  }
74
76
  ],
75
77
  "scripts": {
76
- "start": "tsx example/app.ts"
78
+ "start": "tsx example/app.ts",
79
+ "test": "mocha --require tsx test/pm2-deployment.test.ts"
77
80
  }
78
81
  }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Monkey-patch for PM2's ActionMethods.js
3
+ * Adds custom methods like updateProcessConfig without modifying the original file.
4
+ */
5
+ 'use strict';
6
+
7
+ const Utility = require('../Utility');
8
+
9
+ // Import the original ActionMethods module
10
+ const originalActionMethods = require('./ActionMethods_orig.js');
11
+
12
+ module.exports = function(God) {
13
+ // Apply original ActionMethods
14
+ originalActionMethods(God);
15
+
16
+ /**
17
+ * Update process configuration without restart.
18
+ * Merges opts.env.current_conf into proc.pm2_env.
19
+ * @method updateProcessConfig
20
+ * @param {Object} opts - { id: pm_id, env: { current_conf: { ... } } }
21
+ * @param {Function} cb
22
+ */
23
+ God.updateProcessConfig = function updateProcessConfig(opts, cb) {
24
+ var id = opts.id;
25
+
26
+ if (typeof(id) === 'undefined')
27
+ return cb(God.logAndGenerateError('opts.id not passed to updateProcessConfig', opts));
28
+ if (!(id in God.clusters_db))
29
+ return cb(God.logAndGenerateError('Process id unknown'), {});
30
+
31
+ const proc = God.clusters_db[id];
32
+
33
+ if (!proc || !proc.pm2_env)
34
+ return cb(God.logAndGenerateError('Process not found or missing pm2_env'), {});
35
+
36
+ // Merge env into pm2_env.env
37
+ if (opts.env) {
38
+ Utility.extend(proc.pm2_env, opts.env);
39
+ Utility.extend(proc.pm2_env.env, opts.env); // why both?
40
+ }
41
+
42
+ // Merge current_conf directly into pm2_env
43
+ Utility.extendExtraConfig(proc, opts);
44
+
45
+ return cb(null, Utility.clone(proc.pm2_env));
46
+ };
47
+ };
@@ -14,8 +14,7 @@ const io = require('@pm2/io');
14
14
  const path = require('path');
15
15
  const shared = require('./shared');
16
16
 
17
- const opts = { env: process.env.NODE_ENV || "production", };
18
- let config = undefined;
17
+ let appConfig = undefined;
19
18
 
20
19
  io.initModule({
21
20
  pid: path.resolve('/var/run/colyseus-agent.pid'),
@@ -52,9 +51,11 @@ pm2.connect(function(err) {
52
51
  }
53
52
 
54
53
  try {
55
- config = await shared.getAppConfig(ecosystemFilePath);
56
- opts.cwd = cwd;
57
- postDeploy(cwd, onReply);
54
+ const config = await shared.getAppConfig(ecosystemFilePath);
55
+
56
+ appConfig = { ...config.apps[0], cwd };
57
+
58
+ postDeploy(appConfig, onReply);
58
59
 
59
60
  } catch (err) {
60
61
  onReply({ success: false, message: err?.message });
@@ -64,7 +65,7 @@ pm2.connect(function(err) {
64
65
 
65
66
  const restartingAppIds = new Set();
66
67
 
67
- function postDeploy(cwd, reply) {
68
+ function postDeploy(config, reply) {
68
69
  shared.listApps(function(err, apps) {
69
70
  if (err) {
70
71
  console.error(err);
@@ -73,7 +74,8 @@ function postDeploy(cwd, reply) {
73
74
 
74
75
  // first deploy, start all processes
75
76
  if (apps.length === 0) {
76
- return pm2.start(config, {...opts}, (err, result) => {
77
+ console.log("First deploy detected, starting all processes... pm2.start(config) => ", config);
78
+ return pm2.start(config, (err, result) => {
77
79
  reply({ success: !err, message: err?.message });
78
80
  updateAndSave(err, result);
79
81
  });
@@ -82,7 +84,7 @@ function postDeploy(cwd, reply) {
82
84
  //
83
85
  // detect if cwd has changed, and restart PM2 if it has
84
86
  //
85
- if (apps[0].pm2_env.pm_cwd !== cwd) {
87
+ if (apps[0].pm2_env.pm_cwd !== config.cwd) {
86
88
  console.log("App Root Directory changed. Restarting may take a bit longer...");
87
89
 
88
90
  //
@@ -92,7 +94,7 @@ function postDeploy(cwd, reply) {
92
94
  logIfError(err);
93
95
 
94
96
  // start again
95
- pm2.start(config, { ...opts }, (err, result) => {
97
+ pm2.start(config, (err, result) => {
96
98
  reply({ success: !err, message: err?.message });
97
99
  updateAndSave(err, result);
98
100
  });
@@ -140,6 +142,20 @@ function postDeploy(cwd, reply) {
140
142
  if (err) { return console.error(err); }
141
143
 
142
144
  let numActiveApps = initialApps.length + restartingAppIds.size;
145
+ const maxActiveProcesses = config.instances;
146
+
147
+ // update config of newly spawned processes (memory limit, etc)
148
+ shared.listApps(function(err, apps) {
149
+ logIfError(err);
150
+
151
+ // get new processes excluding initialApps
152
+ const new_app_envs = shared.filterActiveApps(apps)
153
+ .map((app) => app.pm2_env)
154
+ .filter(app_env => !initialApps.find(init_app => init_app.pm_id === app_env.pm_id));
155
+
156
+ new_app_envs.forEach((app_env) =>
157
+ shared.updateProcessConfig(app_env.pm_id, appConfig, logIfError));
158
+ });
143
159
 
144
160
  /**
145
161
  * - Write NGINX config to expose only the new active process
@@ -157,7 +173,7 @@ function postDeploy(cwd, reply) {
157
173
  // (They make take from minutes up to hours to stop)
158
174
  //
159
175
  appsToStop.forEach((app_env) => {
160
- if (numActiveApps < shared.MAX_ACTIVE_PROCESSES) {
176
+ if (numActiveApps < maxActiveProcesses) {
161
177
  numActiveApps++;
162
178
 
163
179
  restartingAppIds.add(app_env.pm_id);
@@ -167,6 +183,9 @@ function postDeploy(cwd, reply) {
167
183
 
168
184
  // reset counter stats (restart_time=0)
169
185
  pm2.reset(app_env.pm_id, logIfError);
186
+
187
+ // update process config (memory limit, etc)
188
+ shared.updateProcessConfig(app_env.pm_id, config, logIfError);
170
189
  });
171
190
 
172
191
  } else {
@@ -174,8 +193,8 @@ function postDeploy(cwd, reply) {
174
193
  }
175
194
  });
176
195
 
177
- if (numActiveApps < shared.MAX_ACTIVE_PROCESSES) {
178
- const missingOnlineApps = shared.MAX_ACTIVE_PROCESSES - numActiveApps;
196
+ if (numActiveApps < maxActiveProcesses) {
197
+ const missingOnlineApps = maxActiveProcesses - numActiveApps;
179
198
 
180
199
  // console.log("Active apps is lower than MAX_ACTIVE_PROCESSES, will SCALE again =>", {
181
200
  // missingOnlineApps,
@@ -183,11 +202,12 @@ function postDeploy(cwd, reply) {
183
202
  // newNumTotalApps: numTotalApps + missingOnlineApps
184
203
  // });
185
204
 
205
+ console.log("Scale up to", numTotalApps + missingOnlineApps);
186
206
  pm2.scale(apps[0].name, numTotalApps + missingOnlineApps, updateAndSaveIfAllRunning);
187
207
  }
188
208
  };
189
209
 
190
- const numHalfMaxActiveProcesses = Math.ceil(shared.MAX_ACTIVE_PROCESSES / 2);
210
+ const numHalfMaxActiveProcesses = Math.ceil(config.instances / 2);
191
211
 
192
212
  /**
193
213
  * Re-use previously stopped apps if available
@@ -206,6 +226,7 @@ function postDeploy(cwd, reply) {
206
226
 
207
227
  // reset counter stats (restart_time=0)
208
228
  pm2.reset(app_env.pm_id, logIfError);
229
+ shared.updateProcessConfig(app_env.pm_id, config, logIfError);
209
230
 
210
231
  // TODO: set timeout here to exit if some processes are not restarting
211
232
 
@@ -229,6 +250,8 @@ function postDeploy(cwd, reply) {
229
250
 
230
251
  numTotalApps = apps.length + numHalfMaxActiveProcesses;
231
252
 
253
+ console.log("Starting first half of new apps... pm2.scale(app, ", numTotalApps, ")");
254
+
232
255
  // Ensure to scale to a number of processes where `numHalfMaxActiveProcesses` can start immediately.
233
256
  pm2.scale(apps[0].name, numTotalApps, onFirstAppsStart.bind(undefined, initialApps));
234
257
  }
@@ -244,12 +267,12 @@ function updateAndSaveIfAllRunning(err) {
244
267
  if (err) { return console.error(err); }
245
268
 
246
269
  updateAndReloadNginx((app_envs) => {
247
- // console.log("updateAndExitIfAllRunning, app_ids (", app_envs.map(app_env => app_env.NODE_APP_INSTANCE) ,") => ", app_envs.length, "/", shared.MAX_ACTIVE_PROCESSES);
270
+ // console.log("updateAndExitIfAllRunning, app_ids (", app_envs.map(app_env => app_env.NODE_APP_INSTANCE) ,") => ", app_envs.length, "/", config.instances);
248
271
 
249
272
  //
250
273
  // TODO: add timeout to exit here, in case some processes are not starting
251
274
  //
252
- if (app_envs.length === shared.MAX_ACTIVE_PROCESSES) {
275
+ if (app_envs.length === appConfig.instances) {
253
276
  complete();
254
277
  }
255
278
  });
@@ -272,18 +295,24 @@ function updateAndReloadNginx(cb) {
272
295
  if (apps.length === 0) { err = "no apps running."; }
273
296
  if (err) { return console.error(err); }
274
297
 
275
- const app_envs = apps
276
- .filter(app => app.pm2_env.status !== cst.STOPPING_STATUS && app.pm2_env.status !== cst.STOPPED_STATUS)
277
- .map((app) => app.pm2_env);
298
+ const app_envs = shared.filterActiveApps(apps).map((app) => app.pm2_env);
278
299
 
279
300
  writeNginxConfig(app_envs);
280
301
 
302
+ // update processes config (memory limit, etc)
303
+ app_envs.forEach((app_env) =>
304
+ shared.updateProcessConfig(app_env.pm_id, appConfig, logIfError));
305
+
281
306
  cb?.(app_envs);
282
307
  });
283
308
  }
284
309
 
285
310
  function writeNginxConfig(app_envs) {
286
311
  // console.log("writeNginxConfig: ", app_envs.map(app_env => app_env.NODE_APP_INSTANCE));
312
+ if (!fs.existsSync(shared.NGINX_SERVERS_CONFIG_FILE)) {
313
+ console.warn(`NGINX config file not found at ${shared.NGINX_SERVERS_CONFIG_FILE}, skipping NGINX config update.`);
314
+ return;
315
+ }
287
316
 
288
317
  const port = 2567;
289
318
  const addresses = [];
package/pm2/shared.js CHANGED
@@ -1,12 +1,25 @@
1
1
  const pm2 = require('pm2');
2
+ const cst = require('pm2/constants');
2
3
  const os = require('os');
3
4
 
4
5
  const NAMESPACE = 'cloud';
5
- const MAX_ACTIVE_PROCESSES = os.cpus().length;
6
+ const MAX_ACTIVE_PROCESSES = Number(process.env.MAX_ACTIVE_PROCESSES || os.cpus().length);
7
+
8
+ const CONFIG_KEYS = [
9
+ 'max_memory_restart',
10
+ 'kill_timeout',
11
+ 'kill_retry_time',
12
+ 'wait_ready',
13
+ 'merge_logs',
14
+ 'cron_restart',
15
+ 'autorestart',
16
+ 'exp_backoff_restart_delay',
17
+ 'restart_delay',
18
+ ];
6
19
 
7
20
  function listApps(callback) {
8
21
  pm2.list((err, apps) => {
9
- if (err) { return callback(err);; }
22
+ if (err) { return callback(err); }
10
23
 
11
24
  // Filter out @colyseus/tools module (PM2 post-deploy agent)
12
25
  apps = apps.filter(app => app.name !== '@colyseus/tools');
@@ -16,8 +29,10 @@ function listApps(callback) {
16
29
  }
17
30
 
18
31
  async function getAppConfig(ecosystemFilePath) {
19
- const module = await import(ecosystemFilePath);
20
- const config = module.default;
32
+ // Clear require cache to force reload of the config file
33
+ const resolvedPath = require.resolve(ecosystemFilePath);
34
+ delete require.cache[resolvedPath];
35
+ const config = require(ecosystemFilePath);
21
36
 
22
37
  /**
23
38
  * Tune PM2 app config
@@ -29,10 +44,16 @@ async function getAppConfig(ecosystemFilePath) {
29
44
  app.namespace = NAMESPACE;
30
45
  app.exec_mode = "fork";
31
46
 
47
+ // default: number of CPU cores
32
48
  if (app.instances === undefined) {
33
49
  app.instances = MAX_ACTIVE_PROCESSES;
34
50
  }
35
51
 
52
+ // default: restart if memory exceeds 512M
53
+ if (app.max_memory_restart === undefined) {
54
+ app.max_memory_restart = '512M';
55
+ }
56
+
36
57
  app.time = true;
37
58
  app.wait_ready = true;
38
59
  app.watch = false;
@@ -52,17 +73,74 @@ async function getAppConfig(ecosystemFilePath) {
52
73
  if (!app.kill_retry_time) {
53
74
  app.kill_retry_time = 5000;
54
75
  }
76
+
77
+ // Ensure these config values are also set in app.env so they take priority
78
+ // over inherited environment variables during PM2's Utility.extend() merge.
79
+ // This prevents the parent process's environment (e.g., @colyseus/tools agent)
80
+ // from overwriting the app's config values like max_memory_restart.
81
+ if (!app.env) {
82
+ app.env = {};
83
+ }
84
+
85
+ CONFIG_KEYS.forEach((key) => {
86
+ if (app[key] !== undefined) {
87
+ app.env[key] = convertValue(app[key]);
88
+ }
89
+ });
55
90
  }
56
91
 
57
92
  return config;
58
93
  }
59
94
 
95
+ function convertValue(value) {
96
+ if (typeof value === 'string') {
97
+ const conversionUnit = {
98
+ // bytes
99
+ 'G': 1024 * 1024 * 1024,
100
+ 'M': 1024 * 1024,
101
+ 'K': 1024,
102
+ // milliseconds
103
+ 'h': 60 * 60 * 1000,
104
+ 'm': 60 * 1000,
105
+ 's': 1000
106
+ };
107
+
108
+ if (!conversionUnit[value.slice(-1)]) {
109
+ return parseInt(value, 10);
110
+
111
+ } else {
112
+ return parseFloat(value.slice(0, -1)) * (conversionUnit[value.slice(-1)]);
113
+ }
114
+ }
115
+ return value;
116
+ }
117
+
118
+ /**
119
+ * Update process configuration without restart.
120
+ * This patches pm2_env directly via a custom God method.
121
+ * @param {number|string} pm_id - Process ID to update
122
+ * @param {Object} config - Configuration object (e.g., { max_memory_restart: '512M', kill_timeout: 30000 })
123
+ * @param {Function} cb - Callback(err, updatedEnv)
124
+ */
125
+ function updateProcessConfig(pm_id, config, cb) {
126
+ const env = {};
127
+
128
+ CONFIG_KEYS.forEach((key) => {
129
+ if (config[key] !== undefined) {
130
+ env[key] = convertValue(config[key]);
131
+ }
132
+ });
133
+
134
+ const opts = { id: pm_id, env, };
135
+ pm2.Client.executeRemote('updateProcessConfig', opts, cb);
136
+ }
137
+
60
138
  module.exports = {
61
139
  /**
62
140
  * Constants
63
141
  */
64
- NGINX_SERVERS_CONFIG_FILE: '/etc/nginx/colyseus_servers.conf',
65
- PROCESS_UNIX_SOCK_PATH: '/run/colyseus/',
142
+ NGINX_SERVERS_CONFIG_FILE: process.env.NGINX_CONFIG_FILE || '/etc/nginx/colyseus_servers.conf',
143
+ PROCESS_UNIX_SOCK_PATH: process.env.UNIX_SOCK_PATH || '/run/colyseus/',
66
144
 
67
145
  MAX_ACTIVE_PROCESSES,
68
146
  NAMESPACE,
@@ -72,4 +150,11 @@ module.exports = {
72
150
  */
73
151
  listApps,
74
152
  getAppConfig,
153
+
154
+ updateProcessConfig,
155
+
156
+ filterActiveApps: (apps) => apps.filter(app =>
157
+ app.pm2_env.status !== cst.STOPPING_STATUS &&
158
+ app.pm2_env.status !== cst.STOPPED_STATUS
159
+ ),
75
160
  }
package/post-deploy.js CHANGED
@@ -90,7 +90,11 @@ function restartAll () {
90
90
  });
91
91
  }
92
92
 
93
- function reloadAll(retry = 0) {
93
+ async function reloadAll(retry = 0) {
94
+ // Read user's ecosystem config to get the desired number of instances
95
+ const config = await shared.getAppConfig(CONFIG_FILE_PATH);
96
+ const desiredInstances = config.apps?.[0]?.instances || shared.MAX_ACTIVE_PROCESSES;
97
+
94
98
  pm2.reload(CONFIG_FILE_PATH, { ...opts }, function (err, apps) {
95
99
  if (err) {
96
100
  //
@@ -110,9 +114,9 @@ function reloadAll(retry = 0) {
110
114
  const name = apps[0].name;
111
115
  const reloadedAppIds = apps.map(app => app.pm_id);
112
116
 
113
- // scale app to use all CPUs available
114
- if (apps.length !== shared.MAX_ACTIVE_PROCESSES) {
115
- pm2.scale(name, shared.MAX_ACTIVE_PROCESSES, () => onAppRunning(reloadedAppIds));
117
+ // scale app to the number of instances specified in user's config
118
+ if (apps.length !== desiredInstances) {
119
+ pm2.scale(name, desiredInstances, () => onAppRunning(reloadedAppIds));
116
120
 
117
121
  } else {
118
122
  onAppRunning(reloadedAppIds);
package/system-boot.js CHANGED
@@ -2,8 +2,88 @@
2
2
 
3
3
  const os = require('os');
4
4
  const fs = require('fs');
5
+ const path = require('path');
5
6
  const { execSync } = require('child_process');
6
7
 
8
+ /**
9
+ * Apply PM2 monkey-patch for custom functionality (e.g., updateProcessConfig)
10
+ * This patches the globally installed PM2 used by the system.
11
+ *
12
+ * Strategy: Rename ActionMethods.js -> ActionMethods_orig.js and replace with
13
+ * our wrapper that imports the original and adds custom methods.
14
+ */
15
+ function applyPM2Patches() {
16
+ try {
17
+ // Find global PM2 installation
18
+ const globalPM2Path = execSync('npm root -g', { encoding: 'utf8' }).trim() + '/pm2';
19
+
20
+ if (!fs.existsSync(globalPM2Path)) {
21
+ console.log('[PM2 Patch] Global PM2 not found, skipping...');
22
+ return;
23
+ }
24
+
25
+ const godPath = path.join(globalPM2Path, 'lib/God');
26
+ const actionMethodsPath = path.join(godPath, 'ActionMethods.js');
27
+ const actionMethodsOrigPath = path.join(godPath, 'ActionMethods_orig.js');
28
+ const ourActionMethodsPath = path.join(__dirname, 'pm2', 'ActionMethods.js');
29
+
30
+ // Check if our patch file exists
31
+ if (!fs.existsSync(ourActionMethodsPath)) {
32
+ console.log('[PM2 Patch] Custom ActionMethods.js not found, skipping...');
33
+ return;
34
+ }
35
+
36
+ // Check if already patched (ActionMethods_orig.js exists)
37
+ if (!fs.existsSync(actionMethodsOrigPath)) {
38
+ // Rename original ActionMethods.js -> ActionMethods_orig.js
39
+ console.log('[PM2 Patch] Renaming original ActionMethods.js -> ActionMethods_orig.js');
40
+ fs.renameSync(actionMethodsPath, actionMethodsOrigPath);
41
+
42
+ } else {
43
+ console.log('[PM2 Patch] Already patched, skipping...');
44
+ }
45
+
46
+ // Copy our ActionMethods.js wrapper
47
+ console.log(`[PM2 Patch] Installing custom ActionMethods.js wrapper (${actionMethodsPath})`);
48
+ fs.copyFileSync(ourActionMethodsPath, actionMethodsPath);
49
+
50
+ // Patch Daemon.js to expose "updateProcessConfig" method
51
+ const daemonPath = path.join(globalPM2Path, 'lib/Daemon.js');
52
+ console.log(`[PM2 Patch] Patching Daemon.js to expose updateProcessConfig (${daemonPath})`);
53
+ if (fs.existsSync(daemonPath)) {
54
+ let daemonContent = fs.readFileSync(daemonPath, 'utf8');
55
+
56
+ // Check if updateProcessConfig is already exposed
57
+ if (!daemonContent.includes('updateProcessConfig')) {
58
+ // Find server.expose({ and add updateProcessConfig after it
59
+ const exposePattern = 'server.expose({';
60
+ const exposeIndex = daemonContent.indexOf(exposePattern);
61
+
62
+ if (exposeIndex !== -1) {
63
+ const insertPos = exposeIndex + exposePattern.length;
64
+ daemonContent = daemonContent.slice(0, insertPos) +
65
+ '\n updateProcessConfig : God.updateProcessConfig,' +
66
+ daemonContent.slice(insertPos);
67
+ fs.writeFileSync(daemonPath, daemonContent);
68
+ console.log(`[PM2 Patch] Daemon.js patched successfully.`);
69
+ } else {
70
+ console.warn('[PM2 Patch] Could not find server.expose pattern in Daemon.js');
71
+ }
72
+ } else {
73
+ console.log('[PM2 Patch] Daemon.js already has updateProcessConfig exposed.');
74
+ }
75
+ } else {
76
+ console.warn('[PM2 Patch] Daemon.js not found at', daemonPath);
77
+ }
78
+
79
+ console.log('[PM2 Patch] PM2 patches applied successfully.');
80
+
81
+ } catch (error) {
82
+ console.warn('[PM2 Patch] Failed to apply PM2 patches:', error.message);
83
+ // Don't exit - patches are optional enhancements
84
+ }
85
+ }
86
+
7
87
  const NGINX_LIMITS_CONFIG_FILE = '/etc/nginx/colyseus_limits.conf';
8
88
  const LIMITS_CONF_FILE = '/etc/security/limits.d/colyseus.conf';
9
89
  const SYSCTL_FILE = '/etc/sysctl.d/local.conf';
@@ -117,4 +197,7 @@ net.ipv4.tcp_keepalive_probes = 3
117
197
  }
118
198
  }
119
199
 
200
+ // Apply PM2 patches before configuring system limits
201
+ applyPM2Patches();
202
+
120
203
  configureSystemLimits();