持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的12天,点击查看活动详情
一、前情回顾
上文讨论 Pool.prototype.readNextMessage 的逻辑,它的核心就是先从进程通信管道 readPipe 中通过 this.readBuffer 先读取数据长度,读取到长度之后再调用 this.readBuffer 读取数据,最后交给处理数据的方法;
此外还详细介绍了 readBuffer 方法的工作原理:监听 readPipe 的 data 事件,读取指定长度的 buffer;在进程通信中,通信的消息由两部分构成:数据长度 + 数据;这个设计很nice,数据长度是个 32 位整数,已知 4 个字节长度。
所以每次先读取 4 个字节,得到后面的数据长度,这样就解决了每次读取数据不一样长的问题。不一样长好办啊,子进程会告诉我们数据长度,我们只需要获取一下这个长度再读取这个长度的 buffer 就是子进程要传递过来的数据了
那么本篇小作文,将会接着讨论 this.readNextMessage 方法读取到 worker 进程传过来的数据之后的数据处理逻辑。
二、this.onWorkerMessage 调用
所谓 this.onWorkerMessage 是在 this.readNextMessage 内部完成数据读取后的逻辑,this.onWorkerMessage 顾名思义,当收到 Worker 的 message 时。
在说这个方法之前,我们先大致复习一下这个调用链路的逻辑。目前的阶段处于 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
-
方法位置:
thread-loader/src/WorkerPool.js; -
方法参数:
- 2.1
message:子进程传递过来的消息数据 - 2.2
finalCallback:处理消息数据后要调用的回调函数
- 2.1
-
方法作用:从收到的
message中获取 类型type和 消息id,对不同的消息进行不同处理,完成后调用finalCallback回调。具体分以下几种类型:- 3.1
job:这个类型是我们例子中见到的消息类型,当message.type为job时,需要调用neo-async库中的又一个mapSeries方法对message中的data进行异步串行遍历,从下面的代码中可以看出,data中的每一项的值表示的是一个buffer长度,而包含在这个长度内的就是一段子进程传递来的数据。 - 3.2
loadModule:loadeModule类型是子进程需要父进程去加载一个module模块,当加载成功后通过管道传递给子进程; - 3.3
resolve:resolve类型是子进程通知父进程进行解析某个request,当解析完成后通过管道传递给子进程; - 3.4
emitWarning/emitError类型则是通过父进程把thread-loader运行过程中收集到的错误和警告发送给compilation(webpack本次编译创建的compilation对象,包含本次编译全部的信息)
- 3.1
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 类型进行了展开讨论,其他类型做了简单说明:
-
job:类型下调用neo-async/mapSeries异步遍历,读取消息中的buffer数据,得到数据后进行处理,最后调用创建这个任务时传递进来的callback,这个callback就是WorkerPool实例的distributeJob的回调,也就是neo-aync/queue中runQueue的done方法,标志这一个并行任务的结束; -
其他类型:
loadModule/resolve/emitWaring/emitError都是子进程告知父进程去做一些工作,然后把这些工作结果再通过管道发送给子进程,这里有个思考点,想一想为什么要这么做?
下一篇我们要详细讨论 neo-async/mapSeries 方法的核心实现,以及如何确保异步串行的结果的顺序。