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