Skip to content

Commit 4d4dddd

Browse files
authored
fix: introduce AsyncTask, make rebuildModule safer (#9244)
fix: introduce AsyncTask to make rebuildModule safer and correct the order of invoking module_executor hook_after_make
1 parent 3fa4f3e commit 4d4dddd

File tree

11 files changed

+151
-34
lines changed

11 files changed

+151
-34
lines changed

Diff for: crates/rspack_binding_values/src/compilation/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,6 @@ impl JsCompilation {
590590
)
591591
.await
592592
.map_err(|e| Error::new(napi::Status::GenericFailure, format!("{e}")))?;
593-
594593
modules
595594
.iter_mut()
596595
.for_each(|module| module.attach(compilation));

Diff for: crates/rspack_core/src/compiler/compilation.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -1201,13 +1201,6 @@ impl Compilation {
12011201

12021202
self.in_finish_make.store(false, Ordering::Release);
12031203

1204-
// sync assets to compilation from module_executor
1205-
if let Some(module_executor) = &mut self.module_executor {
1206-
let mut module_executor = std::mem::take(module_executor);
1207-
module_executor.hook_after_finish_modules(self).await;
1208-
self.module_executor = Some(module_executor);
1209-
}
1210-
12111204
// take built_modules
12121205
if let Some(mutations) = self.incremental.mutations_write() {
12131206
mutations.extend(
@@ -1239,6 +1232,14 @@ impl Compilation {
12391232
.finish_modules
12401233
.call(self)
12411234
.await?;
1235+
1236+
// sync assets to compilation from module_executor
1237+
if let Some(module_executor) = &mut self.module_executor {
1238+
let mut module_executor = std::mem::take(module_executor);
1239+
module_executor.hook_after_finish_modules(self).await;
1240+
self.module_executor = Some(module_executor);
1241+
}
1242+
12421243
logger.time_end(start);
12431244
// Collect dependencies diagnostics at here to make sure:
12441245
// 1. after finish_modules: has provide exports info
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const a = 1;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { a as a1 } from "./a?1";
2+
import { a as a2 } from "./a?2";
3+
import { a as a3 } from "./a?3";
4+
5+
it("compilation rebuild module should works", () => {
6+
expect(a1 + a2 + a3).toBe(15)
7+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
let times = 0;
2+
module.exports = function loader(content) {
3+
times++;
4+
return content.replace("1", times);
5+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const pluginName = "plugin";
2+
3+
class Plugin {
4+
apply(compiler) {
5+
let initial = true;
6+
compiler.hooks.compilation.tap(pluginName, compilation => {
7+
compilation.hooks.finishModules.tapPromise(pluginName, async modules => {
8+
modules = [...modules];
9+
if (initial) {
10+
initial = false;
11+
12+
const results = await Promise.all(
13+
modules.map(m => {
14+
return new Promise((resolve, reject) => {
15+
compilation.rebuildModule(m, (err, module) => {
16+
if (err) {
17+
reject(err);
18+
} else {
19+
resolve(module);
20+
}
21+
});
22+
});
23+
})
24+
);
25+
26+
// should compile success
27+
expect(results.length).toBe(4);
28+
}
29+
});
30+
});
31+
}
32+
}
33+
34+
/**@type {import("@rspack/core").Configuration}*/
35+
module.exports = {
36+
module: {
37+
rules: [
38+
{
39+
test: /a\.js$/,
40+
use: [
41+
{
42+
loader: "./loader"
43+
}
44+
]
45+
}
46+
]
47+
},
48+
plugins: [new Plugin()]
49+
};

Diff for: packages/rspack/etc/core.api.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -838,7 +838,7 @@ export class Compilation {
838838
// (undocumented)
839839
static PROCESS_ASSETS_STAGE_SUMMARIZE: number;
840840
// (undocumented)
841-
rebuildModule(m: Module, f: (err: Error, m: Module) => void): void;
841+
rebuildModule(m: Module, f: (err: Error | null, m: Module | null) => void): void;
842842
// (undocumented)
843843
renameAsset(filename: string, newFilename: string): void;
844844
// (undocumented)

Diff for: packages/rspack/src/Compilation.ts

+26-23
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
type ExternalObject,
1313
type JsCompatSourceOwned,
1414
type JsCompilation,
15-
type JsModule,
1615
type JsPathData,
1716
JsRspackSeverity,
1817
type JsRuntimeModule
@@ -53,7 +52,7 @@ import { LogType, Logger } from "./logging/Logger";
5352
import { StatsFactory } from "./stats/StatsFactory";
5453
import { StatsPrinter } from "./stats/StatsPrinter";
5554
import { type AssetInfo, JsAssetInfo } from "./util/AssetInfo";
56-
import MergeCaller from "./util/MergeCaller";
55+
import { AsyncTask } from "./util/AsyncTask";
5756
import { createReadonlyMap } from "./util/createReadonlyMap";
5857
import { createFakeCompilationDependencies } from "./util/fake";
5958
import type { InputFileSystem } from "./util/fs";
@@ -1075,27 +1074,31 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
10751074
);
10761075
}
10771076

1078-
#rebuildModuleCaller = ((compilation: Compilation) =>
1079-
new MergeCaller(
1080-
(args: Array<[string, (err: Error, m: Module) => void]>) => {
1081-
compilation.#inner.rebuildModule(
1082-
args.map(item => item[0]),
1083-
(err: Error, modules: JsModule[]) => {
1084-
for (const [id, callback] of args) {
1085-
const m = modules.find(item => item.moduleIdentifier === id);
1086-
if (m) {
1087-
callback(err, Module.__from_binding(m));
1088-
} else {
1089-
callback(err || new Error("module no found"), null as any);
1090-
}
1091-
}
1077+
#rebuildModuleTask = new AsyncTask<string, Module>(
1078+
(moduleIdentifiers, doneWork) => {
1079+
this.#inner.rebuildModule(
1080+
moduleIdentifiers,
1081+
(err: Error | null, modules: binding.JsModule[]) => {
1082+
/*
1083+
* TODO:
1084+
* batch all call parameters, once a module is failed, we cannot know which module
1085+
* is failed to rebuild, we have to make all modules failed, this should be improved
1086+
* in the future
1087+
*/
1088+
if (err) {
1089+
doneWork(new Array(moduleIdentifiers.length).fill([err, null]));
1090+
} else {
1091+
doneWork(
1092+
modules.map(jsModule => [null, Module.__from_binding(jsModule)])
1093+
);
10921094
}
1093-
);
1094-
}
1095-
))(this);
1095+
}
1096+
);
1097+
}
1098+
);
10961099

1097-
rebuildModule(m: Module, f: (err: Error, m: Module) => void) {
1098-
this.#rebuildModuleCaller.push([m.identifier(), f]);
1100+
rebuildModule(m: Module, f: (err: Error | null, m: Module | null) => void) {
1101+
this.#rebuildModuleTask.exec(m.identifier(), f);
10991102
}
11001103

11011104
addRuntimeModule(chunk: Chunk, runtimeModule: RuntimeModule) {
@@ -1248,7 +1251,7 @@ class AddIncludeDispatcher {
12481251
this.#cbs = [];
12491252
this.#inner(args, (wholeErr, results) => {
12501253
if (this.#args.length !== 0) {
1251-
queueMicrotask(this.#execute);
1254+
queueMicrotask(this.#execute.bind(this));
12521255
}
12531256

12541257
if (wholeErr) {
@@ -1281,7 +1284,7 @@ class AddIncludeDispatcher {
12811284
callback: (err?: null | WebpackError, module?: Module) => void
12821285
) {
12831286
if (this.#args.length === 0) {
1284-
queueMicrotask(this.#execute);
1287+
queueMicrotask(this.#execute.bind(this));
12851288
}
12861289

12871290
this.#args.push([context, dependency, options as any]);

Diff for: packages/rspack/src/util/AsyncTask.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
type TaskCallback<Ret> = (err: Error | null, ret: Ret | null) => void;
2+
3+
export class AsyncTask<Param, Ret> {
4+
#isRunning = false;
5+
#params: Param[] = [];
6+
#callbacks: TaskCallback<Ret>[] = [];
7+
8+
#task: (
9+
param: Param[],
10+
callback: (results: [Error | null, Ret | null][]) => void
11+
) => void;
12+
13+
constructor(
14+
task: (
15+
param: Param[],
16+
callback: (results: [Error | null, Ret | null][]) => void
17+
) => void
18+
) {
19+
this.#task = task;
20+
}
21+
22+
#exec_internal() {
23+
const params = this.#params;
24+
const callbacks = this.#callbacks;
25+
this.#params = [];
26+
this.#callbacks = [];
27+
28+
this.#task(params, results => {
29+
this.#isRunning = false;
30+
if (this.#params.length) {
31+
this.#isRunning = true;
32+
queueMicrotask(() => this.#exec_internal());
33+
}
34+
35+
for (let i = 0; i < results.length; i++) {
36+
const [err, result] = results[i];
37+
const callback = callbacks[i];
38+
callback(err, result);
39+
}
40+
});
41+
}
42+
43+
exec(param: Param, callback: TaskCallback<Ret>) {
44+
if (!this.#isRunning) {
45+
queueMicrotask(() => this.#exec_internal());
46+
this.#isRunning = true;
47+
}
48+
49+
this.#params.push(param);
50+
this.#callbacks.push(callback);
51+
}
52+
}

Diff for: website/docs/en/api/javascript-api/compilation.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ Triggers a re-build of the module.
479479
```ts
480480
rebuildModule(
481481
m: Module, // module to be rebuilt
482-
f: (err: Error, m: Module) => void // function to be invoked when the module finishes rebuilding
482+
f: (err: Error | null, m: Module | null) => void // function to be invoked when the module finishes rebuilding
483483
): void;
484484
```
485485

Diff for: website/docs/zh/api/javascript-api/compilation.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ export default {
477477
重新构建指定模块。
478478
479479
```ts
480-
rebuildModule(m: Module, f: (err: Error, m: Module) => void): void;
480+
rebuildModule(m: Module, f: (err: Error | null, m: Module | null) => void): void;
481481
```
482482
483483
以下示例将重新构建结尾为 `a.js` 模块:

0 commit comments

Comments
 (0)