Skip to content

Commit

Permalink
fix memory usage; optimize handler
Browse files Browse the repository at this point in the history
  • Loading branch information
shuding committed Mar 12, 2021
1 parent 014a2f8 commit f7ecdca
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 15 deletions.
29 changes: 22 additions & 7 deletions packages/next/next-server/server/image-optimizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,15 @@ export async function imageOptimizer(
const animate =
ANIMATABLE_TYPES.includes(upstreamType) && isAnimated(upstreamBuffer)
if (vector || animate) {
await writeToCacheDir(hashDir, upstreamType, expireAt, upstreamBuffer)
sendResponse(req, res, upstreamType, upstreamBuffer)
const etag = getHash([upstreamBuffer])
sendResponse(req, res, upstreamType, etag, upstreamBuffer)
await writeToCacheDir(
hashDir,
upstreamType,
expireAt,
etag,
upstreamBuffer
)
return { finished: true }
}
}
Expand Down Expand Up @@ -281,13 +288,21 @@ export async function imageOptimizer(
}

if (optimizedBuffer) {
await writeToCacheDir(hashDir, contentType, expireAt, optimizedBuffer)
sendResponse(req, res, contentType, optimizedBuffer)
const etag = getHash([optimizedBuffer])
sendResponse(req, res, contentType, etag, optimizedBuffer)
await writeToCacheDir(
hashDir,
contentType,
expireAt,
etag,
optimizedBuffer
)
} else {
throw new Error('Unable to optimize buffer')
}
} catch (error) {
sendResponse(req, res, upstreamType, upstreamBuffer)
const etag = getHash([upstreamBuffer])
sendResponse(req, res, upstreamType, etag, upstreamBuffer)
}

return { finished: true }
Expand All @@ -297,11 +312,11 @@ async function writeToCacheDir(
dir: string,
contentType: string,
expireAt: number,
etag: string,
buffer: Buffer
) {
await promises.mkdir(dir, { recursive: true })
const extension = getExtension(contentType)
const etag = getHash([buffer])
const filename = join(dir, `${expireAt}.${etag}.${extension}`)
await promises.writeFile(filename, buffer)
}
Expand All @@ -310,9 +325,9 @@ function sendResponse(
req: IncomingMessage,
res: ServerResponse,
contentType: string | null,
etag: string,
buffer: Buffer
) {
const etag = getHash([buffer])
res.setHeader('Cache-Control', 'public, max-age=0, must-revalidate')
if (sendEtagResponse(req, res, etag)) {
return
Expand Down
4 changes: 4 additions & 0 deletions packages/next/next-server/server/lib/squoosh/impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,7 @@ export async function encodePng(
})
return Buffer.from(r)
}

export function noop() {
return null
}
44 changes: 36 additions & 8 deletions packages/next/next-server/server/lib/squoosh/main.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,56 @@
import JestWorker from 'jest-worker'
import Worker from 'jest-worker'
import * as path from 'path'
import os from 'os'
import { execOnce } from '../../../lib/utils'
import ImageData from './image_data'

const CORES = (os.cpus() || { length: 1 }).length

const getWorker = execOnce(
() =>
new JestWorker(path.resolve(__dirname, 'impl'), {
new Worker(path.resolve(__dirname, 'impl'), {
enableWorkerThreads: true,
})
)

// Jest-worker currently holds the latest execution's arguments and return
// value in its internal queue of every worker in the pool, due to some
// uncollected closures and references. This increases the memory use
// tremendously so we are calling the no-op method N times so each worker
// will replace the references of arguments and return value, which triggers
// the GC automatically to reduce memory usage.
function triggerWorkerGC() {
const worker: typeof import('./impl') = getWorker() as any
for (let i = 0; i < CORES; i++) {
worker.noop()
}
}

export async function decodeBuffer(buffer: Buffer): Promise<ImageData> {
const worker: typeof import('./impl') = getWorker() as any
return ImageData.from(await worker.decodeBuffer(buffer))
const data = ImageData.from(await worker.decodeBuffer(buffer))
triggerWorkerGC()
return data
}

export async function rotate(
image: ImageData,
numRotations: number
): Promise<ImageData> {
const worker: typeof import('./impl') = getWorker() as any
return ImageData.from(await worker.rotate(image, numRotations))
const data = ImageData.from(await worker.rotate(image, numRotations))
triggerWorkerGC()
return data
}

export async function resize(
image: ImageData,
{ width }: { width: number }
): Promise<ImageData> {
const worker: typeof import('./impl') = getWorker() as any
return ImageData.from(await worker.resize(image, { width }))
const data = ImageData.from(await worker.resize(image, { width }))
triggerWorkerGC()
return data
}

export async function encodeJpeg(
Expand All @@ -37,7 +59,9 @@ export async function encodeJpeg(
): Promise<Buffer> {
const worker: typeof import('./impl') = getWorker() as any
const o = await worker.encodeJpeg(image, { quality })
return Buffer.from(o)
const data = Buffer.from(o)
triggerWorkerGC()
return data
}

export async function encodeWebp(
Expand All @@ -46,11 +70,15 @@ export async function encodeWebp(
): Promise<Buffer> {
const worker: typeof import('./impl') = getWorker() as any
const o = await worker.encodeWebp(image, { quality })
return Buffer.from(o)
const data = Buffer.from(o)
triggerWorkerGC()
return data
}

export async function encodePng(image: ImageData): Promise<Buffer> {
const worker: typeof import('./impl') = getWorker() as any
const o = await worker.encodePng(image)
return Buffer.from(o)
const data = Buffer.from(o)
triggerWorkerGC()
return data
}

0 comments on commit f7ecdca

Please sign in to comment.