-
Notifications
You must be signed in to change notification settings - Fork 87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: track revalidate / cdn purge to ensure it finishes execution and is not suspended mid-execution #2490
fix: track revalidate / cdn purge to ensure it finishes execution and is not suspended mid-execution #2490
Changes from all commits
c6a8699
bd9771b
55c7843
e88dd8c
b475810
5c1eddb
08f666d
dd43ce8
a72a0cd
17d9aec
2a054cc
1e2f02b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -326,19 +326,33 @@ export class NetlifyCacheHandler implements CacheHandler { | |
if (requestContext?.didPagesRouterOnDemandRevalidate) { | ||
const tag = `_N_T_${key === '/index' ? '/' : key}` | ||
getLogger().debug(`Purging CDN cache for: [${tag}]`) | ||
purgeCache({ tags: [tag] }).catch((error) => { | ||
// TODO: add reporting here | ||
getLogger() | ||
.withError(error) | ||
.error(`[NetlifyCacheHandler]: Purging the cache for tag ${tag} failed`) | ||
}) | ||
requestContext.trackBackgroundWork( | ||
purgeCache({ tags: [tag] }).catch((error) => { | ||
// TODO: add reporting here | ||
getLogger() | ||
.withError(error) | ||
.error(`[NetlifyCacheHandler]: Purging the cache for tag ${tag} failed`) | ||
}), | ||
) | ||
} | ||
} | ||
}) | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
async revalidateTag(tagOrTags: string | string[], ...args: any) { | ||
const revalidateTagPromise = this.doRevalidateTag(tagOrTags, ...args) | ||
|
||
const requestContext = getRequestContext() | ||
if (requestContext) { | ||
requestContext.trackBackgroundWork(revalidateTagPromise) | ||
} | ||
|
||
return revalidateTagPromise | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
private async doRevalidateTag(tagOrTags: string | string[], ...args: any) { | ||
Comment on lines
343
to
+355
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. App Router |
||
getLogger().withFields({ tagOrTags, args }).debug('NetlifyCacheHandler.revalidateTag') | ||
|
||
const tags = Array.isArray(tagOrTags) ? tagOrTags : [tagOrTags] | ||
|
@@ -357,7 +371,7 @@ export class NetlifyCacheHandler implements CacheHandler { | |
}), | ||
) | ||
|
||
purgeCache({ tags }).catch((error) => { | ||
await purgeCache({ tags }).catch((error) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this doesn't change much, because |
||
// TODO: add reporting here | ||
getLogger() | ||
.withError(error) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,34 @@ | ||
import type { ServerResponse } from 'node:http' | ||
import { isPromise } from 'node:util/types' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TIL |
||
|
||
import type { NextApiResponse } from 'next' | ||
|
||
import type { RequestContext } from './handlers/request-context.cjs' | ||
|
||
type ResRevalidateMethod = NextApiResponse['revalidate'] | ||
|
||
function isRevalidateMethod( | ||
key: string, | ||
nextResponseField: unknown, | ||
): nextResponseField is ResRevalidateMethod { | ||
return key === 'revalidate' && typeof nextResponseField === 'function' | ||
} | ||
|
||
// Needing to proxy the response object to intercept the revalidate call for on-demand revalidation on page routes | ||
export const nextResponseProxy = (res: ServerResponse, requestContext: RequestContext) => { | ||
return new Proxy(res, { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
get(target: any[string], key: string) { | ||
const originalValue = target[key] | ||
if (key === 'revalidate') { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
return async function newRevalidate(...args: any[]) { | ||
get(target: ServerResponse, key: string) { | ||
const originalValue = Reflect.get(target, key) | ||
if (isRevalidateMethod(key, originalValue)) { | ||
return function newRevalidate(...args: Parameters<ResRevalidateMethod>) { | ||
requestContext.didPagesRouterOnDemandRevalidate = true | ||
return originalValue?.apply(target, args) | ||
|
||
const result = originalValue.apply(target, args) | ||
if (result && isPromise(result)) { | ||
requestContext.trackBackgroundWork(result) | ||
} | ||
|
||
return result | ||
} | ||
} | ||
return originalValue | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export default async function handler(req, res) { | ||
try { | ||
const pathToPurge = req.query.path ?? '/static/revalidate-manual' | ||
// res.revalidate returns a promise that can be awaited to wait for the revalidation to complete | ||
// if user doesn't await it, we still want to ensure the revalidation is completed, so we internally track | ||
// this as "background work" to ensure it completes before function suspends execution | ||
res.revalidate(pathToPurge) | ||
return res.json({ code: 200, message: 'success' }) | ||
} catch (err) { | ||
return res.status(500).send({ code: 500, message: err.message }) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export default async function handler(req, res) { | ||
try { | ||
const pathToPurge = req.query.path ?? '/static/revalidate-manual' | ||
// res.revalidate returns a promise that can be awaited to wait for the revalidation to complete | ||
// if user doesn't await it, we still want to ensure the revalidation is completed, so we internally track | ||
// this as "background work" to ensure it completes before function suspends execution | ||
res.revalidate(pathToPurge) | ||
return res.json({ code: 200, message: 'success' }) | ||
} catch (err) { | ||
return res.status(500).send({ code: 500, message: err.message }) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
making sure we track
purgeCache
part of the pages router on-demand revalidation so it completes before function execution gets suspended