多进程打包:thread-loader 源码(7)

508 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的12天,点击查看活动详情

一、前情回顾

上文讨论 Pool.prototype.readNextMessage 的逻辑,它的核心就是先从进程通信管道 readPipe 中通过 this.readBuffer 先读取数据长度,读取到长度之后再调用 this.readBuffer 读取数据,最后交给处理数据的方法;

此外还详细介绍了 readBuffer 方法的工作原理:监听 readPipedata 事件,读取指定长度的 buffer;在进程通信中,通信的消息由两部分构成:数据长度 + 数据;这个设计很nice,数据长度是个 32 位整数,已知 4 个字节长度。

所以每次先读取 4 个字节,得到后面的数据长度,这样就解决了每次读取数据不一样长的问题。不一样长好办啊,子进程会告诉我们数据长度,我们只需要获取一下这个长度再读取这个长度的 buffer 就是子进程要传递过来的数据了

那么本篇小作文,将会接着讨论 this.readNextMessage 方法读取到 worker 进程传过来的数据之后的数据处理逻辑。

二、this.onWorkerMessage 调用

所谓 this.onWorkerMessage 是在 this.readNextMessage 内部完成数据读取后的逻辑,this.onWorkerMessage 顾名思义,当收到 Workermessage 时。

在说这个方法之前,我们先大致复习一下这个调用链路的逻辑。目前的阶段处于 WorkerPool 创建了 PoolWorker 实例,父进程不断的通过管道(this.readPipe)读取 worker 进程写入的数据与子进程通信。此时 webpack 进程运行在父进程中,而 thread-loader 要把并发运行的 loader 们处理完的结果传递给 webpack,这些工作都是依赖通信的。

webpack 执行
   -> thread-loader.pitch()
     -> workerPool.run(data, cb)
       -> this.poolQueue.push(data, cb)
         -> neo-async/queue.js/runQueue()
           -> WorkerPool.prototype.distributeJob()
             -> WorkerPool.prototype.createWorker()
               -> new PoolWorker()
                 -> childProcess.spawn(worker.js) 衍生子进程
                 -> PoolWorker.prototype.readNextMessage()
                   -> this.readBuffer(4, (lengthBuffer) => { 
                       // 读 message 长度
                       this.readBuffer(length, (messageBuffer) => { 
                         // 读 message 数据
                         this.onWorkerMessage(message)
                     })
                   })
                   

三、PoolWorker.prototype.onWorkerMessage

  1. 方法位置:thread-loader/src/WorkerPool.js

  2. 方法参数:

    • 2.1 message:子进程传递过来的消息数据
    • 2.2 finalCallback:处理消息数据后要调用的回调函数
  3. 方法作用:从收到的 message 中获取 类型 type 和 消息 id,对不同的消息进行不同处理,完成后调用 finalCallback 回调。具体分以下几种类型:

    • 3.1 job:这个类型是我们例子中见到的消息类型,当 message.typejob 时,需要调用 neo-async 库中的又一个 mapSeries 方法对 message 中的 data 进行异步串行遍历,从下面的代码中可以看出,data 中的每一项的值表示的是一个 buffer 长度,而包含在这个长度内的就是一段子进程传递来的数据。
    • 3.2 loadModuleloadeModule 类型是子进程需要父进程去加载一个 module 模块,当加载成功后通过管道传递给子进程;
    • 3.3 resolveresolve 类型是子进程通知父进程进行解析某个 request,当解析完成后通过管道传递给子进程;
    • 3.4 emitWarning/emitError 类型则是通过父进程把 thread-loader 运行过程中收集到的错误和警告发送给 compilationwebpack 本次编译创建的 compilation 对象,包含本次编译全部的信息)
class PoolWorker {
   
