From 1ea9ac6fe07738d12d0d66794cdbabd9a0956a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Thu, 21 May 2026 16:47:37 -0700 Subject: [PATCH 1/3] fix: purge ephemeral backends between requests --- .../reusable-sandboxes/src/dynamic-backend.js | 32 +++++++++++++++++++ .../fixtures/reusable-sandboxes/src/index.js | 1 + .../fixtures/reusable-sandboxes/tests.json | 5 ++- runtime/fastly/builtins/backend.cpp | 15 +++++++++ runtime/fastly/builtins/backend.h | 3 +- runtime/fastly/handler.cpp | 28 ++++++++++++++++ 6 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 integration-tests/js-compute/fixtures/reusable-sandboxes/src/dynamic-backend.js diff --git a/integration-tests/js-compute/fixtures/reusable-sandboxes/src/dynamic-backend.js b/integration-tests/js-compute/fixtures/reusable-sandboxes/src/dynamic-backend.js new file mode 100644 index 0000000000..4581e8827d --- /dev/null +++ b/integration-tests/js-compute/fixtures/reusable-sandboxes/src/dynamic-backend.js @@ -0,0 +1,32 @@ +/// +import { Backend } from 'fastly:backend'; +import { assert } from './assertions.js'; +import { isRunningLocally, routes } from './routes.js'; + +routes.set('/backend/ephemeral1', async () => { + if (isRunningLocally()) { + return; + } + assert(!Backend.exists('ephemeral')); + new Backend({ + name: 'ephemeral', + target: 'http-me.fastly.dev', + hostOverride: 'http-me.fastly.dev', + useSSL: true, + }); + assert(Backend.exists('ephemeral')); +}); + +routes.set('/backend/ephemeral2', async () => { + if (isRunningLocally()) { + return; + } + assert(!Backend.exists('ephemeral')); + new Backend({ + name: 'ephemeral', + target: 'http-me.fastly.dev', + hostOverride: 'http-me.fastly.dev', + useSSL: true, + }); + assert(Backend.exists('ephemeral')); +}); diff --git a/integration-tests/js-compute/fixtures/reusable-sandboxes/src/index.js b/integration-tests/js-compute/fixtures/reusable-sandboxes/src/index.js index 00faeb4372..21dee146fd 100644 --- a/integration-tests/js-compute/fixtures/reusable-sandboxes/src/index.js +++ b/integration-tests/js-compute/fixtures/reusable-sandboxes/src/index.js @@ -9,6 +9,7 @@ import { setReusableSandboxOptions } from 'fastly:experimental'; setReusableSandboxOptions({ maxRequests: 9001 }); +import './dynamic-backend.js'; import './interleave.js'; addEventListener('fetch', (event) => { diff --git a/integration-tests/js-compute/fixtures/reusable-sandboxes/tests.json b/integration-tests/js-compute/fixtures/reusable-sandboxes/tests.json index 949059d38d..2f480af735 100644 --- a/integration-tests/js-compute/fixtures/reusable-sandboxes/tests.json +++ b/integration-tests/js-compute/fixtures/reusable-sandboxes/tests.json @@ -99,5 +99,8 @@ "QuuxName": "QuuxValue" } } - } + }, + + "session #3, request #1: GET /backend/ephemeral1": {}, + "session #3, request #2: GET /backend/ephemeral2": {} } diff --git a/runtime/fastly/builtins/backend.cpp b/runtime/fastly/builtins/backend.cpp index c3340f924c..fdb44fab57 100644 --- a/runtime/fastly/builtins/backend.cpp +++ b/runtime/fastly/builtins/backend.cpp @@ -1808,6 +1808,21 @@ void Backend::finalize(JS::GCContext *gcx, JSObject *obj) { free(backend); } +bool Backend::restore_global_state(JSContext *cx) { + JS::Rooted props(cx, cx); + if (!JS_Enumerate(cx, Backend::backends, &props)) { + return false; + } + JS::RootedValue backend(cx); + for (uint32_t i = 0, len = props.length(); i < len; i++) { + if (!JS_GetPropertyById(cx, Backend::backends, props[i], &backend)) { + return false; + } + JS_DeletePropertyById(cx, Backend::backends, props[i]); + } + return true; +} + bool set_default_backend_config(JSContext *cx, unsigned argc, JS::Value *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (!args.requireAtLeast(cx, "setDefaultDynamicBackendConfiguration", 1)) { diff --git a/runtime/fastly/builtins/backend.h b/runtime/fastly/builtins/backend.h index 176f04aabe..ffb0fbcd8b 100644 --- a/runtime/fastly/builtins/backend.h +++ b/runtime/fastly/builtins/backend.h @@ -1,13 +1,13 @@ #ifndef FASTLY_BACKEND_H #define FASTLY_BACKEND_H +#include "../host-api/host_api_fastly.h" #include "builtin.h" #include "extension-api.h" namespace fastly::backend { class Backend : public builtins::FinalizableBuiltinImpl { -private: public: static constexpr const char *class_name = "Backend"; static const int ctor_length = 1; @@ -23,6 +23,7 @@ class Backend : public builtins::FinalizableBuiltinImpl { static JSString *name(JSContext *cx, JSObject *self); static JSObject *create(JSContext *cx, JS::HandleObject request); + static bool restore_global_state(JSContext *cx); // static methods static bool exists(JSContext *cx, unsigned argc, JS::Value *vp); diff --git a/runtime/fastly/handler.cpp b/runtime/fastly/handler.cpp index b5bfc6b8b1..dfb8ecf664 100644 --- a/runtime/fastly/handler.cpp +++ b/runtime/fastly/handler.cpp @@ -1,4 +1,5 @@ #include "../StarlingMonkey/builtins/web/performance.h" +#include "./builtins/backend.h" #include "./builtins/fastly.h" #include "./builtins/fetch-event.h" #include "./host-api/fastly.h" @@ -17,12 +18,35 @@ namespace fastly::runtime { api::Engine *ENGINE; +bool snapshot_builtin_state() { + JSContext *cx(ENGINE->cx()); + if (!::fastly::backend::Backend::snapshot_global_state(cx)) { + return false; + } + return true; +} + +int restore_builtin_state() { + if (!::fastly::backend::Backend::restore_global_state(ENGINE->cx())) { + if (ENGINE->debug_logging_enabled()) { + fprintf(stderr, "Warning: Failed to restore Backend state processing next request. Exiting.\n"); + } + return false; + } + return true; +} + // Install corresponds to Wizer time, so we configure the engine here bool install(api::Engine *engine) { #if defined(JS_GC_ZEAL) && defined(FASTLY_GC_FREQUENCY) JS::SetGCZeal(engine->cx(), 2, FASTLY_GC_FREQUENCY); #endif ENGINE = engine; + + if (!snapshot_builtin_state()) { + return false; + } + return true; } @@ -44,6 +68,10 @@ bool handle_incoming(host_api::Request req) { fflush(stdout); } + if (!restore_builtin_state()) { + return false; + } + RootedObject fetch_event(ENGINE->cx(), FetchEvent::create(ENGINE->cx())); if (!FetchEvent::init_request(ENGINE->cx(), fetch_event, req.req, req.body)) { ENGINE->dump_pending_exception("initialization of FetchEvent"); From e94b1163a7ebcc6610ce631aed35db67866180af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Fri, 29 May 2026 11:14:34 -0700 Subject: [PATCH 2/3] more ephemeral stuff --- .../fixtures/reusable-sandboxes/tests.json | 14 +++++++++++-- runtime/fastly/builtins/fastly.cpp | 6 ++++++ runtime/fastly/builtins/fastly.h | 1 + runtime/fastly/handler.cpp | 21 ++++++++----------- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/integration-tests/js-compute/fixtures/reusable-sandboxes/tests.json b/integration-tests/js-compute/fixtures/reusable-sandboxes/tests.json index 2f480af735..24b7b49e56 100644 --- a/integration-tests/js-compute/fixtures/reusable-sandboxes/tests.json +++ b/integration-tests/js-compute/fixtures/reusable-sandboxes/tests.json @@ -101,6 +101,16 @@ } }, - "session #3, request #1: GET /backend/ephemeral1": {}, - "session #3, request #2: GET /backend/ephemeral2": {} + "session #3, request #1: GET /backend/ephemeral1": { + "downstream_request": { + "method": "GET", + "pathname": "/backend/ephemeral1" + } + }, + "session #3, request #2: GET /backend/ephemeral2": { + "downstream_request": { + "method": "GET", + "pathname": "/backend/ephemeral2" + } + } } diff --git a/runtime/fastly/builtins/fastly.cpp b/runtime/fastly/builtins/fastly.cpp index 54d0233fd8..3a9e876583 100644 --- a/runtime/fastly/builtins/fastly.cpp +++ b/runtime/fastly/builtins/fastly.cpp @@ -778,6 +778,12 @@ const JSPropertySpec Fastly::properties[] = { #endif JS_PS_END}; +bool Fastly::restore_builtin_state(JSContext *cx) { + Fastly::baseURL.reset(); + Fastly::defaultBackend.reset(); + return true; +} + bool install(api::Engine *engine) { ENGINE = engine; diff --git a/runtime/fastly/builtins/fastly.h b/runtime/fastly/builtins/fastly.h index 743d823620..0f5d4d2586 100644 --- a/runtime/fastly/builtins/fastly.h +++ b/runtime/fastly/builtins/fastly.h @@ -114,6 +114,7 @@ class Fastly : public builtins::BuiltinNoConstructor { static bool allowDynamicBackends_set(JSContext *cx, unsigned argc, JS::Value *vp); static bool inspect(JSContext *cx, unsigned argc, JS::Value *vp); static bool setReusableSandboxOptions(JSContext *cx, unsigned argc, JS::Value *vp); + static bool restore_builtin_state(JSContext *cx); }; JS::Result> convertBodyInit(JSContext *cx, diff --git a/runtime/fastly/handler.cpp b/runtime/fastly/handler.cpp index dfb8ecf664..4440d730e0 100644 --- a/runtime/fastly/handler.cpp +++ b/runtime/fastly/handler.cpp @@ -18,18 +18,19 @@ namespace fastly::runtime { api::Engine *ENGINE; -bool snapshot_builtin_state() { +int restore_builtin_state() { JSContext *cx(ENGINE->cx()); - if (!::fastly::backend::Backend::snapshot_global_state(cx)) { + if (!::fastly::backend::Backend::restore_global_state(cx)) { + if (ENGINE->debug_logging_enabled()) { + fprintf(stderr, + "Warning: Failed to restore Backend state processing next request. Exiting.\n"); + } return false; } - return true; -} - -int restore_builtin_state() { - if (!::fastly::backend::Backend::restore_global_state(ENGINE->cx())) { + if (!fastly::Fastly::restore_builtin_state(cx)) { if (ENGINE->debug_logging_enabled()) { - fprintf(stderr, "Warning: Failed to restore Backend state processing next request. Exiting.\n"); + fprintf(stderr, + "Warning: Failed to restore Backend state processing next request. Exiting.\n"); } return false; } @@ -43,10 +44,6 @@ bool install(api::Engine *engine) { #endif ENGINE = engine; - if (!snapshot_builtin_state()) { - return false; - } - return true; } From e724ca47f4d285b7edc4f86f078e5836c6163907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Fri, 29 May 2026 11:40:07 -0700 Subject: [PATCH 3/3] try putting it at the end --- runtime/fastly/builtins/fastly.cpp | 2 ++ runtime/fastly/handler.cpp | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/runtime/fastly/builtins/fastly.cpp b/runtime/fastly/builtins/fastly.cpp index 3a9e876583..f0876db1c0 100644 --- a/runtime/fastly/builtins/fastly.cpp +++ b/runtime/fastly/builtins/fastly.cpp @@ -781,6 +781,8 @@ const JSPropertySpec Fastly::properties[] = { bool Fastly::restore_builtin_state(JSContext *cx) { Fastly::baseURL.reset(); Fastly::defaultBackend.reset(); + Fastly::baseURL.init(cx); + Fastly::defaultBackend.init(cx); return true; } diff --git a/runtime/fastly/handler.cpp b/runtime/fastly/handler.cpp index 4440d730e0..aa1a4b9b4d 100644 --- a/runtime/fastly/handler.cpp +++ b/runtime/fastly/handler.cpp @@ -65,10 +65,6 @@ bool handle_incoming(host_api::Request req) { fflush(stdout); } - if (!restore_builtin_state()) { - return false; - } - RootedObject fetch_event(ENGINE->cx(), FetchEvent::create(ENGINE->cx())); if (!FetchEvent::init_request(ENGINE->cx(), fetch_event, req.req, req.body)) { ENGINE->dump_pending_exception("initialization of FetchEvent"); @@ -125,6 +121,11 @@ bool handle_incoming(host_api::Request req) { printf("Done. Total request processing time: %fms. Total compute time: %fms\n", diff / 1000, total_compute / 1000); } + + if (!restore_builtin_state()) { + return false; + } + return true; }