当前位置:   article > 正文

深入理解Node.js多进程_err_child_process_stdio_maxbuffer

err_child_process_stdio_maxbuffer

前提

本篇文章对Node多进程源码进行剥丝抽茧,力图将多进程原理讲清,并且搞清楚exec,execFile,spawn,fork之间到底有什么关联,底层都是如何实现的。
默认视为你已了解了Node多进程,本文章使用当前最新的Node版本v16.1.0进行解析,由于最底层使用C++编写,超出了JS范畴,暂时不做解析。

spawn

先看一段使用代码:

const child = require('child_process')

const spawn = child.spawn('ls', ['-al'])
spawn.stdout.on('data', chunk => console.log(chunk))
  • 1
  • 2
  • 3
  • 4

这段代码会输出当前执行环境的文件目录,但是spawn替你做了些什么?

function spawn(file, args, options) {
   
  // 将参数进行格式化
  options = normalizeSpawnArguments(file, args, options);
  // 如果显式声明了timeout,需要是整数且大于0
  validateTimeout(options.timeout);
  // 如果显式声明了signal,要求必须是一个对象且该对象必须包含aborted属性
  validateAbortSignal(options.signal, 'options.signal');
  // 处理进程杀死的信号值,必须要求是number或string,且必须与内置的信号值对应
  const killSignal = sanitizeKillSignal(options.killSignal);
  // 创建子进程对象
  const child = new ChildProcess();
  // debug打印
  debug('spawn', options);
  // 执行子进程
  child.spawn(options);
  // 如果设置了timeout,此时开始实现:如果超过规定时间直接杀死子进程
  if (options.timeout > 0) {
   
    let timeoutId = setTimeout(() => {
   
      if (timeoutId) {
   
        try {
   
          child.kill(killSignal);
        } catch (err) {
   
          child.emit('error', err);
        }
        timeoutId = null;
      }
    }, options.timeout);
    // 子进程退出时,检查一遍内存
    child.once('exit', () => {
   
      if (timeoutId) {
   
        clearTimeout(timeoutId);
        timeoutId = null;
      }
    });
  }
  // 如果设置了signal,此时开始实现:如果signal.aborted为true,则函数结束后开始检查,否则等到abort再检查
  if (options.signal) {
   
    const signal = options.signal;
    if (signal.aborted) {
   
      process.nextTick(onAbortListener);
    } else {
   
      signal.addEventListener('abort', onAbortListener, {
    once: true });
      child.once('exit', () =>
        signal.removeEventListener('abort', onAbortListener)
      );
    }

    function onAbortListener() {
   
      // 如果在执行时子进程已经杀死,会报错
      abortChildProcess(child, killSignal);
    }
  }

  return child;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

可以看到spawn的核心实现在于ChildProcess类,那么接下来就着重于分析ChildProcess类实现。首先分析处理函数。

格式化参数

function normalizeSpawnArguments(file, args, options) {
   
  // 校验第一个参数是不是string
  validateString(file, 'file');
  // 校验第一个参数是不是一个空串
  if (file.length === 0)
    throw new ERR_INVALID_ARG_VALUE('file', file, 'cannot be empty');
  // 第二个参数是不是数组
  if (ArrayIsArray(args)) {
   
    // 做一层浅拷贝
    args = ArrayPrototypeSlice(args);
  } else if (args == null) {
   
  	// 如果没有写第二个参数,给一个默认值
    args = [];
  } else if (typeof args !== 'object') {
   
  	// 如果第二个参数不是对象,报错
    throw new ERR_INVALID_ARG_TYPE('args', 'object', args);
  } else {
   
  	// 到这一步说明第二个参数是一个对象,将第二第三参数互调,视为options
    options = args;
    args = [];
  }
  // 给options赋默认值
  if (options === undefined) options = {
   };
  // 如果存在options,校验是否为对象
  else validateObject(options, 'options');

  // 如果显示声明了cwd配置属性,会对cwd配置属性进行string类型校验
  if (options.cwd != null) {
   
    validateString(options.cwd, 'options.cwd');
  }

  // 如果显示声明了detached配置属性,会对detached配置属性进行boolean类型校验
  if (options.detached != null && typeof options.detached !== 'boolean') {
   
    throw new ERR_INVALID_ARG_TYPE(
      'options.detached',
      'boolean',
      options.detached
    );
  }

  //  如果显示声明了uid配置属性,会对uid配置属性进行number类型校验
  if (options.uid != null && !isInt32(options.uid)) {
   
    throw new ERR_INVALID_ARG_TYPE('options.uid', 'int32', options.uid);
  }

  // 如果显示声明了gid配置属性,会对gid配置属性进行number类型校验
  if (options.gid != null && !isInt32(options.gid)) {
   
    throw new ERR_INVALID_ARG_TYPE('options.gid', 'int32', options.gid);
  }

  // 如果显示声明了shell配置属性,会对shell配置属性进行boolean, string类型校验
  if (
    options.shell != null &&
    typeof options.shell !== 'boolean' &&
    typeof options.shell !== 'string'
  ) {
   
    throw new ERR_INVALID_ARG_TYPE(
      'options.shell',
      ['boolean', 'string'],
      options.shell
    );
  }

  // 如果显示声明了argv0配置属性,会对argv0配置属性进行string类型校验
  if (options.argv0 != null) {
   
    validateString(options.argv0, 'options.argv0');
  }

  // 如果显示声明了windowHide配置属性,会对windowHide配置属性进行boolean类型校验
  if (options.windowsHide != null && typeof options.windowsHide !== 'boolean') {
   
    throw new ERR_INVALID_ARG_TYPE(
      'options.windowsHide',
      'boolean',
      options.windowsHide
    );
  }

  // 如果显示声明了windowsVerbatimArguments配置属性,会对windowsVerbatimArguments配置属性进行boolean类型校验
  let {
    windowsVerbatimArguments } = options;
  if (
    windowsVerbatimArguments != null &&
    typeof windowsVerbatimArguments !== 'boolean'
  ) {
   
    throw new ERR_INVALID_ARG_TYPE(
      'options.windowsVerbatimArguments',
      'boolean',
      windowsVerbatimArguments
    );
  }

  if (options.shell) {
   
  	// 如果存在shell配置属性,说明要进行自定义脚本配置,这里做一个join操作是为了更改了file为shell脚本路径之后,command不会受其改变而变化
    const command = ArrayPrototypeJoin([file, ...args], ' ');
    // 如果是windows,需要做特殊处理
    if (process.platform === 'win32') {
   
      // 如果你设置的就是string类型,说明你已经决定好该使用什么来执行node,直接设置即可
      if (typeof options.shell === 'string') file = options.shell;
      // 否则node帮你选择默认的脚本执行命令
      else file = process.env.comspec || 'cmd.exe';
      // 再次匹配是否是cmd.exe
      if (RegExpPrototypeTest(/^(?:.*\\)?cmd(?:\.exe)?$/i, file)) {
   
        // 为cmd.exe添加三个参数,这三个参数只能是cmd.exe使用
        args = ['/d', '/s', '/c', `"${
     command}"`];
        // 如果是cmd.exe,默认不为windows参数加上引号或转义
        windowsVerbatimArguments = true;
      } else {
   
        // 如果自己定义了shell且不是cmd.exe,正常为其添加-c
        args = ['-c', command];
      }
    } else {
   
      // 同理,判断其他平台并修改file,最后添加-c,
      if (typeof options.shell === 'string') file = options.shell;
      else if (process.platform === 'android') file = '/system/bin/sh';
      else file = '/bin/sh';
      args = ['-c', command];
    }
  }
  
  // 此时,会将argv0设置到最前面
  if (typeof options.argv0 === 'string') {
   
    ArrayPrototypeUnshift(args, options.argv0);
  } else {
   
    ArrayPrototypeUnshift(args, file);
  }
  // 如果显式声明了env,则直接取;否则从默认环境拿
  const env = options.env || process.env;
  const envPairs = [];

  // 确保env.NODE_V8_COVERAGE存在,NODE_V8_COVERAGE可通过对应的工具收集和分析覆盖率
  if (
    process.env.NODE_V8_COVERAGE &&
    !ObjectPrototypeHasOwnProperty(options.env || {
   }, 'NODE_V8_COVERAGE')
  ) {
   
    env.NODE_V8_COVERAGE = process.env.NODE_V8_COVERAGE;
  }

  let envKeys = [];
  // 将env的key都推入一个数组,这里是有意的包含原型链属性
  for (const key in env) {
   
    ArrayPrototypePush(envKeys, key);
  }

  if (process.platform === 'win32') {
   
    // Windows环境的env不区分大小写,为了避免重复,只取第一个env,之后的都过滤掉
    const sawKey = new SafeSet();
    envKeys = ArrayPrototypeFilter(ArrayPrototypeSort(envKeys), (key) => {
   
      const uppercaseKey = StringPrototypeToUpperCase(key);
      
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号