    onWorkerMessage(message, finalCallback) {
      const { type, id } = message;
      switch (type) {
        case 'job': {
          const { data, error, result } = message;
          // 调用 neo-async/mapSeries 方法进行遍历
          asyncMapSeries(
            data,
            (length, callback) => this.readBuffer(length, callback),
            (eachErr, buffers) => {
              // asyncMapSeries 遍历完成后会调用这个回调
              
              // this.jobs 是哪里来的呢?
              // this.jobs 是调用 PoolWorker.run(data, callback) 方法时添加的任务, 
              // this.jobs 中的每一项形如:{ data, callback },每个 job 对应一个自增的 id
              const { callback: jobCallback } = this.jobs[id]; // job 中的 callback 被重命名 jobCallback
              
              // 创建 callback
              const callback = (err, arg) => {
                if (jobCallback) {
                  // 从 this.jobs 中移除当前 id 对应的 job,因为这一项已经完成
                  delete this.jobs[id];
                  
                  // 维护 activeJobs 数量减少 1,同样是因为这一任务已经完成
                  this.activeJobs -= 1;
                  
                  // 调用创建 PoolWorker 时传递的 onJobDone,即 WokerPool 的 onJobDone
   
                  this.onJobDone();
                  if (err) {
                   
                  } else {
                    // 调用 job 创建时的 callback,也就是 WorkerPool 的 distributeJob 方法
                    // 的 callback,这个 callback 是 neo-async/queue.js 中
                    // runQueue 传入的 done 方法
                    jobCallback(null, arg);
                  }
                }
                
                // finalCallback 是 onWorkerMessage 收到的
                // 即前面 () => setImmediate(() => this.readNextMessage()) 这个函数
                finalCallback();
              };
         
              // 处理 buffer
              let bufferPosition = 0;
              if (result.result) {
                result.result = result.result.map((r) => {
                  // 如果是 buffer 
                  if (r.buffer) {
                    const buffer = buffers[bufferPosition];
                    bufferPosition += 1;
                    
                    // 如果是字符串就转成字符串
                    if (r.string) {
                      return buffer.toString('utf-8');
                    }
                    return buffer;
                  }
                  // 不是 buffer 返回 data
                  return r.data;
                });
              }
              // 所以 result.result 数组项可能有三种情况:buffer、字符串、data 
              
              // 调用上面的私有方法 callback
              callback(null, result);
            }
          );
          break;
        }
        case 'loadModule': {
          // 加载模块
          const { request, questionId } = message;
          const { data } = this.jobs[id];
          
          // 这个 data 就是 thread-loader pitch 中 workerPool.run(data, cb)
          // 传入的包含 loaders 的对象,这个对象长得很像 loader 的 loaderContext 对象
          data.loadModule(request, (error, source, sourceMap, module) => {
            // 加载完成后通过 this.writePipe 传递给子进程
            this.writeJson({
              type: 'result',
              id: questionId,
              error: error
                ? {
                    message: error.message,
                    details: error.details,
                    missing: error.missing,
                  }
                : null,
              result: [
                source,
                sourceMap,
                // TODO: Serialize module?
                // module,
              ],
            });
          });
          finalCallback();
          break;
        }
        case 'resolve': {
          // 解析 request 路径
          const { context, request, options, questionId } = message;
          const { data } = this.jobs[id];
          if (options) {
            data.getResolve(options)(context, request, (error, result) => {
              this.writeJson({
                type: 'result',
                id: questionId,
                error: error
                  ? {
                      message: error.message,
                      details: error.details,
                      missing: error.missing,
                    }
                  : null,
                result,
              });
            });
          } else {
            data.resolve(context, request, (error, result) => {
              this.writeJson({
                type: 'result',
                id: questionId,
                error: error
                  ? {
                      message: error.message,
                      details: error.details,
                      missing: error.missing,
                    }
                  : null,
                result,
              });
            });
          }
          finalCallback();
          break;
        }
        case 'emitWarning': {
          // 发射警告到 compilation
          const { data } = message;
          const { data: jobData } = this.jobs[id];
          jobData.emitWarning(this.fromErrorObj(data));
          finalCallback();
          break;
        }
        case 'emitError': {
          // 发射错误到 compilation
          const { data } = message;
          const { data: jobData } = this.jobs[id];
          jobData.emitError(this.fromErrorObj(data));
          finalCallback();
          break;
        }
        default: {
          // 其他类型当做错误处理
          console.error(`Unexpected worker message ${type} in WorkerPool.`);
          finalCallback();
          break;
        }
      }
    }
}

四、总结

本文讨论 onWorkerMessage 的方法核心逻辑,并就 job 类型进行了展开讨论,其他类型做了简单说明:

  1. job:类型下调用 neo-async/mapSeries 异步遍历,读取消息中的 buffer 数据,得到数据后进行处理,最后调用创建这个任务时传递进来的 callback,这个 callback 就是 WorkerPool 实例的 distributeJob 的回调,也就是 neo-aync/queuerunQueuedone 方法,标志这一个并行任务的结束;

  2. 其他类型:loadModule/resolve/emitWaring/emitError 都是子进程告知父进程去做一些工作,然后把这些工作结果再通过管道发送给子进程,这里有个思考点,想一想为什么要这么做?

下一篇我们要详细讨论 neo-async/mapSeries 方法的核心实现,以及如何确保异步串行的结果的顺序。