Skip to content
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: ensure background work is finished when response has 3xx or 5xx status code #2742

Merged
merged 3 commits into from
Jan 22, 2025

Conversation

pieh
Copy link
Contributor

@pieh pieh commented Jan 16, 2025

Description

We are skipping code path that is ensuring background work finishes before lambda suspends execution if the response is 3xx or 5xx currently. It seems like most notable problem happens with 304 respones - I didn't have much luck reproducing it with redirects, but I managed to do so with 304

Tests

Added failing test without any code changes first https://github.com/opennextjs/opennextjs-netlify/actions/runs/12816826577/job/35738662910?pr=2742#step:13:122

Relevant links (GitHub issues, etc.) or a picture of cute animal

Copy link

github-actions bot commented Jan 16, 2025

📊 Package size report   -0%↓

File Before (Size / Gzip) After (Size / Gzip)
dist/run/handlers/server.js 141.1 kB / 33.0 kB -0.02%↓141.0 kB / -0.04%↓33.0 kB
Total (Includes all files) 5.8 MB / 1.2 MB -0%↓5.8 MB / -0%↓1.2 MB
Tarball size 1.2 MB -0%↓1.2 MB
Unchanged files
File Size (Size / Gzip)
dist/build/advanced-api-routes.js 4.3 kB / 1.4 kB
dist/build/cache.js 1.0 kB / 414 B
dist/build/content/next-shims/telemetry-storage.cjs 1.6 kB / 659 B
dist/build/content/prerendered.js 9.2 kB / 2.8 kB
dist/build/content/server.js 8.7 kB / 2.8 kB
dist/build/content/static.js 3.9 kB / 1.2 kB
dist/build/functions/edge.js 20.1 kB / 5.2 kB
dist/build/functions/server.js 5.0 kB / 1.6 kB
dist/build/image-cdn.js 54.0 kB / 11.1 kB
dist/build/plugin-context.js 10.1 kB / 3.0 kB
dist/build/templates/handler-monorepo.tmpl.js 1.6 kB / 671 B
dist/build/templates/handler.tmpl.js 1.5 kB / 622 B
dist/build/verification.js 4.5 kB / 1.5 kB
dist/esm-chunks/chunk-5QSXBV7L.js 2.4 kB / 842 B
dist/esm-chunks/chunk-APO262HE.js 61.2 kB / 11.1 kB
dist/esm-chunks/chunk-GNGHTHMQ.js 55.6 kB / 9.7 kB
dist/esm-chunks/chunk-KGYJQ2U2.js 186.5 kB / 32.9 kB
dist/esm-chunks/chunk-OEQOKJGE.js 2.3 kB / 977 B
dist/esm-chunks/package-MI2MBCHL.js 3.6 kB / 1.4 kB
dist/index.js 3.4 kB / 1.1 kB
dist/run/config.js 1.2 kB / 595 B
dist/run/constants.js 516 B / 308 B
dist/run/handlers/cache.cjs 19.6 kB / 5.3 kB
dist/run/handlers/request-context.cjs 5.5 kB / 1.7 kB
dist/run/handlers/tracer.cjs 29.9 kB / 6.3 kB
dist/run/handlers/tracing.js 3.0 MB / 418.4 kB
dist/run/handlers/wait-until.cjs 1.4 kB / 665 B
dist/run/headers.js 7.9 kB / 2.4 kB
dist/run/next.cjs 23.6 kB / 5.8 kB
dist/run/regional-blob-store.cjs 19.6 kB / 5.5 kB
dist/run/revalidate.js 1.0 kB / 475 B
dist/shared/blobkey.js 742 B / 399 B
dist/shared/cache-types.cjs 1.3 kB / 566 B
edge-runtime/lib/headers.ts 1.9 kB / 841 B
edge-runtime/lib/logging.ts 115 B / 121 B
edge-runtime/lib/middleware.ts 1.9 kB / 807 B
edge-runtime/lib/next-request.ts 3.3 kB / 1.1 kB
edge-runtime/lib/response.ts 9.2 kB / 2.9 kB
edge-runtime/lib/routing.ts 15.1 kB / 3.9 kB
edge-runtime/lib/util.test.ts 1.6 kB / 356 B
edge-runtime/lib/util.ts 3.7 kB / 1.3 kB
edge-runtime/matchers.json 3 B / 23 B
edge-runtime/middleware.ts 2.4 kB / 1.0 kB
edge-runtime/next.config.json 3 B / 23 B
edge-runtime/README.md 992 B / 509 B
edge-runtime/shim/index.js 1.5 kB / 717 B
edge-runtime/vendor.ts 745 B / 312 B
edge-runtime/vendor/deno.land/std@0.175.0/_util/asserts.ts 854 B / 461 B
edge-runtime/vendor/deno.land/std@0.175.0/_util/os.ts 644 B / 355 B
edge-runtime/vendor/deno.land/std@0.175.0/async/abortable.ts 4.0 kB / 1.0 kB
edge-runtime/vendor/deno.land/std@0.175.0/async/deadline.ts 974 B / 544 B
edge-runtime/vendor/deno.land/std@0.175.0/async/debounce.ts 2.2 kB / 956 B
edge-runtime/vendor/deno.land/std@0.175.0/async/deferred.ts 1.5 kB / 798 B
edge-runtime/vendor/deno.land/std@0.175.0/async/delay.ts 1.8 kB / 845 B
edge-runtime/vendor/deno.land/std@0.175.0/async/mod.ts 465 B / 241 B
edge-runtime/vendor/deno.land/std@0.175.0/async/mux_async_iterator.ts 2.5 kB / 1.1 kB
edge-runtime/vendor/deno.land/std@0.175.0/async/pool.ts 3.2 kB / 1.4 kB
edge-runtime/vendor/deno.land/std@0.175.0/async/retry.ts 2.4 kB / 1.0 kB
edge-runtime/vendor/deno.land/std@0.175.0/async/tee.ts 2.1 kB / 924 B
edge-runtime/vendor/deno.land/std@0.175.0/bytes/index_of_needle.ts 1.4 kB / 668 B
edge-runtime/vendor/deno.land/std@0.175.0/crypto/timing_safe_equal.ts 875 B / 442 B
edge-runtime/vendor/deno.land/std@0.175.0/datetime/to_imf.ts 1.3 kB / 681 B
edge-runtime/vendor/deno.land/std@0.175.0/encoding/base64.ts 2.5 kB / 1.0 kB
edge-runtime/vendor/deno.land/std@0.175.0/encoding/base64url.ts 2.0 kB / 872 B
edge-runtime/vendor/deno.land/std@0.175.0/flags/mod.ts 22.6 kB / 5.9 kB
edge-runtime/vendor/deno.land/std@0.175.0/fmt/colors.ts 12.4 kB / 2.7 kB
edge-runtime/vendor/deno.land/std@0.175.0/fmt/printf.ts 27.7 kB / 7.7 kB
edge-runtime/vendor/deno.land/std@0.175.0/http/cookie.ts 11.5 kB / 3.6 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/_core.ts 2.3 kB / 716 B
edge-runtime/vendor/deno.land/std@0.175.0/node/_events.d.ts 27.2 kB / 5.8 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/_events.mjs 28.0 kB / 7.4 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/_global.d.ts 1.7 kB / 650 B
edge-runtime/vendor/deno.land/std@0.175.0/node/_next_tick.ts 5.0 kB / 1.4 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/_process/exiting.ts 138 B / 138 B
edge-runtime/vendor/deno.land/std@0.175.0/node/_process/process.ts 3.8 kB / 1.4 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/_process/stdio.mjs 336 B / 233 B
edge-runtime/vendor/deno.land/std@0.175.0/node/_process/streams.mjs 4.0 kB / 1.4 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/_stream.d.ts 53.2 kB / 11.9 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/_stream.mjs 91.2 kB / 25.4 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/_util/_util_callbackify.ts 4.3 kB / 1.7 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/_utils.ts 5.9 kB / 2.0 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/assert.ts 23.1 kB / 4.4 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/assertion_error.ts 19.6 kB / 6.1 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/async_hooks.ts 7.7 kB / 2.1 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/buffer.ts 262 B / 204 B
edge-runtime/vendor/deno.land/std@0.175.0/node/events.ts 303 B / 221 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/_libuv_winerror.ts 7.8 kB / 1.9 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/_listen.ts 561 B / 342 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/_node.ts 443 B / 335 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/_timingSafeEqual.ts 479 B / 268 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/_utils.ts 2.4 kB / 938 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/_winerror.ts 354.4 kB / 64.4 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/ares.ts 2.4 kB / 1.1 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/async_wrap.ts 4.0 kB / 1.8 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/buffer.ts 3.5 kB / 1.3 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/cares_wrap.ts 15.2 kB / 3.9 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/config.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/connection_wrap.ts 2.6 kB / 1.3 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/constants.ts 21.5 kB / 5.1 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/contextify.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/credentials.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/crypto.ts 448 B / 244 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/errors.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/fs_dir.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/fs_event_wrap.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/fs.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/handle_wrap.ts 1.8 kB / 1.0 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/heap_utils.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/http_parser.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/icu.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/inspector.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/js_stream.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/messaging.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/mod.ts 3.1 kB / 955 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/module_wrap.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/native_module.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/natives.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/node_file.ts 2.9 kB / 1.5 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/node_options.ts 1.8 kB / 989 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/options.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/os.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/performance.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/pipe_wrap.ts 10.4 kB / 3.3 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/process_methods.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/report.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/serdes.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/signal_wrap.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/spawn_sync.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/stream_wrap.ts 9.3 kB / 2.8 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/string_decoder.ts 504 B / 261 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/symbols.ts 1.4 kB / 828 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/task_queue.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/tcp_wrap.ts 13.1 kB / 3.7 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/timers.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/tls_wrap.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/trace_events.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/tty_wrap.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/types.ts 5.7 kB / 1.4 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/udp_wrap.ts 12.4 kB / 3.6 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/url.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/util.ts 4.0 kB / 1.8 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/uv.ts 20.1 kB / 3.8 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/v8.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/worker.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal_binding/zlib.ts 87 B / 104 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/buffer.d.ts 73.6 kB / 12.1 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/buffer.mjs 66.1 kB / 10.6 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/crypto/_keys.ts 463 B / 262 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/crypto/constants.ts 252 B / 173 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/error_codes.ts 322 B / 250 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/errors.ts 78.9 kB / 17.4 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/fixed_queue.ts 4.4 kB / 1.2 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/hide_stack_frames.ts 550 B / 377 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/net.ts 3.1 kB / 1.5 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/normalize_encoding.mjs 2.1 kB / 500 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/options.ts 1.7 kB / 959 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/primordials.mjs 1.8 kB / 431 B
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/process/per_thread.mjs 7.8 kB / 2.3 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/readline/callbacks.mjs 3.8 kB / 1.4 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/readline/utils.mjs 14.3 kB / 3.7 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/streams/destroy.mjs 6.9 kB / 1.8 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/streams/end-of-stream.mjs 7.1 kB / 1.9 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/streams/utils.mjs 5.9 kB / 1.2 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/util.mjs 4.0 kB / 1.4 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/util/comparisons.ts 16.6 kB / 3.8 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/util/debuglog.ts 3.2 kB / 1.4 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/util/inspect.mjs 71.5 kB / 19.8 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/util/types.ts 3.7 kB / 1.3 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/internal/validators.mjs 8.0 kB / 2.1 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/process.ts 19.4 kB / 5.2 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/stream.ts 671 B / 346 B
edge-runtime/vendor/deno.land/std@0.175.0/node/string_decoder.ts 10.3 kB / 3.3 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/util.ts 7.8 kB / 2.2 kB
edge-runtime/vendor/deno.land/std@0.175.0/node/util/types.ts 199 B / 153 B
edge-runtime/vendor/deno.land/std@0.175.0/path/_constants.ts 2.0 kB / 727 B
edge-runtime/vendor/deno.land/std@0.175.0/path/_interface.ts 728 B / 369 B
edge-runtime/vendor/deno.land/std@0.175.0/path/_util.ts 5.0 kB / 1.6 kB
edge-runtime/vendor/deno.land/std@0.175.0/path/common.ts 1.2 kB / 607 B
edge-runtime/vendor/deno.land/std@0.175.0/path/glob.ts 12.7 kB / 3.9 kB
edge-runtime/vendor/deno.land/std@0.175.0/path/mod.ts 1.4 kB / 690 B
edge-runtime/vendor/deno.land/std@0.175.0/path/posix.ts 13.9 kB / 3.7 kB
edge-runtime/vendor/deno.land/std@0.175.0/path/separator.ts 259 B / 209 B
edge-runtime/vendor/deno.land/std@0.175.0/path/win32.ts 28.5 kB / 6.4 kB
edge-runtime/vendor/deno.land/std@0.175.0/streams/write_all.ts 2.2 kB / 598 B
edge-runtime/vendor/deno.land/std@0.175.0/testing/_diff.ts 11.6 kB / 3.6 kB
edge-runtime/vendor/deno.land/std@0.175.0/testing/_format.ts 705 B / 462 B
edge-runtime/vendor/deno.land/std@0.175.0/testing/asserts.ts 25.5 kB / 5.7 kB
edge-runtime/vendor/deno.land/std@0.175.0/types.d.ts 4.2 kB / 1.2 kB
edge-runtime/vendor/deno.land/x/htmlrewriter@v1.0.0/pkg/htmlrewriter_bg.wasm 573.2 kB / 262.7 kB
edge-runtime/vendor/deno.land/x/htmlrewriter@v1.0.0/pkg/htmlrewriter.js 31.0 kB / 4.7 kB
edge-runtime/vendor/deno.land/x/htmlrewriter@v1.0.0/src/index.ts 2.6 kB / 989 B
edge-runtime/vendor/deno.land/x/htmlrewriter@v1.0.0/src/types.d.ts 2.1 kB / 446 B
edge-runtime/vendor/deno.land/x/path_to_regexp@v6.2.1/index.ts 15.4 kB / 4.2 kB
edge-runtime/vendor/import_map.json 148 B / 111 B
edge-runtime/vendor/v1-7-0--edge-utils.netlify.app/logger/logger.ts 3.2 kB / 747 B
edge-runtime/vendor/v1-7-0--edge-utils.netlify.app/logger/mod.ts 29 B / 49 B
manifest.yml 31 B / 51 B
package.json 3.2 kB / 1.2 kB
README.md 2.8 kB / 1.2 kB

