Skip to content

Commit 8352645

Browse files
committed
use already existing trackBackgroundWork helper from request context to handle next/after
1 parent e13fa25 commit 8352645

File tree

5 files changed

+33
-29
lines changed

5 files changed

+33
-29
lines changed

src/build/templates/handler-monorepo.tmpl.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default async function (req, context) {
1616
tracing.start()
1717
}
1818

19-
const requestContext = createRequestContext(req)
19+
const requestContext = createRequestContext(req, context)
2020
const tracer = getTracer()
2121

2222
const handlerResponse = await runWithRequestContext(requestContext, () => {

src/build/templates/handler.tmpl.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default async function handler(req, context) {
1313
if (process.env.NETLIFY_OTLP_TRACE_EXPORTER_URL) {
1414
tracing.start()
1515
}
16-
const requestContext = createRequestContext(req)
16+
const requestContext = createRequestContext(req, context)
1717
const tracer = getTracer()
1818

1919
const handlerResponse = await runWithRequestContext(requestContext, () => {

src/run/handlers/request-context.cts

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import { AsyncLocalStorage } from 'node:async_hooks'
22

3+
import type { Context } from '@netlify/functions'
34
import { LogLevel, systemLogger } from '@netlify/functions/internal'
45

56
import type { NetlifyCachedRouteValue } from '../../shared/cache-types.cjs'
67

78
type SystemLogger = typeof systemLogger
89

10+
// TODO: remove once https://github.com/netlify/serverless-functions-api/pull/219
11+
// is released and public types are updated
12+
export interface FutureContext extends Context {
13+
waitUntil?: (promise: Promise<unknown>) => void
14+
}
15+
916
export type RequestContext = {
1017
captureServerTiming: boolean
1118
responseCacheGetLastModified?: number
@@ -28,13 +35,17 @@ export type RequestContext = {
2835

2936
type RequestContextAsyncLocalStorage = AsyncLocalStorage<RequestContext>
3037

31-
export function createRequestContext(request?: Request): RequestContext {
38+
export function createRequestContext(request?: Request, context?: FutureContext): RequestContext {
3239
const backgroundWorkPromises: Promise<unknown>[] = []
3340

3441
return {
3542
captureServerTiming: request?.headers.has('x-next-debug-logging') ?? false,
3643
trackBackgroundWork: (promise) => {
37-
backgroundWorkPromises.push(promise)
44+
if (context?.waitUntil) {
45+
context.waitUntil(promise)
46+
} else {
47+
backgroundWorkPromises.push(promise)
48+
}
3849
},
3950
get backgroundWorkPromise() {
4051
return Promise.allSettled(backgroundWorkPromises)

src/run/handlers/server.ts

+14-19
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { OutgoingHttpHeaders } from 'http'
22

33
import { ComputeJsOutgoingMessage, toComputeResponse, toReqRes } from '@fastly/http-compute-js'
4-
import { Context } from '@netlify/functions'
54
import type { NextConfigComplete } from 'next/dist/server/config-shared.js'
65
import type { WorkerRequestHandler } from 'next/dist/server/lib/types.js'
76

@@ -16,10 +15,12 @@ import { nextResponseProxy } from '../revalidate.js'
1615

1716
import { createRequestContext, getLogger, getRequestContext } from './request-context.cjs'
1817
import { getTracer } from './tracer.cjs'
19-
import { setWaitUntil } from './wait-until.cjs'
18+
import { setupWaitUntil } from './wait-until.cjs'
2019

2120
const nextImportPromise = import('../next.cjs')
2221

22+
setupWaitUntil()
23+
2324
let nextHandler: WorkerRequestHandler, nextConfig: NextConfigComplete
2425

2526
/**
@@ -45,15 +46,9 @@ const disableFaultyTransferEncodingHandling = (res: ComputeJsOutgoingMessage) =>
4546
}
4647
}
4748

48-
// TODO: remove once https://github.com/netlify/serverless-functions-api/pull/219
49-
// is released and public types are updated
50-
interface FutureContext extends Context {
51-
waitUntil?: (promise: Promise<unknown>) => void
52-
}
53-
54-
export default async (request: Request, context: FutureContext) => {
49+
export default async (request: Request) => {
5550
const tracer = getTracer()
56-
setWaitUntil(context)
51+
5752
if (!nextHandler) {
5853
await tracer.withActiveSpan('initialize next server', async () => {
5954
// set the server config
@@ -129,19 +124,19 @@ export default async (request: Request, context: FutureContext) => {
129124
return new Response(body || null, response)
130125
}
131126

132-
if (context.waitUntil) {
133-
context.waitUntil(requestContext.backgroundWorkPromise)
134-
}
135-
136127
const keepOpenUntilNextFullyRendered = new TransformStream({
137128
async flush() {
138129
// it's important to keep the stream open until the next handler has finished
139130
await nextHandlerPromise
140-
if (!context.waitUntil) {
141-
// if waitUntil is not available, we have to keep response stream open until background promises are resolved
142-
// to ensure that all background work executes
143-
await requestContext.backgroundWorkPromise
144-
}
131+
132+
// Next.js relies on `close` event emitted by response to trigger running callback variant of `next/after`
133+
// however @fastly/http-compute-js never actually emits that event - so we have to emit it ourselves,
134+
// otherwise Next would never run the callback variant of `next/after`
135+
res.emit('close')
136+
137+
// if waitUntil is not available, we have to keep response stream open until background promises are resolved
138+
// to ensure that all background work executes
139+
await requestContext.backgroundWorkPromise
145140
},
146141
})
147142

src/run/handlers/wait-until.cts

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { getRequestContext } from './request-context.cjs'
2+
13
/**
24
* @see https://github.com/vercel/next.js/blob/canary/packages/next/src/server/after/builtin-request-context.ts
35
*/
@@ -14,15 +16,11 @@ type GlobalThisWithRequestContext = typeof globalThis & {
1416
/**
1517
* Registers a `waitUntil` to be used by Next.js for next/after
1618
*/
17-
18-
export function setWaitUntil({ waitUntil }: { waitUntil?: (promise: Promise<unknown>) => void }) {
19-
if (!waitUntil) {
20-
return
21-
}
19+
export function setupWaitUntil() {
2220
// eslint-disable-next-line @typescript-eslint/no-extra-semi
2321
;(globalThis as GlobalThisWithRequestContext)[NEXT_REQUEST_CONTEXT_SYMBOL] = {
2422
get() {
25-
return { waitUntil }
23+
return { waitUntil: getRequestContext()?.trackBackgroundWork }
2624
},
2725
}
2826
}

0 commit comments

Comments
 (0)