Skip to content

Commit

Permalink
Use atomic operations to prevent partial files when generating images…
Browse files Browse the repository at this point in the history
… (#3513)

## Description

We've had issues where during the asset generation stage of `npm run
dev`, the script would crash due to OOM. Subsequent runs will say
"Assets generated successfully" but the images would be corrupt. This PR
uses a temporary file and rename to alleviate this issue.

## Checklist
- [x] I have read and understood the [WATcloud
Guidelines](https://cloud.watonomous.ca/docs/community-docs/watcloud/guidelines)
- [x] I have performed a self-review of my code
  • Loading branch information
ben-z authored Dec 30, 2024
1 parent f5c73a7 commit 5a94184
Showing 1 changed file with 9 additions and 4 deletions.
13 changes: 9 additions & 4 deletions scripts/generate-assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ async function processImage(image, preprocessSteps = []) {
if (sha256Hash !== imageURI.sha256) {
throw new Error(`SHA-256 hash mismatch for "${image.name}"! Expected ${imageURI.sha256}, got ${sha256Hash}`);
}
await fs.promises.writeFile(originalPath, Buffer.from(buffer));
// Perform an atomic write to prevent partial files
await fs.promises.writeFile(originalPath + ".partial", Buffer.from(buffer));
await fs.promises.rename(originalPath + ".partial", originalPath);
}

let sharpImage = sharp(originalPath);
Expand Down Expand Up @@ -145,13 +147,16 @@ async function processImage(image, preprocessSteps = []) {
const jpgCacheName = `${image.name}-${slugify(JSON.stringify(jpgOptions), { lower: true, strict: true })}.jpg`;

if (!fs.existsSync(path.join(cacheDir, avifCacheName))) {
await sharpImage.avif(avifOptions).toFile(path.join(cacheDir, avifCacheName));
await sharpImage.avif(avifOptions).toFile(path.join(cacheDir, avifCacheName + ".partial"));
await fs.promises.rename(path.join(cacheDir, avifCacheName + ".partial"), path.join(cacheDir, avifCacheName));
}
if (!fs.existsSync(path.join(cacheDir, webpCacheName))) {
await sharpImage.webp(webpOptions).toFile(path.join(cacheDir, webpCacheName));
await sharpImage.webp(webpOptions).toFile(path.join(cacheDir, webpCacheName + ".partial"));
await fs.promises.rename(path.join(cacheDir, webpCacheName + ".partial"), path.join(cacheDir, webpCacheName));
}
if (!fs.existsSync(path.join(cacheDir, jpgCacheName))) {
await sharpImage.jpeg(jpgOptions).toFile(path.join(cacheDir, jpgCacheName));
await sharpImage.jpeg(jpgOptions).toFile(path.join(cacheDir, jpgCacheName + ".partial"));
await fs.promises.rename(path.join(cacheDir, jpgCacheName + ".partial"), path.join(cacheDir, jpgCacheName));
}

await Promise.all([
Expand Down

0 comments on commit 5a94184

Please sign in to comment.