🤖 This report was automatically generated by pkg-size-action

@pieh pieh force-pushed the fix/dont-suspend-background-work-for-3xx branch from fe45660 to 62ed00c Compare January 16, 2025 20:12
Comment on lines +504 to +517
await page.goto(new URL(`always-the-same-body/${slug}`, pageRouter.url).href)

await new Promise((resolve) => setTimeout(resolve, 15_000))

await page.goto(new URL(`always-the-same-body/${slug}`, pageRouter.url).href)

await new Promise((resolve) => setTimeout(resolve, 15_000))

await page.goto(new URL(`always-the-same-body/${slug}`, pageRouter.url).href)

await new Promise((resolve) => setTimeout(resolve, 15_000))

// keep lambda executing to allow for background getStaticProps to finish in case background work execution was suspended
await fetch(new URL(`api/sleep-5`, pageRouter.url).href)
Copy link
Contributor Author

@pieh pieh Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit unclear to me why I need to make 3 requests, but this whole thing result in following logs that I've added to getStaticProps function that is being tested with those routes (+ some commentary I added to hopefully explain the logs):

# first request - this will always be very first request for this path, because it's
# not prerendered - this has `fallback: 'blocking'` so very first request is
# expected to execute `getStaticProps` in foreground and not background
Jan 16, 09:10:57 PM: 4f18552f INFO   [timestamp] 1737058256930 getStaticProps start
Jan 16, 09:10:57 PM: 4f18552f INFO   [timestamp] 1737058256930 getStaticProps start stored
Jan 16, 09:11:02 PM: 4f18552f INFO   [timestamp] 1737058256930 getStaticProps end (duration: 5s)
Jan 16, 09:11:02 PM: 4f18552f INFO   [timestamp] 1737058256930 getStaticProps end stored
Jan 16, 09:11:02 PM: 4f18552f Duration: 5136.12 ms	Memory Usage: 177 MB
# second request 15s after first one finished - this should generate exactly
# same thing - however response is 200 not 304 🤷 
Jan 16, 09:11:17 PM: 21a21fc8 INFO   [timestamp] 1737058256930 getStaticProps start
Jan 16, 09:11:18 PM: 21a21fc8 INFO   [timestamp] 1737058256930 getStaticProps start stored
Jan 16, 09:11:22 PM: 21a21fc8 INFO   [timestamp] 1737058256930 getStaticProps end (duration: 5s)
Jan 16, 09:11:23 PM: 21a21fc8 INFO   [timestamp] 1737058256930 getStaticProps end stored
Jan 16, 09:11:23 PM: 21a21fc8 Duration: 5119.96 ms	Memory Usage: 177 MB
# third request 15s after second one finished (but only 10 seconds after
# getStaticProps triggered by second request finished - that's because
# it was executing in background) - we can see that we DO start executing
# getStaticProps - but it doesn't finish before lambda execution finishes/suspends
# this one is 304 response (which is currently problematic one)
Jan 16, 09:11:33 PM: 13effc77 INFO   [timestamp] 1737058256930 getStaticProps start
Jan 16, 09:11:33 PM: 13effc77 Duration: 63.11 ms	Memory Usage: 177 MB
# now we were just sleeping for 15 seconds and after we are hitting
# an api endpoint that is being handled by same next.js server handler
# which allows to resume previously suspended execution - you can also
# see it has same execution context as previous request (13effc77) despite
# lambda execution seemingly was finished (log line with Duration and
# Memory Usage signifies end of execution) - you can see that now
# logged duration inside getStaticProps says 15s instead of expected ~5s 
Jan 16, 09:11:48 PM: 13effc77 INFO   [timestamp] 1737058256930 getStaticProps end (duration: 15.722s)
Jan 16, 09:11:48 PM: 13effc77 INFO   [timestamp] 1737058256930 getStaticProps start stored
Jan 16, 09:11:48 PM: 4974d6c2 INFO   [timestamp] 1737058256930 getStaticProps end stored
Jan 16, 09:11:53 PM: 4974d6c2 Duration: 5009.36 ms	Memory Usage: 177 MB

