Skip to content

Commit a1f4517

Browse files
Jake ChampionJakeChampion
authored andcommitted
feat: implement SimpleCache.getOrSet method
SimpleCache.getOrSet is a simplified interface to Fastly's request collapsing functionality for lookups and inserts into the service's cache.
1 parent 8864c1c commit a1f4517

File tree

10 files changed

+1267
-5
lines changed

10 files changed

+1267
-5
lines changed

integration-tests/js-compute/fixtures/cache-simple/bin/index.js

Lines changed: 531 additions & 4 deletions
Large diffs are not rendered by default.

runtime/js-compute-runtime/builtins/cache-simple.cpp

Lines changed: 441 additions & 0 deletions
Large diffs are not rendered by default.

runtime/js-compute-runtime/builtins/cache-simple.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ class SimpleCache : public BuiltinImpl<SimpleCache> {
4444
static bool delete_(JSContext *cx, unsigned argc, JS::Value *vp);
4545
static bool get(JSContext *cx, unsigned argc, JS::Value *vp);
4646
static bool set(JSContext *cx, unsigned argc, JS::Value *vp);
47+
static bool getOrSet(JSContext *cx, unsigned argc, JS::Value *vp);
48+
49+
static bool getOrSetThenHandler(JSContext *cx, JS::HandleObject owner, JS::HandleValue extra,
50+
JS::CallArgs args);
4751

4852
static bool constructor(JSContext *cx, unsigned argc, JS::Value *vp);
4953

runtime/js-compute-runtime/fastly-world/fastly_world.c

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,22 @@ typedef struct {
217217
} val;
218218
} fastly_world_result_cache_handle_error_t;
219219

