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