@pieh pieh changed the title test: add test for ISR case that always return same body fix: ensure background work is finished when response has 3xx or 5xx status code Jan 16, 2025
@pieh pieh force-pushed the fix/dont-suspend-background-work-for-3xx branch from 2536087 to 3fd8d77 Compare January 16, 2025 21:07
Comment on lines 116 to 126
// Temporary workaround for an issue where sending a response with an empty
// body causes an unhandled error. This doesn't catch everything, but redirects are the
// most common case of sending empty bodies. We can't check it directly because these are streams.
// The side effect is that responses which do contain data will not be streamed to the client,
// but that's fine for redirects.
// TODO: Remove once a fix has been rolled out.
if ((response.status > 300 && response.status < 400) || response.status >= 500) {
const body = await response.text()
return new Response(body || null, response)
}

Copy link
Contributor Author

@pieh pieh Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally should be able to remove this because there was fix rolled out ( https://github.com/netlify/serverless-functions-api/pull/203 )

But this doesn't help fix the issue with 304 cases not ensuring background work execution because

return new Response(response.body?.pipeThrough(keepOpenUntilNextFullyRendered), response)

response.body is null for 304s (?) so we still don't use keepOpenUntilNextFullyRendered

We also can't force non-null body for those because that seems not allowed:

> new Response('', { status: 304 })
VM957:1 Uncaught TypeError: Failed to construct 'Response': Response with null body status cannot have body
    at <anonymous>:1:1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this might work?

if ((response.status > 300 && response.status < 400) || response.status >= 500) {
  const body = await response.text()
  // Create an empty stream for null bodies, otherwise create a stream from the body text
  const stream = body 
    ? new TextEncoder().encode(body).stream() 
    : new ReadableStream({ start(controller) { controller.close() } });
  return new Response(stream.pipeThrough(keepOpenUntilNextFullyRendered), response)
}

Copy link
Contributor Author

@pieh pieh Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

> new Response(new ReadableStream({ start(controller) { controller.close() } }), { status: 304 })
VM447:1 Uncaught TypeError: Failed to construct 'Response': Response with null body status cannot have body
    at <anonymous>:1:1

this is pretty much same problem - for 304 body has to be null - it can't be empty stream or string :/

I did try few alternatives similar to this before, but it all was resulting in crashes when 304 was produced and body essentially was not null

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. I might be misunderstanding this, but could we abstract the background work completion and do it both places. Something like:

const waitForBackgroundWork = async () => {
  await nextHandlerPromise
  res.emit('close')
  await requestContext.backgroundWorkPromise
}

if ((response.status > 300 && response.status < 400) || response.status >= 500) {
  const body = await response.text()
  await waitForBackgroundWork()
  return new Response(body || null, response)
}

const keepOpenUntilNextFullyRendered = new TransformStream({
  async flush() {
    await waitForBackgroundWork()
  },
})

return new Response(response.body?.pipeThrough(keepOpenUntilNextFullyRendered), response)

It obviously won't stream for those status codes, but that shouldn't matter too much?

Copy link
Contributor Author

@pieh pieh Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We certainly can do that and this would solve the issue with background work being suspended, but it will completely block response until background work finishes for those 304 cases

This feels like worst of both worlds - because it would still serve stale - but it only would do that after background work to generate new response would finish, but maybe this is just what we need to do :/

It feels like if we are blocking on background work - it would be better to just serve freshly generated response instead - but a) this kind of break expectations of SWR behavior and b) it's not obvious how to exactly do that without completely disabling SWR (we can do that with CacheHandler.get returning null for stale entries - but if we would want to do that only for 304 cases - at the point we could force it - we have no idea if 304 would be produced)