220+
typedef struct {
221+
bool is_err;
222+
union {
223+
fastly_world_tuple2_body_handle_cache_handle_t ok;
224+
fastly_error_t err;
225+
} val;
226+
} fastly_world_result_tuple2_body_handle_cache_handle_error_t;
227+
228+
typedef struct {
229+
bool is_err;
230+
union {
231+
fastly_cache_lookup_state_t ok;
232+
fastly_error_t err;
233+
} val;
234+
} fastly_world_result_cache_lookup_state_error_t;
235+
220236
typedef struct {
221237
bool is_err;
222238
union {
@@ -451,6 +467,18 @@ void __wasm_import_fastly_cache_lookup(int32_t, int32_t, int32_t, int32_t, int32
451467
__attribute__((import_module("fastly"), import_name("cache-insert")))
452468
void __wasm_import_fastly_cache_insert(int32_t, int32_t, int64_t, int32_t, int32_t, int32_t, int64_t, int64_t, int32_t, int32_t, int64_t, int32_t, int32_t, int32_t, int32_t);
453469

470+
__attribute__((import_module("fastly"), import_name("transaction-lookup")))
471+
void __wasm_import_fastly_transaction_lookup(int32_t, int32_t, int32_t, int32_t, int32_t);
472+
473+
__attribute__((import_module("fastly"), import_name("transaction-insert-and-stream-back")))
474+
void __wasm_import_fastly_transaction_insert_and_stream_back(int32_t, int64_t, int32_t, int32_t, int32_t, int64_t, int64_t, int32_t, int32_t, int64_t, int32_t, int32_t, int32_t, int32_t);
475+
476+
__attribute__((import_module("fastly"), import_name("transaction-cancel")))
477+
void __wasm_import_fastly_transaction_cancel(int32_t, int32_t);
478+
479+
__attribute__((import_module("fastly"), import_name("cache-get-state")))
480+
void __wasm_import_fastly_cache_get_state(int32_t, int32_t);
481+
454482
__attribute__((import_module("fastly"), import_name("cache-get-body")))
455483
void __wasm_import_fastly_cache_get_body(int32_t, int64_t, int64_t, int32_t);
456484

@@ -2774,6 +2802,125 @@ bool fastly_cache_insert(fastly_world_string_t *cache_key, fastly_cache_write_op
27742802
}
27752803
}
27762804

2805+
bool fastly_transaction_lookup(fastly_world_string_t *cache_key, fastly_cache_lookup_options_t *options, fastly_cache_handle_t *ret, fastly_error_t *err) {
2806+
__attribute__((aligned(4)))
2807+
uint8_t ret_area[8];
2808+
int32_t option;
2809+
int32_t option1;
2810+
if (((*options).request_headers).is_some) {
2811+
const fastly_request_handle_t *payload0 = &((*options).request_headers).val;
2812+
option = 1;
2813+
option1 = (int32_t) (*payload0);
2814+
} else {
2815+
option = 0;
2816+
option1 = 0;
2817+
}
2818+
int32_t ptr = (int32_t) &ret_area;
2819+
__wasm_import_fastly_transaction_lookup((int32_t) (*cache_key).ptr, (int32_t) (*cache_key).len, option, option1, ptr);
2820+
fastly_world_result_cache_handle_error_t result;
2821+
switch ((int32_t) (*((uint8_t*) (ptr + 0)))) {
2822+
case 0: {
2823+
result.is_err = false;
2824+
result.val.ok = (uint32_t) (*((int32_t*) (ptr + 4)));
2825+
break;
2826+
}
2827+
case 1: {
2828+
result.is_err = true;
2829+
result.val.err = (int32_t) (*((uint8_t*) (ptr + 4)));
2830+
break;
2831+
}
2832+
}
2833+
if (!result.is_err) {
2834+
*ret = result.val.ok;
2835+
return 1;
2836+
} else {
2837+
*err = result.val.err;
2838+
return 0;
2839+
}
2840+
}
2841+
2842+
bool fastly_transaction_insert_and_stream_back(fastly_cache_handle_t handle, fastly_cache_write_options_t *options, fastly_world_tuple2_body_handle_cache_handle_t *ret, fastly_error_t *err) {
2843+
__attribute__((aligned(4)))
2844+
uint8_t ret_area[12];
2845+
int32_t ptr = (int32_t) &ret_area;
2846+
__wasm_import_fastly_transaction_insert_and_stream_back((int32_t) (handle), (int64_t) ((*options).max_age_ns), (int32_t) ((*options).request_headers), (int32_t) ((*options).vary_rule).ptr, (int32_t) ((*options).vary_rule).len, (int64_t) ((*options).initial_age_ns), (int64_t) ((*options).stale_while_revalidate_ns), (int32_t) ((*options).surrogate_keys).ptr, (int32_t) ((*options).surrogate_keys).len, (int64_t) ((*options).length), (int32_t) ((*options).user_metadata).ptr, (int32_t) ((*options).user_metadata).len, (*options).sensitive_data, ptr);
2847+
fastly_world_result_tuple2_body_handle_cache_handle_error_t result;
2848+
switch ((int32_t) (*((uint8_t*) (ptr + 0)))) {
2849+
case 0: {
2850+
result.is_err = false;
2851+
result.val.ok = (fastly_world_tuple2_body_handle_cache_handle_t) {
2852+
(uint32_t) (*((int32_t*) (ptr + 4))),
2853+
(uint32_t) (*((int32_t*) (ptr + 8))),
2854+
};
2855+
break;
2856+
}
2857+
case 1: {
2858+
result.is_err = true;
2859+
result.val.err = (int32_t) (*((uint8_t*) (ptr + 4)));
2860+
break;
2861+
}
2862+
}
2863+
if (!result.is_err) {
2864+
*ret = result.val.ok;
2865+
return 1;
2866+
} else {
2867+
*err = result.val.err;
2868+
return 0;
2869+
}
2870+
}
2871+
2872+
bool fastly_transaction_cancel(fastly_cache_handle_t handle, fastly_error_t *err) {
2873+
__attribute__((aligned(1)))
2874+
uint8_t ret_area[2];
2875+
int32_t ptr = (int32_t) &ret_area;
2876+
__wasm_import_fastly_transaction_cancel((int32_t) (handle), ptr);
2877+
fastly_world_result_void_error_t result;
2878+
switch ((int32_t) (*((uint8_t*) (ptr + 0)))) {
2879+
case 0: {
2880+
result.is_err = false;
2881+
break;
2882+
}
2883+
case 1: {
2884+
result.is_err = true;
2885+
result.val.err = (int32_t) (*((uint8_t*) (ptr + 1)));
2886+
break;
2887+
}
2888+
}
2889+
if (!result.is_err) {
2890+
return 1;
2891+
} else {
2892+
*err = result.val.err;
2893+
return 0;
2894+
}
2895+
}
2896+
2897+
bool fastly_cache_get_state(fastly_cache_handle_t handle, fastly_cache_lookup_state_t *ret, fastly_error_t *err) {
2898+
__attribute__((aligned(1)))
2899+
uint8_t ret_area[2];
2900+
int32_t ptr = (int32_t) &ret_area;
2901+
__wasm_import_fastly_cache_get_state((int32_t) (handle), ptr);
2902+
fastly_world_result_cache_lookup_state_error_t result;
2903+
switch ((int32_t) (*((uint8_t*) (ptr + 0)))) {
2904+
case 0: {
2905+
result.is_err = false;
2906+
result.val.ok = (int32_t) (*((uint8_t*) (ptr + 1)));
2907+
break;
2908+
}
2909+
case 1: {
2910+
result.is_err = true;
2911+
result.val.err = (int32_t) (*((uint8_t*) (ptr + 1)));
2912+
break;
2913+
}
2914+
}
2915+
if (!result.is_err) {
2916+
*ret = result.val.ok;
2917+
return 1;
2918+
} else {
2919+
*err = result.val.err;
2920+
return 0;
2921+
}
2922+
}
2923+
27772924
bool fastly_cache_get_body(fastly_cache_handle_t handle, fastly_cache_get_body_options_t *options, fastly_body_handle_t *ret, fastly_error_t *err) {
27782925
__attribute__((aligned(4)))
27792926
uint8_t ret_area[8];

runtime/js-compute-runtime/fastly-world/fastly_world.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,14 @@ typedef uint8_t fastly_content_encodings_t;
160160

161161
typedef uint64_t fastly_cache_object_length_t;
162162

163+
// The status of this lookup (and potential transaction)
164+
typedef uint8_t fastly_cache_lookup_state_t;
165+
166+
#define FASTLY_CACHE_LOOKUP_STATE_FOUND (1 << 0)
167+
#define FASTLY_CACHE_LOOKUP_STATE_USABLE (1 << 1)
168+
#define FASTLY_CACHE_LOOKUP_STATE_STALE (1 << 2)
169+
#define FASTLY_CACHE_LOOKUP_STATE_MUST_INSERT_OR_UPDATE (1 << 3)
170+
163171
typedef struct {
164172
bool is_some;
165173
fastly_request_handle_t val;
@@ -282,6 +290,11 @@ typedef struct {
282290
size_t len;
283291
} fastly_world_list_async_handle_t;
284292

293+
typedef struct {
294+
fastly_body_handle_t f0;
295+
fastly_cache_handle_t f1;
296+
} fastly_world_tuple2_body_handle_cache_handle_t;
297+
285298
typedef uint32_t compute_at_edge_request_handle_t;
286299

287300
typedef uint32_t compute_at_edge_body_handle_t;
@@ -443,6 +456,16 @@ bool fastly_cache_lookup(fastly_world_string_t *cache_key, fastly_cache_lookup_o
443456
fastly_cache_handle_t *ret, fastly_error_t *err);
444457
bool fastly_cache_insert(fastly_world_string_t *cache_key, fastly_cache_write_options_t *options,
445458
fastly_body_handle_t *ret, fastly_error_t *err);
459+
bool fastly_transaction_lookup(fastly_world_string_t *cache_key,
460+
fastly_cache_lookup_options_t *options, fastly_cache_handle_t *ret,
461+
fastly_error_t *err);
462+
bool fastly_transaction_insert_and_stream_back(fastly_cache_handle_t handle,
463+
fastly_cache_write_options_t *options,
464+
fastly_world_tuple2_body_handle_cache_handle_t *ret,
465+
fastly_error_t *err);
466+
bool fastly_transaction_cancel(fastly_cache_handle_t handle, fastly_error_t *err);
467+
bool fastly_cache_get_state(fastly_cache_handle_t handle, fastly_cache_lookup_state_t *ret,
468+
fastly_error_t *err);
446469
bool fastly_cache_get_body(fastly_cache_handle_t handle, fastly_cache_get_body_options_t *options,
447470
fastly_body_handle_t *ret, fastly_error_t *err);
448471

runtime/js-compute-runtime/fastly-world/fastly_world_adapter.cpp

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -796,4 +796,71 @@ bool fastly_cache_get_body(fastly_cache_handle_t handle, fastly_cache_get_body_o
796796
return true;
797797
}
798798
return ok;
799-
}
799+
}
800+
bool fastly_transaction_lookup(fastly_world_string_t *cache_key,
801+
fastly_cache_lookup_options_t *options, fastly_cache_handle_t *ret,
802+
fastly_error_t *err) {
803+
// Currently this host-call has been implemented to support the `SimpleCache.getOrSet` method,
804+
// which does not use any fields from `fastly_cache_lookup_options_t`.
805+
uint32_t options_mask = 0;
806+
return convert_result(
807+
fastly::cache_transaction_lookup(cache_key->ptr, cache_key->len, options_mask, options, ret),
808+
err);
809+
}
810+
bool fastly_transaction_insert_and_stream_back(fastly_cache_handle_t handle,
811+
fastly_cache_write_options_t *options,
812+
fastly_world_tuple2_body_handle_cache_handle_t *ret,
813+
fastly_error_t *err) {
814+
uint16_t options_mask = 0;
815+
fastly::CacheWriteOptions opts;
816+
std::memset(&opts, 0, sizeof(opts));
817+
opts.max_age_ns = options->max_age_ns;
818+
819+
MOZ_ASSERT(options->request_headers == INVALID_HANDLE || options->request_headers == 0);
820+
821+
if (options->vary_rule.ptr != nullptr) {
822+
options_mask |= FASTLY_CACHE_WRITE_OPTIONS_MASK_VARY_RULE;
823+
opts.vary_rule_len = options->vary_rule.len;
824+
opts.vary_rule_ptr = reinterpret_cast<uint8_t *>(options->vary_rule.ptr);
825+
}
826+
if (options->initial_age_ns != 0) {
827+
options_mask |= FASTLY_CACHE_WRITE_OPTIONS_MASK_INITIAL_AGE_NS;
828+
opts.initial_age_ns = options->initial_age_ns;
829+
}
830+
if (options->stale_while_revalidate_ns != 0) {
831+
options_mask |= FASTLY_CACHE_WRITE_OPTIONS_MASK_STALE_WHILE_REVALIDATE_NS;
832+
opts.stale_while_revalidate_ns = options->stale_while_revalidate_ns;
833+
}
834+
if (options->surrogate_keys.ptr != nullptr) {
835+
options_mask |= FASTLY_CACHE_WRITE_OPTIONS_MASK_SURROGATE_KEYS;
836+
opts.surrogate_keys_len = options->surrogate_keys.len;
837+
opts.surrogate_keys_ptr = reinterpret_cast<uint8_t *>(options->surrogate_keys.ptr);
838+
}
839+
if (options->length != 0) {
840+
options_mask |= FASTLY_CACHE_WRITE_OPTIONS_MASK_LENGTH;
841+
opts.length = options->length;
842+
}
843+
if (options->user_metadata.ptr != nullptr) {
844+
options_mask |= FASTLY_CACHE_WRITE_OPTIONS_MASK_USER_METADATA;
845+
opts.user_metadata_len = options->user_metadata.len;
846+
opts.user_metadata_ptr = options->user_metadata.ptr;
847+
}
848+
if (options->sensitive_data) {
849+
options_mask |= FASTLY_CACHE_WRITE_OPTIONS_MASK_SENSITIVE_DATA;
850+
}
851+
return convert_result(fastly::cache_transaction_insert_and_stream_back(handle, options_mask,
852+
&opts, &ret->f0, &ret->f1),
853+
err);
854+
}
855+
856+
/// Cancel an obligation to provide an object to the cache.
857+
///
858+
/// Useful if there is an error before streaming is possible, e.g. if a backend is unreachable.
859+
bool fastly_transaction_cancel(fastly_cache_handle_t handle, fastly_error_t *err) {
860+
return convert_result(fastly::cache_transaction_cancel(handle), err);
861+
}
862+
863+
bool fastly_cache_get_state(fastly_cache_handle_t handle, fastly_cache_lookup_state_t *ret,
864+
fastly_error_t *err) {
865+
return convert_result(fastly::cache_get_state(handle, ret), err);
866+
}
Binary file not shown.

runtime/js-compute-runtime/fastly.wit

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,17 @@ default world fastly-world {
540540
end: u64,
541541
}
542542

543+
/// The status of this lookup (and potential transaction)
544+
flags cache-lookup-state {
545+
/// a cached object was found
546+
found,
547+
/// the cached object is valid to use (implies found)
548+
usable,
549+
/// the cached object is stale (but may or may not be valid to use)
550+
stale,
551+
/// this client is requested to insert or revalidate an object
552+
must-insert-or-update,
553+
}
543554

544555
/// Performs a non-request-collapsing cache lookup.
545556
///
@@ -552,6 +563,30 @@ default world fastly-world {
552563
/// the cache.
553564
cache-insert: func(cache-key: string, options: cache-write-options) -> result<body-handle, error>
554565

566+
/// The entrypoint to the request-collapsing cache transaction API.
567+
///
568+
/// This operation always participates in request collapsing and may return stale objects. To bypass
569+
/// request collapsing, use `lookup` and `insert` instead.
570+
transaction-lookup: func(cache-key: string, options: cache-lookup-options) -> result<cache-handle, error>
571+
572+
/// Insert an object into the cache with the given metadata, and return a readable stream of the
573+
/// bytes as they are stored.
574+
///
575+
/// This helps avoid the "slow reader" problem on a teed stream, for example when a program wishes
576+
/// to store a backend request in the cache while simultaneously streaming to a client in an HTTP
577+
/// response.
578+
///
579+
/// The returned body handle is to a streaming body that is used for writing the object _into_
580+
/// the cache. The returned cache handle provides a separate transaction for reading out the
581+
/// newly cached object to send elsewhere.
582+
transaction-insert-and-stream-back: func(handle: cache-handle, options: cache-write-options) -> result<tuple<body-handle, cache-handle>, error>
583+
584+
/// Cancel an obligation to provide an object to the cache.
585+
///
586+
/// Useful if there is an error before streaming is possible, e.g. if a backend is unreachable.
587+
transaction-cancel: func(handle: cache-handle) -> result<_, error>
588+
589+
cache-get-state: func(handle: cache-handle) -> result<cache-lookup-state, error>
555590

556591
/// Gets a range of the found object body, returning the `$none` error if there
557592
/// was no found object.

runtime/js-compute-runtime/host_interface/fastly.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,22 @@ WASM_IMPORT("fastly_cache", "insert")
391391
int cache_insert(char *cache_key, size_t cache_key_len, uint32_t options_mask,
392392
CacheWriteOptions *options, fastly_body_handle_t *ret);
393393

394+
WASM_IMPORT("fastly_cache", "transaction_lookup")
395+
int cache_transaction_lookup(char *cache_key, size_t cache_key_len, uint32_t options_mask,
396+
fastly_cache_lookup_options_t *options, fastly_cache_handle_t *ret);
397+
398+
WASM_IMPORT("fastly_cache", "transaction_insert_and_stream_back")
399+
int cache_transaction_insert_and_stream_back(fastly_cache_handle_t handle, uint32_t options_mask,
400+
CacheWriteOptions *options,
401+
fastly_body_handle_t *ret_body,
402+
fastly_cache_handle_t *ret_cache);
403+
404+
WASM_IMPORT("fastly_cache", "transaction_cancel")
405+
int cache_transaction_cancel(fastly_cache_handle_t handle);
406+
407+
WASM_IMPORT("fastly_cache", "get_state")
408+
int cache_get_state(fastly_cache_handle_t handle, fastly_cache_lookup_state_t *ret);
409+
394410
WASM_IMPORT("fastly_cache", "get_body")
395411
int cache_get_body(fastly_cache_handle_t handle, uint32_t options_mask,
396412
fastly_cache_get_body_options_t *options, fastly_body_handle_t *ret);

types/fastly:cache.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ declare module "fastly:cache" {
33
static get(key: string): SimpleCacheEntry | null;
44
static set(key: string, value: BodyInit, ttl: number): undefined;
55
static set(key: string, value: ReadableStream, ttl: number, length: number): undefined;
6+
static getOrSet(key: string, set: () => Promise<{value: BodyInit, ttl: number}>): Promise<SimpleCacheEntry>;
7+
static getOrSet(key: string, set: () => Promise<{value: ReadableStream, ttl: number, length: number}>): Promise<SimpleCacheEntry>;
68
static delete(key: string): undefined;
79
}
810

0 commit comments

Comments
 (0)