Skip to content

Commit f7ecdca

Browse files
committed
fix memory usage; optimize handler
1 parent 014a2f8 commit f7ecdca

File tree

3 files changed

+62
-15
lines changed

3 files changed

+62
-15
lines changed

packages/next/next-server/server/image-optimizer.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,15 @@ export async function imageOptimizer(
234234
const animate =
235235
ANIMATABLE_TYPES.includes(upstreamType) && isAnimated(upstreamBuffer)
236236
if (vector || animate) {
237-
await writeToCacheDir(hashDir, upstreamType, expireAt, upstreamBuffer)
238-
sendResponse(req, res, upstreamType, upstreamBuffer)
237+
const etag = getHash([upstreamBuffer])
238+
sendResponse(req, res, upstreamType, etag, upstreamBuffer)
239+
await writeToCacheDir(
240+
hashDir,
241+
upstreamType,
242+
expireAt,
243+
etag,
244+
upstreamBuffer
245+
)
239246
return { finished: true }
240247
}
241248
}
@@ -281,13 +288,21 @@ export async function imageOptimizer(
281288
}
282289

283290
if (optimizedBuffer) {
284-
await writeToCacheDir(hashDir, contentType, expireAt, optimizedBuffer)
285-
sendResponse(req, res, contentType, optimizedBuffer)
291+
const etag = getHash([optimizedBuffer])
292+
sendResponse(req, res, contentType, etag, optimizedBuffer)
293+
await writeToCacheDir(
294+
hashDir,
295+
contentType,
296+
expireAt,
297+
etag,
298+
optimizedBuffer
299+
)
286300
} else {
287301
throw new Error('Unable to optimize buffer')
288302
}
289303
} catch (error) {
290-
sendResponse(req, res, upstreamType, upstreamBuffer)
304+
const etag = getHash([upstreamBuffer])
305+
sendResponse(req, res, upstreamType, etag, upstreamBuffer)
291306
}
292307

293308
return { finished: true }
@@ -297,11 +312,11 @@ async function writeToCacheDir(
297312
dir: string,
298313
contentType: string,
299314
expireAt: number,
315+
etag: string,
300316
buffer: Buffer
301317
) {
302318
await promises.mkdir(dir, { recursive: true })
303319
const extension = getExtension(contentType)
304-
const etag = getHash([buffer])
305320
const filename = join(dir, `${expireAt}.${etag}.${extension}`)
306321
await promises.writeFile(filename, buffer)
307322
}
@@ -310,9 +325,9 @@ function sendResponse(
310325
req: IncomingMessage,
311326
res: ServerResponse,
312327
contentType: string | null,
328+
etag: string,
313329
buffer: Buffer
314330
) {
315-
const etag = getHash([buffer])
316331
res.setHeader('Cache-Control', 'public, max-age=0, must-revalidate')
317332
if (sendEtagResponse(req, res, etag)) {
318333
return

packages/next/next-server/server/lib/squoosh/impl.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,7 @@ export async function encodePng(
8383
})
8484
return Buffer.from(r)
8585
}
86+
87+
export function noop() {
88+
return null
89+
}
Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,56 @@
1-
import JestWorker from 'jest-worker'
1+
import Worker from 'jest-worker'
22
import * as path from 'path'
3+
import os from 'os'
34
import { execOnce } from '../../../lib/utils'
45
import ImageData from './image_data'
56

7+
const CORES = (os.cpus() || { length: 1 }).length
8+
69
const getWorker = execOnce(
710
() =>
8-
new JestWorker(path.resolve(__dirname, 'impl'), {
11+
new Worker(path.resolve(__dirname, 'impl'), {
912
enableWorkerThreads: true,
1013
})
1114
)
1215

16+
// Jest-worker currently holds the latest execution's arguments and return
17+
// value in its internal queue of every worker in the pool, due to some
18+
// uncollected closures and references. This increases the memory use
19+
// tremendously so we are calling the no-op method N times so each worker
20+
// will replace the references of arguments and return value, which triggers
21+
// the GC automatically to reduce memory usage.
22+
function triggerWorkerGC() {
23+
const worker: typeof import('./impl') = getWorker() as any
24+
for (let i = 0; i < CORES; i++) {
25+
worker.noop()
26+
}
27+
}
28+
1329
export async function decodeBuffer(buffer: Buffer): Promise<ImageData> {
1430
const worker: typeof import('./impl') = getWorker() as any
15-
return ImageData.from(await worker.decodeBuffer(buffer))
31+
const data = ImageData.from(await worker.decodeBuffer(buffer))
32+
triggerWorkerGC()
33+
return data
1634
}
1735

1836
export async function rotate(
1937
image: ImageData,
2038
numRotations: number
2139
): Promise<ImageData> {
2240
const worker: typeof import('./impl') = getWorker() as any
23-
return ImageData.from(await worker.rotate(image, numRotations))
41+
const data = ImageData.from(await worker.rotate(image, numRotations))
42+
triggerWorkerGC()
43+
return data
2444
}
2545

2646
export async function resize(
2747
image: ImageData,
2848
{ width }: { width: number }
2949
): Promise<ImageData> {
3050
const worker: typeof import('./impl') = getWorker() as any
31-
return ImageData.from(await worker.resize(image, { width }))
51+
const data = ImageData.from(await worker.resize(image, { width }))
52+
triggerWorkerGC()
53+
return data
3254
}
3355

3456
export async function encodeJpeg(
@@ -37,7 +59,9 @@ export async function encodeJpeg(
3759
): Promise<Buffer> {
3860
const worker: typeof import('./impl') = getWorker() as any
3961
const o = await worker.encodeJpeg(image, { quality })
40-
return Buffer.from(o)
62+
const data = Buffer.from(o)
63+
triggerWorkerGC()
64+
return data
4165
}
4266

4367
export async function encodeWebp(
@@ -46,11 +70,15 @@ export async function encodeWebp(
4670
): Promise<Buffer> {
4771
const worker: typeof import('./impl') = getWorker() as any
4872
const o = await worker.encodeWebp(image, { quality })
49-
return Buffer.from(o)
73+
const data = Buffer.from(o)
74+
triggerWorkerGC()
75+
return data
5076
}
5177

5278
export async function encodePng(image: ImageData): Promise<Buffer> {
5379
const worker: typeof import('./impl') = getWorker() as any
5480
const o = await worker.encodePng(image)
55-
return Buffer.from(o)
81+
const data = Buffer.from(o)
82+
triggerWorkerGC()
83+
return data
5684
}

0 commit comments

Comments
 (0)