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