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..24b7b49e56 100644 --- a/integration-tests/js-compute/fixtures/reusable-sandboxes/tests.json +++ b/integration-tests/js-compute/fixtures/reusable-sandboxes/tests.json @@ -99,5 +99,18 @@ "QuuxName": "QuuxValue" } } + }, + + "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/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/builtins/fastly.cpp b/runtime/fastly/builtins/fastly.cpp index 54d0233fd8..f0876db1c0 100644 --- a/runtime/fastly/builtins/fastly.cpp +++ b/runtime/fastly/builtins/fastly.cpp @@ -778,6 +778,14 @@ const JSPropertySpec Fastly::properties[] = { #endif JS_PS_END}; +bool Fastly::restore_builtin_state(JSContext *cx) { + Fastly::baseURL.reset(); + Fastly::defaultBackend.reset(); + Fastly::baseURL.init(cx); + Fastly::defaultBackend.init(cx); + 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 b5bfc6b8b1..aa1a4b9b4d 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,32 @@ namespace fastly::runtime { api::Engine *ENGINE; +int restore_builtin_state() { + JSContext *cx(ENGINE->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; + } + 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"); + } + 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; + return true; } @@ -100,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; }