1'use strict'; 2 3// This files contains process bootstrappers that can be 4// run when setting up each thread, including the main 5// thread and the worker threads. 6 7const { 8 ArrayPrototypeEvery, 9 ArrayPrototypeForEach, 10 ArrayPrototypeIncludes, 11 ArrayPrototypeMap, 12 ArrayPrototypePush, 13 ArrayPrototypeSplice, 14 BigUint64Array, 15 Float64Array, 16 NumberMAX_SAFE_INTEGER, 17 ObjectFreeze, 18 ObjectDefineProperty, 19 ReflectApply, 20 RegExpPrototypeExec, 21 SafeArrayIterator, 22 Set, 23 SetPrototypeEntries, 24 SetPrototypeValues, 25 StringPrototypeEndsWith, 26 StringPrototypeReplace, 27 StringPrototypeSlice, 28 StringPrototypeStartsWith, 29 Symbol, 30 SymbolIterator, 31 Uint32Array, 32} = primordials; 33 34const { 35 errnoException, 36 codes: { 37 ERR_ASSERTION, 38 ERR_INVALID_ARG_TYPE, 39 ERR_INVALID_ARG_VALUE, 40 ERR_OUT_OF_RANGE, 41 ERR_UNKNOWN_SIGNAL, 42 }, 43} = require('internal/errors'); 44const format = require('internal/util/inspect').format; 45const { 46 validateArray, 47 validateNumber, 48 validateObject, 49} = require('internal/validators'); 50const constants = internalBinding('constants').os.signals; 51 52const { 53 handleProcessExit, 54} = require('internal/modules/esm/handle_process_exit'); 55 56const kInternal = Symbol('internal properties'); 57 58function assert(x, msg) { 59 if (!x) throw new ERR_ASSERTION(msg || 'assertion error'); 60} 61 62const binding = internalBinding('process_methods'); 63 64let hrValues; 65let hrBigintValues; 66 67function refreshHrtimeBuffer() { 68 // The 3 entries filled in by the original process.hrtime contains 69 // the upper/lower 32 bits of the second part of the value, 70 // and the remaining nanoseconds of the value. 71 hrValues = new Uint32Array(binding.hrtimeBuffer); 72 // Use a BigUint64Array in the closure because this is actually a bit 73 // faster than simply returning a BigInt from C++ in V8 7.1. 74 hrBigintValues = new BigUint64Array(binding.hrtimeBuffer, 0, 1); 75} 76 77// Create the buffers. 78refreshHrtimeBuffer(); 79 80function hrtime(time) { 81 binding.hrtime(); 82 83 if (time !== undefined) { 84 validateArray(time, 'time'); 85 if (time.length !== 2) { 86 throw new ERR_OUT_OF_RANGE('time', 2, time.length); 87 } 88 89 const sec = (hrValues[0] * 0x100000000 + hrValues[1]) - time[0]; 90 const nsec = hrValues[2] - time[1]; 91 const needsBorrow = nsec < 0; 92 return [needsBorrow ? sec - 1 : sec, needsBorrow ? nsec + 1e9 : nsec]; 93 } 94 95 return [ 96 hrValues[0] * 0x100000000 + hrValues[1], 97 hrValues[2], 98 ]; 99} 100 101function hrtimeBigInt() { 102 binding.hrtimeBigInt(); 103 return hrBigintValues[0]; 104} 105 106function nop() {} 107 108// The execution of this function itself should not cause any side effects. 109function wrapProcessMethods(binding) { 110 const { 111 cpuUsage: _cpuUsage, 112 memoryUsage: _memoryUsage, 113 rss, 114 resourceUsage: _resourceUsage, 115 } = binding; 116 117 function _rawDebug(...args) { 118 binding._rawDebug(ReflectApply(format, null, args)); 119 } 120 121 // Create the argument array that will be passed to the native function. 122 const cpuValues = new Float64Array(2); 123 124 // Replace the native function with the JS version that calls the native 125 // function. 126 function cpuUsage(prevValue) { 127 // If a previous value was passed in, ensure it has the correct shape. 128 if (prevValue) { 129 if (!previousValueIsValid(prevValue.user)) { 130 validateObject(prevValue, 'prevValue'); 131 132 validateNumber(prevValue.user, 'prevValue.user'); 133 throw new ERR_INVALID_ARG_VALUE.RangeError('prevValue.user', 134 prevValue.user); 135 } 136 137 if (!previousValueIsValid(prevValue.system)) { 138 validateNumber(prevValue.system, 'prevValue.system'); 139 throw new ERR_INVALID_ARG_VALUE.RangeError('prevValue.system', 140 prevValue.system); 141 } 142 } 143 144 // Call the native function to get the current values. 145 _cpuUsage(cpuValues); 146 147 // If a previous value was passed in, return diff of current from previous. 148 if (prevValue) { 149 return { 150 user: cpuValues[0] - prevValue.user, 151 system: cpuValues[1] - prevValue.system, 152 }; 153 } 154 155 // If no previous value passed in, return current value. 156 return { 157 user: cpuValues[0], 158 system: cpuValues[1], 159 }; 160 } 161 162 // Ensure that a previously passed in value is valid. Currently, the native 163 // implementation always returns numbers <= Number.MAX_SAFE_INTEGER. 164 function previousValueIsValid(num) { 165 return typeof num === 'number' && 166 num <= NumberMAX_SAFE_INTEGER && 167 num >= 0; 168 } 169 170 const memValues = new Float64Array(5); 171 function memoryUsage() { 172 _memoryUsage(memValues); 173 return { 174 rss: memValues[0], 175 heapTotal: memValues[1], 176 heapUsed: memValues[2], 177 external: memValues[3], 178 arrayBuffers: memValues[4], 179 }; 180 } 181 182 memoryUsage.rss = rss; 183 184 function exit(code) { 185 process.off('exit', handleProcessExit); 186 187 if (code || code === 0) 188 process.exitCode = code; 189 190 if (!process._exiting) { 191 process._exiting = true; 192 process.emit('exit', process.exitCode || 0); 193 } 194 // FIXME(joyeecheung): This is an undocumented API that gets monkey-patched 195 // in the user land. Either document it, or deprecate it in favor of a 196 // better public alternative. 197 process.reallyExit(process.exitCode || 0); 198 199 // If this is a worker, v8::Isolate::TerminateExecution() is called above. 200 // That function spoofs the stack pointer to cause the stack guard 201 // check to throw the termination exception. Because v8 performs 202 // stack guard check upon every function call, we give it a chance. 203 // 204 // Without this, user code after `process.exit()` would take effect. 205 // test/parallel/test-worker-voluntarily-exit-followed-by-addition.js 206 // test/parallel/test-worker-voluntarily-exit-followed-by-throw.js 207 nop(); 208 } 209 210 function kill(pid, sig) { 211 let err; 212 213 // eslint-disable-next-line eqeqeq 214 if (pid != (pid | 0)) { 215 throw new ERR_INVALID_ARG_TYPE('pid', 'number', pid); 216 } 217 218 // Preserve null signal 219 if (sig === (sig | 0)) { 220 // XXX(joyeecheung): we have to use process._kill here because 221 // it's monkey-patched by tests. 222 err = process._kill(pid, sig); 223 } else { 224 sig = sig || 'SIGTERM'; 225 if (constants[sig]) { 226 err = process._kill(pid, constants[sig]); 227 } else { 228 throw new ERR_UNKNOWN_SIGNAL(sig); 229 } 230 } 231 232 if (err) 233 throw errnoException(err, 'kill'); 234 235 return true; 236 } 237 238 const resourceValues = new Float64Array(16); 239 function resourceUsage() { 240 _resourceUsage(resourceValues); 241 return { 242 userCPUTime: resourceValues[0], 243 systemCPUTime: resourceValues[1], 244 maxRSS: resourceValues[2], 245 sharedMemorySize: resourceValues[3], 246 unsharedDataSize: resourceValues[4], 247 unsharedStackSize: resourceValues[5], 248 minorPageFault: resourceValues[6], 249 majorPageFault: resourceValues[7], 250 swappedOut: resourceValues[8], 251 fsRead: resourceValues[9], 252 fsWrite: resourceValues[10], 253 ipcSent: resourceValues[11], 254 ipcReceived: resourceValues[12], 255 signalsCount: resourceValues[13], 256 voluntaryContextSwitches: resourceValues[14], 257 involuntaryContextSwitches: resourceValues[15], 258 }; 259 } 260 261 262 return { 263 _rawDebug, 264 cpuUsage, 265 resourceUsage, 266 memoryUsage, 267 kill, 268 exit, 269 }; 270} 271 272const replaceUnderscoresRegex = /_/g; 273const leadingDashesRegex = /^--?/; 274const trailingValuesRegex = /=.*$/; 275 276// This builds the initial process.allowedNodeEnvironmentFlags 277// from data in the config binding. 278function buildAllowedFlags() { 279 const { 280 envSettings: { kAllowedInEnvvar }, 281 types: { kBoolean }, 282 } = internalBinding('options'); 283 const { options, aliases } = require('internal/options'); 284 285 const allowedNodeEnvironmentFlags = []; 286 for (const { 0: name, 1: info } of options) { 287 if (info.envVarSettings === kAllowedInEnvvar) { 288 ArrayPrototypePush(allowedNodeEnvironmentFlags, name); 289 if (info.type === kBoolean) { 290 const negatedName = `--no-${name.slice(2)}`; 291 ArrayPrototypePush(allowedNodeEnvironmentFlags, negatedName); 292 } 293 } 294 } 295 296 function isAccepted(to) { 297 if (!StringPrototypeStartsWith(to, '-') || to === '--') return true; 298 const recursiveExpansion = aliases.get(to); 299 if (recursiveExpansion) { 300 if (recursiveExpansion[0] === to) 301 ArrayPrototypeSplice(recursiveExpansion, 0, 1); 302 return ArrayPrototypeEvery(recursiveExpansion, isAccepted); 303 } 304 return options.get(to).envVarSettings === kAllowedInEnvvar; 305 } 306 for (const { 0: from, 1: expansion } of aliases) { 307 if (ArrayPrototypeEvery(expansion, isAccepted)) { 308 let canonical = from; 309 if (StringPrototypeEndsWith(canonical, '=')) 310 canonical = StringPrototypeSlice(canonical, 0, canonical.length - 1); 311 if (StringPrototypeEndsWith(canonical, ' <arg>')) 312 canonical = StringPrototypeSlice(canonical, 0, canonical.length - 4); 313 ArrayPrototypePush(allowedNodeEnvironmentFlags, canonical); 314 } 315 } 316 317 const trimLeadingDashes = 318 (flag) => StringPrototypeReplace(flag, leadingDashesRegex, ''); 319 320 // Save these for comparison against flags provided to 321 // process.allowedNodeEnvironmentFlags.has() which lack leading dashes. 322 const nodeFlags = ArrayPrototypeMap(allowedNodeEnvironmentFlags, 323 trimLeadingDashes); 324 325 class NodeEnvironmentFlagsSet extends Set { 326 constructor(array) { 327 super(); 328 this[kInternal] = { array }; 329 } 330 331 add() { 332 // No-op, `Set` API compatible 333 return this; 334 } 335 336 delete() { 337 // No-op, `Set` API compatible 338 return false; 339 } 340 341 clear() { 342 // No-op, `Set` API compatible 343 } 344 345 has(key) { 346 // This will return `true` based on various possible 347 // permutations of a flag, including present/missing leading 348 // dash(es) and/or underscores-for-dashes. 349 // Strips any values after `=`, inclusive. 350 // TODO(addaleax): It might be more flexible to run the option parser 351 // on a dummy option set and see whether it rejects the argument or 352 // not. 353 if (typeof key === 'string') { 354 key = StringPrototypeReplace(key, replaceUnderscoresRegex, '-'); 355 if (RegExpPrototypeExec(leadingDashesRegex, key) !== null) { 356 key = StringPrototypeReplace(key, trailingValuesRegex, ''); 357 return ArrayPrototypeIncludes(this[kInternal].array, key); 358 } 359 return ArrayPrototypeIncludes(nodeFlags, key); 360 } 361 return false; 362 } 363 364 entries() { 365 this[kInternal].set ??= 366 new Set(new SafeArrayIterator(this[kInternal].array)); 367 return SetPrototypeEntries(this[kInternal].set); 368 } 369 370 forEach(callback, thisArg = undefined) { 371 ArrayPrototypeForEach( 372 this[kInternal].array, 373 (v) => ReflectApply(callback, thisArg, [v, v, this]), 374 ); 375 } 376 377 get size() { 378 return this[kInternal].array.length; 379 } 380 381 values() { 382 this[kInternal].set ??= 383 new Set(new SafeArrayIterator(this[kInternal].array)); 384 return SetPrototypeValues(this[kInternal].set); 385 } 386 } 387 const flagSetValues = NodeEnvironmentFlagsSet.prototype.values; 388 ObjectDefineProperty(NodeEnvironmentFlagsSet.prototype, SymbolIterator, { 389 __proto__: null, 390 value: flagSetValues, 391 }); 392 ObjectDefineProperty(NodeEnvironmentFlagsSet.prototype, 'keys', { 393 __proto__: null, 394 value: flagSetValues, 395 }); 396 397 ObjectFreeze(NodeEnvironmentFlagsSet.prototype.constructor); 398 ObjectFreeze(NodeEnvironmentFlagsSet.prototype); 399 400 return ObjectFreeze(new NodeEnvironmentFlagsSet( 401 allowedNodeEnvironmentFlags, 402 )); 403} 404 405// Lazy load internal/trace_events_async_hooks only if the async_hooks 406// trace event category is enabled. 407let traceEventsAsyncHook; 408// Dynamically enable/disable the traceEventsAsyncHook 409function toggleTraceCategoryState(asyncHooksEnabled) { 410 if (asyncHooksEnabled) { 411 if (!traceEventsAsyncHook) { 412 traceEventsAsyncHook = 413 require('internal/trace_events_async_hooks').createHook(); 414 } 415 traceEventsAsyncHook.enable(); 416 } else if (traceEventsAsyncHook) { 417 traceEventsAsyncHook.disable(); 418 } 419} 420 421module.exports = { 422 toggleTraceCategoryState, 423 assert, 424 buildAllowedFlags, 425 wrapProcessMethods, 426 hrtime, 427 hrtimeBigInt, 428 refreshHrtimeBuffer, 429}; 430