• 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 {
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