• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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