Serialize builds to prevent stale output race#4
Serialize builds to prevent stale output race#4christian-bromann merged 4 commits intostencil-community:mainfrom
Conversation
|
Note: performance is struggling; it takes around 3-4 seconds in watch mode. This was the only way I managed to fix the issue with the race condition. Any feedback or improvement suggestions would be great to hear ❤️ |
christian-bromann
left a comment
There was a problem hiding this comment.
Thanks for the work on this. I tested it out and it works. Performance wise we would need to look into the Stencil compiler directly and allow to re-compiler single files rather than the whole project. I think we can gain some performance wins there.
I have a suggestion regarding the implementation: I think it makes sense to wrap the compiler usage into a specific class to avoid confusion in the code. This way we can keep the interaction with the Stencil compiler separate from the Unplugin. An initial implementation could look like this:
class BuildQueue {
#buildId = 0
#compiler: CoreCompiler.Compiler
#queue: [number, Promise<CoreCompiler.CompilerBuildResults>][] = []
constructor(compiler: CoreCompiler.Compiler) {
this.#compiler = compiler
}
#schedule() {
const buildId = ++this.#buildId
this.#queue.push([
buildId,
this.#compiler.build()
])
/**
* only keep last 10 builds
*/
if (this.#queue.length > 10) {
this.#queue.shift()
}
return buildId
}
async waitForLatestBuild() {
const [latestBuildId, latestBuild] = this.#queue[this.#queue.length - 1]
await latestBuild
return latestBuildId
}
async ensureFreshBuild(srcPath: string, distPath: string) {
try {
const [srcStats, distStats] = await Promise.all([
compiler?.sys.stat(srcPath),
compiler?.sys.stat(distPath),
])
if (
distStats?.mtimeMs
&& srcStats?.mtimeMs
&& distStats.mtimeMs >= srcStats.mtimeMs
) {
return
}
}
catch {}
this.#schedule()
await this.waitForLatestBuild()
}
}Wdyt?
|
Sure, sounds good! Gonna update pr |
I am trying to implement the suggested class, and the major works well. The only problem is saving while there is an ongoing build. For example, you saved some change and straight away do quick change and save, at this moment I am getting |
|
This seems to come from the in-memory-fs of Stencil. I assume the |
src/index.ts
Outdated
| await buildQueue?.ensureFreshBuild(id, compilerFilePath) | ||
|
|
||
| const exists = await compiler.sys.access(compilerFilePath) | ||
| if (!exists) | ||
| throw new Error('Could not find the output file') | ||
|
|
||
| const raw = await compiler!.sys.readFile(compilerFilePath) |
There was a problem hiding this comment.
Let's move this into the build queue and rename ensureFreshBuild to something like `getLatestBuild(id, compilerPath)
| await buildQueue?.ensureFreshBuild(id, compilerFilePath) | |
| const exists = await compiler.sys.access(compilerFilePath) | |
| if (!exists) | |
| throw new Error('Could not find the output file') | |
| const raw = await compiler!.sys.readFile(compilerFilePath) | |
| const raw = await buildQueue?.getLatestBuild(id, compilerFilePath) |
src/build-queue.ts
Outdated
| catch {} | ||
|
|
||
| this.#queueBuild() | ||
| while (this.#isBuilding || this.#pending) await new Promise(r => setTimeout(r, 25)) |
There was a problem hiding this comment.
What do you think about having this class extend from EventEmitter and do something like this:
| while (this.#isBuilding || this.#pending) await new Promise(r => setTimeout(r, 25)) | |
| return new Promise((resolve) => this.once('buildFinished', resolve)) |
?
There was a problem hiding this comment.
Excellent idea!
Learned something new 😂
…d` to `getLatestBuild
|
@timurzholudev I had to push a little fix as I was testing the new version with the storybook plugin. In |
Oh, I don't know why I didn't test new changes in the storybook plugin 🤦 Great catch, |
Description:
The unplugin-stencil plugin does not update the dist folder after launching the build and modifying a component.
Changes Made
Fix race condition where transform() could return stale output if a new build started during an ongoing one. Builds are now serialized and awaited using a version-safe loop, with a debounce added to coalesce rapid file changes.