The ideal solution is having context.waitUntil work, but this is not rolled out yet (not sure of the status).

Another somewhat hacky idea - initiate new request to same lambda instance (not sure how to ensure that) with specific payload that next-runtime would intercept and all it would do is to wait for any background work initiated by previous invocations to finish and wouldn't go through next-server at all - kind of keeping lambda warm with "meaningless" request - basically doing similar as I did here for the test:

// keep lambda executing to allow for background getStaticProps to finish in case background work execution was suspended
await fetch(new URL(`api/sleep-5`, pageRouter.url).href)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I'll make the change you suggested, to at least have a working fix

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have any better ideas so I think just awaiting for background work before responding with body-less response will have to do here - done that in last commit - c8e103b?diff=split&w=1

…status code
@pieh pieh force-pushed the fix/dont-suspend-background-work-for-3xx branch from 3fd8d77 to c8e103b Compare January 17, 2025 19:06
@pieh pieh marked this pull request as ready for review January 20, 2025 16:34
Copy link
Contributor

@orinokai orinokai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that all looks good. At first I thought we needed an early return for responses without a body, but I see now that optional chaining will prevent attempting to pipe through in that case, so yep - all good!

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
@pieh pieh enabled auto-merge (squash) January 22, 2025 10:46
@pieh pieh merged commit ff2632f into main Jan 22, 2025
27 checks passed
@pieh pieh deleted the fix/dont-suspend-background-work-for-3xx branch January 22, 2025 11:05
@pieh pieh mentioned this pull request Jan 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants