From e5b222d83df6f294d9ecdb42484476652d71c542 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Mon, 23 May 2022 16:35:15 -0400 Subject: [PATCH 1/7] feat: Initial scratch storage implementation --- applications/Makefile | 6 + applications/wasm_apps | 2 +- libsledge/Makefile | 2 +- libsledge/include/sledge_abi.h | 7 + libsledge/src/sledge_extensions.c | 65 +++++++ runtime/include/lock.h | 1 + runtime/include/map.h | 183 ++++++++++++++++++++ runtime/include/route_config.h | 1 - runtime/include/tenant.h | 2 + runtime/include/tenant_functions.h | 1 + runtime/src/sledge_abi.c | 125 +++++++++++++ tests/scratch_storage/.gitignore | 3 + tests/scratch_storage/Makefile | 40 +++++ tests/scratch_storage/edf_nopreemption.env | 2 + tests/scratch_storage/edf_preemption.env | 3 + tests/scratch_storage/fifo_nopreemption.env | 2 + tests/scratch_storage/fifo_preemption.env | 2 + tests/scratch_storage/install.sh | 13 ++ tests/scratch_storage/spec.json | 26 +++ 19 files changed, 483 insertions(+), 3 deletions(-) create mode 100644 libsledge/src/sledge_extensions.c create mode 100644 runtime/include/map.h create mode 100644 tests/scratch_storage/.gitignore create mode 100644 tests/scratch_storage/Makefile create mode 100644 tests/scratch_storage/edf_nopreemption.env create mode 100644 tests/scratch_storage/edf_preemption.env create mode 100644 tests/scratch_storage/fifo_nopreemption.env create mode 100644 tests/scratch_storage/fifo_preemption.env create mode 100755 tests/scratch_storage/install.sh create mode 100644 tests/scratch_storage/spec.json diff --git a/applications/Makefile b/applications/Makefile index a461aa2..8cf3187 100644 --- a/applications/Makefile +++ b/applications/Makefile @@ -88,3 +88,9 @@ stack_overflow.install: ../runtime/bin/stack_overflow.wasm.so .PHONY: html.install html.install: ../runtime/bin/html.wasm.so + +.PHONY: scratch_storage_get.install +scratch_storage_get.install: ../runtime/bin/scratch_storage_get.wasm.so + +.PHONY: scratch_storage_set.install +scratch_storage_set.install: ../runtime/bin/scratch_storage_set.wasm.so diff --git a/applications/wasm_apps b/applications/wasm_apps index 456edf5..54de4d2 160000 --- a/applications/wasm_apps +++ b/applications/wasm_apps @@ -1 +1 @@ -Subproject commit 456edf5cecd7edc36314ba87e2d76ebf8cf1b5db +Subproject commit 54de4d27f9a6e1f20b65b7110612a658479e6133 diff --git a/libsledge/Makefile b/libsledge/Makefile index f0dd19c..37679f1 100644 --- a/libsledge/Makefile +++ b/libsledge/Makefile @@ -26,7 +26,7 @@ dist: dist/%.o: src/%.c dist clang ${CFLAGS} -c ${INCLUDES} -o $@ $< -dist/libsledge.a: dist/control_instructions.o dist/memory_instructions.o dist/numeric_instructions.o dist/table_instructions.o dist/variable_instructions.o dist/instantiation.o dist/wasi_snapshot_preview1.o +dist/libsledge.a: dist/control_instructions.o dist/memory_instructions.o dist/numeric_instructions.o dist/table_instructions.o dist/variable_instructions.o dist/instantiation.o dist/wasi_snapshot_preview1.o dist/sledge_extensions.o ar rcs dist/libsledge.a $^ clean: diff --git a/libsledge/include/sledge_abi.h b/libsledge/include/sledge_abi.h index a26e959..2b47f86 100644 --- a/libsledge/include/sledge_abi.h +++ b/libsledge/include/sledge_abi.h @@ -249,3 +249,10 @@ uint32_t sledge_abi__wasi_snapshot_preview1_sock_send(__wasi_fd_t fd, __wasi_siz __wasi_size_t nbytes_retoffset); uint32_t sledge_abi__wasi_snapshot_preview1_sock_shutdown(__wasi_fd_t fd, uint32_t how); + +uint32_t sledge_abi__scratch_storage_get_size(uint32_t key_offset, uint32_t key_len); +int sledge_abi__scratch_storage_get(uint32_t key_offset, uint32_t key_size, uint32_t buf_offset, uint32_t buf_len); +int sledge_abi__scratch_storage_set(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len); +int sledge_abi__scratch_storage_delete(uint32_t key_offset, uint32_t key_len); +void +sledge_abi__scratch_storage_upsert(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len); diff --git a/libsledge/src/sledge_extensions.c b/libsledge/src/sledge_extensions.c new file mode 100644 index 0000000..3687a4f --- /dev/null +++ b/libsledge/src/sledge_extensions.c @@ -0,0 +1,65 @@ +#include +#include "sledge_abi.h" + +#define INLINE __attribute__((always_inline)) + +/** + * @param key + * @param key_len + * @returns value_size at key or 0 if key not present + */ +INLINE uint32_t +scratch_storage_get_size(uint32_t key_offset, uint32_t key_len) +{ + return sledge_abi__scratch_storage_get_size(key_offset, key_len); +} + +/** + * @param key_offset + * @param key_len + * @param buf_offset linear memory offset to buffer where value should be copied. + * @param buf_len Size of buffer. Assumed to be size returned by sledge_kv_get_value_size. + * @returns 0 on success, 1 if key missing, 2 if buffer too small + */ +INLINE int +scratch_storage_get(uint32_t key_offset, uint32_t key_len, uint32_t buf_offset, uint32_t buf_len) +{ + return sledge_abi__scratch_storage_get(key_offset, key_len, buf_offset, buf_len); +}; + +/** + * @param key_offset + * @param key_len + * @param value_offset + * @param value_len + * @returns 0 on success, 1 if already present, + */ +INLINE int +scratch_storage_set(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len) +{ + return sledge_abi__scratch_storage_set(key_offset, key_len, value_offset, value_len); +} + +/** + * @param key_offset + * @param key_len + * @returns 0 on success, 1 if not present + */ +INLINE int +scratch_storage_delete(uint32_t key_offset, uint32_t key_len) +{ + return sledge_abi__scratch_storage_delete(key_offset, key_len); +} + +/** + * @param key_offset + * @param key_len + * @param value_offset + * @param value_len + * @returns 0 on success, 1 if already present, + */ +INLINE void +scratch_storage_upsert(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len) +{ + sledge_abi__scratch_storage_upsert(key_offset, key_len, value_offset, value_len); +} diff --git a/runtime/include/lock.h b/runtime/include/lock.h index 5f15d1b..81a7681 100644 --- a/runtime/include/lock.h +++ b/runtime/include/lock.h @@ -5,6 +5,7 @@ #include "arch/getcycles.h" #include "runtime.h" +#include "generic_thread.h" typedef ck_spinlock_mcs_t lock_t; diff --git a/runtime/include/map.h b/runtime/include/map.h new file mode 100644 index 0000000..1a64ddd --- /dev/null +++ b/runtime/include/map.h @@ -0,0 +1,183 @@ +#pragma once + +#include +#include +#include +#include + +#include "lock.h" + +/* Simple K-V store based on The Practice of Programming by Kernighan and Pike */ + +/* Bucket count is sized to be a prime that is approximately 20% larger than the desired capacity (6k keys) */ +#define MAP_BUCKET_COUNT 7907 +#define MAP_LOCK_STRIPES 17 + +#define MAP_HASH jenkins_hash + +struct map_node { + uint32_t hash; + uint8_t *key; + uint32_t key_len; + uint8_t *value; + uint32_t value_len; + struct map_node *next; +}; + +struct map { + struct map_node *buckets[MAP_BUCKET_COUNT]; + lock_t locks[MAP_LOCK_STRIPES]; +}; + +static inline void +map_init(struct map *restrict map) +{ + for (int i = 0; i < MAP_BUCKET_COUNT; i++) { map->buckets[i] = NULL; } + for (int i = 0; i < MAP_LOCK_STRIPES; i++) { LOCK_INIT(&map->locks[i]); } +}; + +/* See https://en.wikipedia.org/wiki/Jenkins_hash_function */ +static inline uint32_t +jenkins_hash(uint8_t *key, uint32_t key_len) +{ + uint32_t i = 0; + uint32_t hash = 0; + while (i != key_len) { + hash += key[i++]; + hash += hash << 10; + hash ^= hash >> 6; + } + hash += hash << 3; + hash ^= hash >> 11; + hash += hash << 15; + return hash; +} + +static inline uint8_t * +map_get(struct map *map, uint8_t *key, uint32_t key_len, uint32_t *ret_value_len) +{ + uint8_t *value = NULL; + + uint32_t hash = MAP_HASH(key, key_len); + LOCK_LOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + for (struct map_node *node = map->buckets[hash % MAP_BUCKET_COUNT]; node != NULL; node = node->next) { + if (node->hash == hash) { + value = node->value; + *ret_value_len = node->value_len; + goto DONE; + } + } + + if (value == NULL) *ret_value_len = 0; + +DONE: + LOCK_UNLOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + return value; +} + +static inline bool +map_set(struct map *map, uint8_t *key, uint32_t key_len, uint8_t *value, uint32_t value_len) +{ + bool did_set = false; + + uint32_t hash = MAP_HASH(key, key_len); + LOCK_LOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + for (struct map_node *node = map->buckets[hash % MAP_BUCKET_COUNT]; node != NULL; node = node->next) { + if (node->hash == hash) goto DONE; + } + + struct map_node *new_node = (struct map_node *)malloc(sizeof(struct map_node)); + + *(new_node) = (struct map_node){ .hash = hash, + .key = malloc(key_len), + .key_len = key_len, + .value = malloc(value_len), + .value_len = value_len, + .next = map->buckets[hash % MAP_BUCKET_COUNT] }; + + assert(new_node->key); + assert(new_node->value); + + // Copy Key and Value + memcpy(new_node->key, key, key_len); + memcpy(new_node->value, value, value_len); + + map->buckets[hash % MAP_BUCKET_COUNT] = new_node; + did_set = true; + +DONE: + LOCK_UNLOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + return did_set; +} + +/** + * @returns boolean if node was deleted or not + */ +static inline bool +map_delete(struct map *map, uint8_t *key, uint32_t key_len) +{ + bool did_delete = false; + + uint32_t hash = MAP_HASH(key, key_len); + LOCK_LOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + + struct map_node *prev = map->buckets[hash % MAP_BUCKET_COUNT]; + if (prev->hash == hash) { + map->buckets[hash % MAP_BUCKET_COUNT] = prev->next; + free(prev->key); + free(prev->value); + free(prev); + did_delete = true; + goto DONE; + } + + for (struct map_node *node = prev->next; node != NULL; prev = node, node = node->next) { + prev->next = node->next; + free(node->key); + free(node->value); + free(node); + did_delete = true; + goto DONE; + } + +DONE: + LOCK_UNLOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + return did_delete; +} + +static inline void +map_upsert(struct map *map, uint8_t *key, uint32_t key_len, uint8_t *value, uint32_t value_len) +{ + uint32_t hash = MAP_HASH(key, key_len); + LOCK_LOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + for (struct map_node *node = map->buckets[hash % MAP_BUCKET_COUNT]; node != NULL; node = node->next) { + if (node->hash == hash) { + node->value_len = value_len; + node->value = realloc(&node->value, value_len); + assert(node->value); + memcpy(&node->value, value, value_len); + } + goto DONE; + } + + struct map_node *new_node = (struct map_node *)malloc(sizeof(struct map_node)); + + *(new_node) = (struct map_node){ .hash = hash, + .key = malloc(key_len), + .key_len = key_len, + .value = malloc(value_len), + .value_len = value_len, + .next = map->buckets[hash % MAP_BUCKET_COUNT] }; + + assert(new_node->key); + assert(new_node->value); + + // Copy Key and Value + memcpy(&new_node->key, key, key_len); + memcpy(&new_node->value, value, value_len); + + map->buckets[hash % MAP_BUCKET_COUNT] = new_node; + +DONE: + LOCK_UNLOCK(&map->locks[hash % MAP_LOCK_STRIPES]); +} diff --git a/runtime/include/route_config.h b/runtime/include/route_config.h index 3a2dc6b..aeb214e 100644 --- a/runtime/include/route_config.h +++ b/runtime/include/route_config.h @@ -73,7 +73,6 @@ route_config_validate(struct route_config *config, bool *did_set) if (did_set[route_config_member_http_resp_content_type] == false) { debuglog("http_resp_content_type not set, defaulting to text/plain\n"); config->http_resp_content_type = "text/plain"; - return -1; } if (scheduler == SCHEDULER_EDF) { diff --git a/runtime/include/tenant.h b/runtime/include/tenant.h index 01977fc..06fa730 100644 --- a/runtime/include/tenant.h +++ b/runtime/include/tenant.h @@ -1,6 +1,7 @@ #pragma once #include "http_router.h" +#include "map.h" #include "module_database.h" #include "tcp_server.h" @@ -9,4 +10,5 @@ struct tenant { struct tcp_server tcp_server; http_router_t router; struct module_database module_db; + struct map scratch_storage; }; diff --git a/runtime/include/tenant_functions.h b/runtime/include/tenant_functions.h index cdf446c..89bb6f8 100644 --- a/runtime/include/tenant_functions.h +++ b/runtime/include/tenant_functions.h @@ -37,6 +37,7 @@ tenant_alloc(struct tenant_config *config) tcp_server_init(&tenant->tcp_server, config->port); http_router_init(&tenant->router, config->routes_len); module_database_init(&tenant->module_db); + map_init(&tenant->scratch_storage); for (int i = 0; i < config->routes_len; i++) { struct module *module = module_database_find_by_path(&tenant->module_db, config->routes[i].path); diff --git a/runtime/src/sledge_abi.c b/runtime/src/sledge_abi.c index 5cdf918..b1c2146 100644 --- a/runtime/src/sledge_abi.c +++ b/runtime/src/sledge_abi.c @@ -1,4 +1,5 @@ #include "current_sandbox.h" +#include "map.h" #include "sandbox_set_as_running_sys.h" #include "sandbox_set_as_running_user.h" #include "sledge_abi.h" @@ -1039,3 +1040,127 @@ sledge_abi__wasi_snapshot_preview1_sock_shutdown(__wasi_fd_t fd, uint32_t how) { return wasi_unsupported_syscall(__func__); } + +/** + * @param key + * @param key_len + * @returns value_len at key or 0 if key not present + */ +EXPORT uint32_t +sledge_abi__scratch_storage_get_size(uint32_t key_offset, uint32_t key_len) +{ + struct sandbox *sandbox = current_sandbox_get(); + + sandbox_syscall(sandbox); + + uint8_t *key = (uint8_t *)get_memory_ptr_for_runtime(key_offset, key_len); + + uint32_t value_len; + map_get(&sandbox->tenant->scratch_storage, key, key_len, &value_len); + + sandbox_return(sandbox); + + return value_len; +} + +EXPORT int +sledge_abi__scratch_storage_get(uint32_t key_offset, uint32_t key_len, uint32_t buf_offset, uint32_t buf_len) +{ + int rc = 0; + + struct sandbox *sandbox = current_sandbox_get(); + + sandbox_syscall(sandbox); + + uint8_t *key = (uint8_t *)get_memory_ptr_for_runtime(key_offset, key_len); + uint8_t *buf = (uint8_t *)get_memory_ptr_for_runtime(buf_offset, buf_len); + + uint32_t value_len; + uint8_t *value = map_get(&sandbox->tenant->scratch_storage, key, key_len, &value_len); + + if (value == NULL) { + rc = 1; + goto DONE; + } else if (value_len > buf_len) { + rc = 2; + goto DONE; + } else { + memcpy(buf, value, value_len); + rc = 0; + } + +DONE: + sandbox_return(sandbox); + return rc; +} + +/** + * @param key_offset + * @param key_len + * @param value_offset + * @param value_len + * @returns 0 on success, 1 if already present, + */ +EXPORT int +sledge_abi__scratch_storage_set(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len) +{ + int rc = 0; + + struct sandbox *sandbox = current_sandbox_get(); + + sandbox_syscall(sandbox); + + uint8_t *key = (uint8_t *)get_memory_ptr_for_runtime(key_offset, key_len); + uint8_t *value = (uint8_t *)get_memory_ptr_for_runtime(value_offset, value_len); + + bool did_set = map_set(&sandbox->tenant->scratch_storage, key, key_len, value, value_len); + +DONE: + sandbox_return(sandbox); + return did_set ? 0 : 1; +} + +/** + * @param key_offset + * @param key_len + * @returns 0 on success, 1 if not present + */ +EXPORT int +sledge_abi__scratch_storage_delete(uint32_t key_offset, uint32_t key_len) +{ + int rc = 0; + + struct sandbox *sandbox = current_sandbox_get(); + + sandbox_syscall(sandbox); + + uint8_t *key = (uint8_t *)get_memory_ptr_for_runtime(key_offset, key_len); + + bool did_delete = map_delete(&sandbox->tenant->scratch_storage, key, key_len); + +DONE: + sandbox_return(sandbox); + return did_delete ? 0 : 1; +} + +/** + * @param key_offset + * @param key_len + * @param value_offset + * @param value_len + * @returns 0 on success, 1 if already present, + */ +EXPORT void +sledge_abi__scratch_storage_upsert(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len) +{ + struct sandbox *sandbox = current_sandbox_get(); + + sandbox_syscall(sandbox); + + uint8_t *key = (uint8_t *)get_memory_ptr_for_runtime(key_offset, key_len); + uint8_t *value = (uint8_t *)get_memory_ptr_for_runtime(value_offset, value_len); + + map_upsert(&sandbox->tenant->scratch_storage, key, key_len, value, value_len); + + sandbox_return(sandbox); +} diff --git a/tests/scratch_storage/.gitignore b/tests/scratch_storage/.gitignore new file mode 100644 index 0000000..26cdcd9 --- /dev/null +++ b/tests/scratch_storage/.gitignore @@ -0,0 +1,3 @@ +res +perf.data +perf.data.old diff --git a/tests/scratch_storage/Makefile b/tests/scratch_storage/Makefile new file mode 100644 index 0000000..7239f8a --- /dev/null +++ b/tests/scratch_storage/Makefile @@ -0,0 +1,40 @@ +RUNTIME_DIR=../../runtime/ +SLEDGE_BINARY_DIR=${RUNTIME_DIR}/bin +SLEDGE_TESTS_DIR=${RUNTIME_DIR}/tests +HOSTNAME=localhost + +all: run + +clean: + make -C ${RUNTIME_DIR} clean + make -C ${SLEDGE_TESTS_DIR} clean + rm -f ${SLEDGE_BINARY_DIR}/html.wasm.so + +${SLEDGE_BINARY_DIR}/sledgert: + make -C ${RUNTIME_DIR} runtime + +.PHONY: sledgert +sledgert: ${SLEDGE_BINARY_DIR}/sledgert + +${SLEDGE_BINARY_DIR}/scratch_storage.wasm.so: + make -C ../../applications scratch_storage.install + +.PHONY: scratch_storage_get +scratch_storage_get: ${SLEDGE_BINARY_DIR}/scratch_storage_get.wasm.so + +.PHONY: scratch_storage_set +scratch_storage_set: ${SLEDGE_BINARY_DIR}/scratch_storage_set.wasm.so + +run: sledgert scratch_storage_get scratch_storage_set + LD_LIBRARY_PATH=${SLEDGE_BINARY_DIR} ${SLEDGE_BINARY_DIR}/sledgert spec.json + +debug: sledgert scratch_storage_get scratch_storage_set + SLEDGE_DISABLE_PREEMPTION=true SLEDGE_NWORKERS=1 \ + LD_LIBRARY_PATH=${SLEDGE_BINARY_DIR} gdb ${SLEDGE_BINARY_DIR}/sledgert \ + --eval-command="handle SIGUSR1 noprint nostop" \ + --eval-command="handle SIGPIPE noprint nostop" \ + --eval-command="set pagination off" \ + --eval-command="run spec.json" + +client: + http :1337/scratch_storage diff --git a/tests/scratch_storage/edf_nopreemption.env b/tests/scratch_storage/edf_nopreemption.env new file mode 100644 index 0000000..eeba531 --- /dev/null +++ b/tests/scratch_storage/edf_nopreemption.env @@ -0,0 +1,2 @@ +SLEDGE_SCHEDULER=EDF +SLEDGE_DISABLE_PREEMPTION=true diff --git a/tests/scratch_storage/edf_preemption.env b/tests/scratch_storage/edf_preemption.env new file mode 100644 index 0000000..302a324 --- /dev/null +++ b/tests/scratch_storage/edf_preemption.env @@ -0,0 +1,3 @@ +SLEDGE_SCHEDULER=EDF +SLEDGE_DISABLE_PREEMPTION=false +SLEDGE_SIGALRM_HANDLER=TRIAGED diff --git a/tests/scratch_storage/fifo_nopreemption.env b/tests/scratch_storage/fifo_nopreemption.env new file mode 100644 index 0000000..a572a70 --- /dev/null +++ b/tests/scratch_storage/fifo_nopreemption.env @@ -0,0 +1,2 @@ +SLEDGE_SCHEDULER=FIFO +SLEDGE_DISABLE_PREEMPTION=true diff --git a/tests/scratch_storage/fifo_preemption.env b/tests/scratch_storage/fifo_preemption.env new file mode 100644 index 0000000..eb1298f --- /dev/null +++ b/tests/scratch_storage/fifo_preemption.env @@ -0,0 +1,2 @@ +SLEDGE_SCHEDULER=FIFO +SLEDGE_DISABLE_PREEMPTION=false diff --git a/tests/scratch_storage/install.sh b/tests/scratch_storage/install.sh new file mode 100755 index 0000000..0cbcfe8 --- /dev/null +++ b/tests/scratch_storage/install.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +if ! command -v hey > /dev/null; then + HEY_URL=https://hey-release.s3.us-east-2.amazonaws.com/hey_linux_amd64 + wget $HEY_URL -O hey + chmod +x hey + + if [[ $(whoami) == "root" ]]; then + mv hey /usr/bin/hey + else + sudo mv hey /usr/bin/hey + fi +fi diff --git a/tests/scratch_storage/spec.json b/tests/scratch_storage/spec.json new file mode 100644 index 0000000..0047ea7 --- /dev/null +++ b/tests/scratch_storage/spec.json @@ -0,0 +1,26 @@ +[ + { + "name": "gwu", + "port": 1337, + "routes": [ + { + "route": "/set", + "path": "scratch_storage_set.wasm.so", + "expected-execution-us": 10000000, + "admissions-percentile": 70, + "relative-deadline-us": 20000000, + "http-resp-content-type": "text/plain", + "http-resp-size": 1024 + }, + { + "route": "/get", + "path": "scratch_storage_get.wasm.so", + "expected-execution-us": 10000000, + "admissions-percentile": 70, + "relative-deadline-us": 20000000, + "http-resp-content-type": "text/plain", + "http-resp-size": 1024 + } + ] + } +] From 32248f11be315b257152db386ce398188d56c159 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Thu, 26 May 2022 10:48:10 -0400 Subject: [PATCH 2/7] fix: Correct upsert bug --- applications/Makefile | 12 +++++++++++- applications/wasm_apps | 2 +- runtime/include/map.h | 8 ++++---- runtime/src/sledge_abi.c | 1 - tests/scratch_storage/spec.json | 18 ++++++++++++++++++ 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/applications/Makefile b/applications/Makefile index 8cf3187..a779fc1 100644 --- a/applications/Makefile +++ b/applications/Makefile @@ -22,7 +22,11 @@ all: \ gocr.install \ gps_ekf.install \ license_plate_detection.install \ - resize_image.install + resize_image.install \ + scratch_storage_get.install \ + scratch_storage_set.install \ + scratch_storage_delete.install \ + scratch_storage_upsert.install \ .PHONY: clean clean: @@ -94,3 +98,9 @@ scratch_storage_get.install: ../runtime/bin/scratch_storage_get.wasm.so .PHONY: scratch_storage_set.install scratch_storage_set.install: ../runtime/bin/scratch_storage_set.wasm.so + +.PHONY: scratch_storage_delete.install +scratch_storage_delete.install: ../runtime/bin/scratch_storage_delete.wasm.so + +.PHONY: scratch_storage_upsert.install +scratch_storage_upsert.install: ../runtime/bin/scratch_storage_upsert.wasm.so diff --git a/applications/wasm_apps b/applications/wasm_apps index 54de4d2..cf8a8ba 160000 --- a/applications/wasm_apps +++ b/applications/wasm_apps @@ -1 +1 @@ -Subproject commit 54de4d27f9a6e1f20b65b7110612a658479e6133 +Subproject commit cf8a8bafb10c37f207ccba1405cac07cd70bae8f diff --git a/runtime/include/map.h b/runtime/include/map.h index 1a64ddd..0bad1db 100644 --- a/runtime/include/map.h +++ b/runtime/include/map.h @@ -153,9 +153,9 @@ map_upsert(struct map *map, uint8_t *key, uint32_t key_len, uint8_t *value, uint for (struct map_node *node = map->buckets[hash % MAP_BUCKET_COUNT]; node != NULL; node = node->next) { if (node->hash == hash) { node->value_len = value_len; - node->value = realloc(&node->value, value_len); + node->value = realloc(node->value, value_len); assert(node->value); - memcpy(&node->value, value, value_len); + memcpy(node->value, value, value_len); } goto DONE; } @@ -173,8 +173,8 @@ map_upsert(struct map *map, uint8_t *key, uint32_t key_len, uint8_t *value, uint assert(new_node->value); // Copy Key and Value - memcpy(&new_node->key, key, key_len); - memcpy(&new_node->value, value, value_len); + memcpy(new_node->key, key, key_len); + memcpy(new_node->value, value, value_len); map->buckets[hash % MAP_BUCKET_COUNT] = new_node; diff --git a/runtime/src/sledge_abi.c b/runtime/src/sledge_abi.c index b1c2146..6d1424a 100644 --- a/runtime/src/sledge_abi.c +++ b/runtime/src/sledge_abi.c @@ -1148,7 +1148,6 @@ DONE: * @param key_len * @param value_offset * @param value_len - * @returns 0 on success, 1 if already present, */ EXPORT void sledge_abi__scratch_storage_upsert(uint32_t key_offset, uint32_t key_len, uint32_t value_offset, uint32_t value_len) diff --git a/tests/scratch_storage/spec.json b/tests/scratch_storage/spec.json index 0047ea7..79ee5d1 100644 --- a/tests/scratch_storage/spec.json +++ b/tests/scratch_storage/spec.json @@ -20,6 +20,24 @@ "relative-deadline-us": 20000000, "http-resp-content-type": "text/plain", "http-resp-size": 1024 + }, + { + "route": "/delete", + "path": "scratch_storage_delete.wasm.so", + "expected-execution-us": 10000000, + "admissions-percentile": 70, + "relative-deadline-us": 20000000, + "http-resp-content-type": "text/plain", + "http-resp-size": 1024 + }, + { + "route": "/upsert", + "path": "scratch_storage_upsert.wasm.so", + "expected-execution-us": 10000000, + "admissions-percentile": 70, + "relative-deadline-us": 20000000, + "http-resp-content-type": "text/plain", + "http-resp-size": 1024 } ] } From b803befa0a1fd11d0dd7409f79cb75573cc0f1cf Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Thu, 26 May 2022 11:01:13 -0400 Subject: [PATCH 3/7] test: test concurrent access to key --- tests/scratch_storage/Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/scratch_storage/Makefile b/tests/scratch_storage/Makefile index 7239f8a..c4f42c2 100644 --- a/tests/scratch_storage/Makefile +++ b/tests/scratch_storage/Makefile @@ -36,5 +36,11 @@ debug: sledgert scratch_storage_get scratch_storage_set --eval-command="set pagination off" \ --eval-command="run spec.json" +client-upsert-multi: + hey -z 6s -cpus 4 -c 100 -t 0 -o csv -m GET "http://${HOSTNAME}:1337/upsert?test&wakka2" + +client-get-multi: + hey -z 6s -cpus 4 -c 100 -t 0 -o csv -m GET "http://${HOSTNAME}:1337/get?test" + client: http :1337/scratch_storage From 443f6c2c434efc7485bcb2b5f886f74770d28871 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Tue, 7 Jun 2022 14:09:17 -0400 Subject: [PATCH 4/7] fix: reorder map_node to improve alignment --- runtime/include/map.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/include/map.h b/runtime/include/map.h index 0bad1db..93ad65f 100644 --- a/runtime/include/map.h +++ b/runtime/include/map.h @@ -16,12 +16,12 @@ #define MAP_HASH jenkins_hash struct map_node { - uint32_t hash; + struct map_node *next; uint8_t *key; - uint32_t key_len; uint8_t *value; + uint32_t key_len; uint32_t value_len; - struct map_node *next; + uint32_t hash; }; struct map { From c1df8e2fc38bfe19ae160f27ce708d7f2e8e41dc Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Tue, 7 Jun 2022 15:28:43 -0400 Subject: [PATCH 5/7] fix: Address Gabe feedback --- applications/Makefile | 18 ++++ applications/scratch_storage/Makefile | 24 +++++ .../scratch_storage/scratch_storage_delete.c | 32 +++++++ .../scratch_storage/scratch_storage_get.c | 38 ++++++++ .../scratch_storage/scratch_storage_set.c | 35 +++++++ .../scratch_storage/scratch_storage_upsert.c | 28 ++++++ applications/wasm_apps | 2 +- runtime/include/map.h | 92 ++++++++++--------- runtime/include/xmalloc.h | 14 +++ 9 files changed, 240 insertions(+), 43 deletions(-) create mode 100644 applications/scratch_storage/Makefile create mode 100644 applications/scratch_storage/scratch_storage_delete.c create mode 100644 applications/scratch_storage/scratch_storage_get.c create mode 100644 applications/scratch_storage/scratch_storage_set.c create mode 100644 applications/scratch_storage/scratch_storage_upsert.c create mode 100644 runtime/include/xmalloc.h diff --git a/applications/Makefile b/applications/Makefile index a779fc1..ba0ec94 100644 --- a/applications/Makefile +++ b/applications/Makefile @@ -31,6 +31,7 @@ all: \ .PHONY: clean clean: @make -C wasm_apps clean + @make -C scratch_storage clean @rm -rf dist @rm -rf ../runtime/bin/*.so @@ -43,6 +44,23 @@ wasm_apps/dist/%.wasm: ../libsledge/dist/libsledge.a: ../libsledge/dist/ make -C .. libsledge +PHONY: scratch_storage +scratch_storage: + make -C scratch_storage all + +PHONY: scratch_storage.install +scratch_storage.install: \ + scratch_storage_get.install \ + scratch_storage_set.install \ + scratch_storage_delete.install \ + scratch_storage_upsert.install + +scratch_storage/scratch_storage_%.wasm: + make -C scratch_storage all + +dist/scratch_storage_%.bc: scratch_storage/scratch_storage_%.wasm dist + ${AWSMCC} ${AWSMFLAGS} $< -o $@ + dist/%.bc: ./wasm_apps/dist/%.wasm dist ${AWSMCC} ${AWSMFLAGS} $< -o $@ diff --git a/applications/scratch_storage/Makefile b/applications/scratch_storage/Makefile new file mode 100644 index 0000000..1a751d8 --- /dev/null +++ b/applications/scratch_storage/Makefile @@ -0,0 +1,24 @@ +include ../wasm_apps/common.mk + +.PHONY: all +all: \ + scratch_storage_get.wasm \ + scratch_storage_set.wasm \ + scratch_storage_delete.wasm \ + scratch_storage_upsert.wasm \ + +.PHONY: clean +clean: + @rm -f scratch_storage_set.wa* scratch_storage_get.wa* scratch_storage_delete.wa* scratch_storage_upsert.wa* + +scratch_storage_set.wasm: scratch_storage_set.c + @${WASMCC} ${WASMCFLAGS} ${WASMLDFLAGS} $^ -o $@ + +scratch_storage_get.wasm: scratch_storage_get.c + @${WASMCC} ${WASMCFLAGS} ${WASMLDFLAGS} $^ -o $@ + +scratch_storage_delete.wasm: scratch_storage_delete.c + @${WASMCC} ${WASMCFLAGS} ${WASMLDFLAGS} $^ -o $@ + +scratch_storage_upsert.wasm: scratch_storage_upsert.c + @${WASMCC} ${WASMCFLAGS} ${WASMLDFLAGS} $^ -o $@ diff --git a/applications/scratch_storage/scratch_storage_delete.c b/applications/scratch_storage/scratch_storage_delete.c new file mode 100644 index 0000000..5111107 --- /dev/null +++ b/applications/scratch_storage/scratch_storage_delete.c @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include + +extern int scratch_storage_delete(void *key, uint32_t key_len) + __attribute__((__import_module__("scratch_storage"), __import_name__("delete"))); + +int +main(int argc, char **argv) +{ + if (argc < 2) { + fprintf(stderr, "%s ", argv[0]); + return 0; + } + + char *key = argv[1]; + + if (key == NULL || strlen(key) < 0) { + fprintf(stderr, "%s ", argv[0]); + return 0; + } + + int rc = scratch_storage_delete(key, strlen(key)); + if (rc == 1) { + printf("Key '%s' not found\n", key); + return 0; + } else { + printf("Key %s deleted\n", key); + } +}; diff --git a/applications/scratch_storage/scratch_storage_get.c b/applications/scratch_storage/scratch_storage_get.c new file mode 100644 index 0000000..ba863e5 --- /dev/null +++ b/applications/scratch_storage/scratch_storage_get.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include + +extern int scratch_storage_get(void *key, uint32_t key_len, void *buf, uint32_t buf_len) + __attribute__((__import_module__("scratch_storage"), __import_name__("get"))); + +extern uint32_t scratch_storage_get_size(void *key, uint32_t key_len) + __attribute__((__import_module__("scratch_storage"), __import_name__("get_size"))); + +int +main(int argc, char **argv) +{ + if (argc < 2) { + fprintf(stderr, "%s ", argv[0]); + return 0; + } + + char *key = argv[1]; + + if (key == NULL || strlen(key) < 0) { + fprintf(stderr, "%s ", argv[0]); + return 0; + } + + uint32_t val_size = scratch_storage_get_size(key, strlen(key)); + char *buf = calloc(val_size + 1, sizeof(char)); + int rc = scratch_storage_get(key, strlen(key), buf, val_size); + assert(rc != 2); + if (rc == 1) { + printf("Key '%s' not found\n", key); + return 0; + } else { + printf("Key %s resolved to value of size %u with contents %s\n", key, val_size, buf); + } +}; diff --git a/applications/scratch_storage/scratch_storage_set.c b/applications/scratch_storage/scratch_storage_set.c new file mode 100644 index 0000000..232dc17 --- /dev/null +++ b/applications/scratch_storage/scratch_storage_set.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include + +extern int scratch_storage_set(void *key, uint32_t key_len, void *value, uint32_t value_len) + __attribute__((__import_module__("scratch_storage"), __import_name__("set"))); + +int +main(int argc, char **argv) +{ + if (argc < 3) { + fprintf(stderr, "%s ", argv[0]); + return 0; + } + + char *key = argv[1]; + char *value = argv[2]; + + if (key == NULL || strlen(key) < 0 || value == NULL || strlen(value) < 0) { + fprintf(stderr, "%s ", argv[0]); + return 0; + } + + int rc = scratch_storage_set(key, strlen(key), value, strlen(value)); + if (rc == 1) { + printf("Key %s was already present\n", key); + return 0; + } + + assert(rc == 0); + printf("Key %s set to value %s\n", key, value); + return rc; +}; diff --git a/applications/scratch_storage/scratch_storage_upsert.c b/applications/scratch_storage/scratch_storage_upsert.c new file mode 100644 index 0000000..6e05a96 --- /dev/null +++ b/applications/scratch_storage/scratch_storage_upsert.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +extern int scratch_storage_upsert(void *key, uint32_t key_len, void *value, uint32_t value_len) + __attribute__((__import_module__("scratch_storage"), __import_name__("upsert"))); + +int +main(int argc, char **argv) +{ + if (argc < 3) { + fprintf(stderr, "%s ", argv[0]); + return 0; + } + + char *key = argv[1]; + char *value = argv[2]; + + if (key == NULL || strlen(key) < 0 || value == NULL || strlen(value) < 0) { + fprintf(stderr, "%s ", argv[0]); + return 0; + } + + scratch_storage_upsert(key, strlen(key), value, strlen(value)); + printf("Key %s set to value %s\n", key, value); +}; diff --git a/applications/wasm_apps b/applications/wasm_apps index cf8a8ba..20a7c88 160000 --- a/applications/wasm_apps +++ b/applications/wasm_apps @@ -1 +1 @@ -Subproject commit cf8a8bafb10c37f207ccba1405cac07cd70bae8f +Subproject commit 20a7c88816c8f8882e03d42c76ff8c1e72bfeaec diff --git a/runtime/include/map.h b/runtime/include/map.h index 93ad65f..fa3e784 100644 --- a/runtime/include/map.h +++ b/runtime/include/map.h @@ -6,13 +6,12 @@ #include #include "lock.h" +#include "xmalloc.h" /* Simple K-V store based on The Practice of Programming by Kernighan and Pike */ /* Bucket count is sized to be a prime that is approximately 20% larger than the desired capacity (6k keys) */ #define MAP_BUCKET_COUNT 7907 -#define MAP_LOCK_STRIPES 17 - #define MAP_HASH jenkins_hash struct map_node { @@ -24,16 +23,22 @@ struct map_node { uint32_t hash; }; +struct map_bucket { + lock_t lock; + struct map_node *head; +}; + struct map { - struct map_node *buckets[MAP_BUCKET_COUNT]; - lock_t locks[MAP_LOCK_STRIPES]; + struct map_bucket buckets[MAP_BUCKET_COUNT]; }; static inline void map_init(struct map *restrict map) { - for (int i = 0; i < MAP_BUCKET_COUNT; i++) { map->buckets[i] = NULL; } - for (int i = 0; i < MAP_LOCK_STRIPES; i++) { LOCK_INIT(&map->locks[i]); } + for (int i = 0; i < MAP_BUCKET_COUNT; i++) { + map->buckets[i].head = NULL; + LOCK_INIT(&map->buckets[i].lock); + } }; /* See https://en.wikipedia.org/wiki/Jenkins_hash_function */ @@ -59,8 +64,11 @@ map_get(struct map *map, uint8_t *key, uint32_t key_len, uint32_t *ret_value_len uint8_t *value = NULL; uint32_t hash = MAP_HASH(key, key_len); - LOCK_LOCK(&map->locks[hash % MAP_LOCK_STRIPES]); - for (struct map_node *node = map->buckets[hash % MAP_BUCKET_COUNT]; node != NULL; node = node->next) { + + struct map_bucket *bucket = &map->buckets[hash % MAP_BUCKET_COUNT]; + + LOCK_LOCK(&bucket->lock); + for (struct map_node *node = bucket->head; node != NULL; node = node->next) { if (node->hash == hash) { value = node->value; *ret_value_len = node->value_len; @@ -71,7 +79,7 @@ map_get(struct map *map, uint8_t *key, uint32_t key_len, uint32_t *ret_value_len if (value == NULL) *ret_value_len = 0; DONE: - LOCK_UNLOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + LOCK_UNLOCK(&bucket->lock); return value; } @@ -80,33 +88,30 @@ map_set(struct map *map, uint8_t *key, uint32_t key_len, uint8_t *value, uint32_ { bool did_set = false; - uint32_t hash = MAP_HASH(key, key_len); - LOCK_LOCK(&map->locks[hash % MAP_LOCK_STRIPES]); - for (struct map_node *node = map->buckets[hash % MAP_BUCKET_COUNT]; node != NULL; node = node->next) { + uint32_t hash = MAP_HASH(key, key_len); + struct map_bucket *bucket = &map->buckets[hash % MAP_BUCKET_COUNT]; + LOCK_LOCK(&bucket->lock); + for (struct map_node *node = bucket->head; node != NULL; node = node->next) { if (node->hash == hash) goto DONE; } - struct map_node *new_node = (struct map_node *)malloc(sizeof(struct map_node)); - - *(new_node) = (struct map_node){ .hash = hash, - .key = malloc(key_len), - .key_len = key_len, - .value = malloc(value_len), - .value_len = value_len, - .next = map->buckets[hash % MAP_BUCKET_COUNT] }; - - assert(new_node->key); - assert(new_node->value); + struct map_node *new_node = (struct map_node *)xmalloc(sizeof(struct map_node)); + *(new_node) = (struct map_node){ .hash = hash, + .key = xmalloc(key_len), + .key_len = key_len, + .value = xmalloc(value_len), + .value_len = value_len, + .next = bucket->head }; // Copy Key and Value memcpy(new_node->key, key, key_len); memcpy(new_node->value, value, value_len); - map->buckets[hash % MAP_BUCKET_COUNT] = new_node; - did_set = true; + bucket->head = new_node; + did_set = true; DONE: - LOCK_UNLOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + LOCK_UNLOCK(&bucket->lock); return did_set; } @@ -118,12 +123,13 @@ map_delete(struct map *map, uint8_t *key, uint32_t key_len) { bool did_delete = false; - uint32_t hash = MAP_HASH(key, key_len); - LOCK_LOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + uint32_t hash = MAP_HASH(key, key_len); + struct map_bucket *bucket = &map->buckets[hash % MAP_BUCKET_COUNT]; + LOCK_LOCK(&bucket->lock); - struct map_node *prev = map->buckets[hash % MAP_BUCKET_COUNT]; - if (prev->hash == hash) { - map->buckets[hash % MAP_BUCKET_COUNT] = prev->next; + struct map_node *prev = bucket->head; + if (prev != NULL && prev->hash == hash) { + bucket->head = prev->next; free(prev->key); free(prev->value); free(prev); @@ -141,33 +147,35 @@ map_delete(struct map *map, uint8_t *key, uint32_t key_len) } DONE: - LOCK_UNLOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + LOCK_UNLOCK(&bucket->lock); return did_delete; } static inline void map_upsert(struct map *map, uint8_t *key, uint32_t key_len, uint8_t *value, uint32_t value_len) { - uint32_t hash = MAP_HASH(key, key_len); - LOCK_LOCK(&map->locks[hash % MAP_LOCK_STRIPES]); - for (struct map_node *node = map->buckets[hash % MAP_BUCKET_COUNT]; node != NULL; node = node->next) { + uint32_t hash = MAP_HASH(key, key_len); + struct map_bucket *bucket = &map->buckets[hash % MAP_BUCKET_COUNT]; + LOCK_LOCK(&bucket->lock); + + for (struct map_node *node = bucket->head; node != NULL; node = node->next) { if (node->hash == hash) { node->value_len = value_len; node->value = realloc(node->value, value_len); assert(node->value); memcpy(node->value, value, value_len); + goto DONE; } - goto DONE; } - struct map_node *new_node = (struct map_node *)malloc(sizeof(struct map_node)); + struct map_node *new_node = (struct map_node *)xmalloc(sizeof(struct map_node)); *(new_node) = (struct map_node){ .hash = hash, - .key = malloc(key_len), + .key = xmalloc(key_len), .key_len = key_len, - .value = malloc(value_len), + .value = xmalloc(value_len), .value_len = value_len, - .next = map->buckets[hash % MAP_BUCKET_COUNT] }; + .next = bucket->head }; assert(new_node->key); assert(new_node->value); @@ -176,8 +184,8 @@ map_upsert(struct map *map, uint8_t *key, uint32_t key_len, uint8_t *value, uint memcpy(new_node->key, key, key_len); memcpy(new_node->value, value, value_len); - map->buckets[hash % MAP_BUCKET_COUNT] = new_node; + bucket->head = new_node; DONE: - LOCK_UNLOCK(&map->locks[hash % MAP_LOCK_STRIPES]); + LOCK_UNLOCK(&bucket->lock); } diff --git a/runtime/include/xmalloc.h b/runtime/include/xmalloc.h new file mode 100644 index 0000000..8449da0 --- /dev/null +++ b/runtime/include/xmalloc.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "likely.h" +#include "panic.h" + +static inline void * +xmalloc(size_t size) +{ + void *allocation = malloc(size); + if (unlikely(allocation == NULL)) panic("xmalloc failed!\n"); + return allocation; +} From bfb280afb9134f00570082f038f2ae78e74330ff Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Tue, 7 Jun 2022 15:29:02 -0400 Subject: [PATCH 6/7] chore: Add gitignore scratch_storage --- applications/scratch_storage/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 applications/scratch_storage/.gitignore diff --git a/applications/scratch_storage/.gitignore b/applications/scratch_storage/.gitignore new file mode 100644 index 0000000..19e1bce --- /dev/null +++ b/applications/scratch_storage/.gitignore @@ -0,0 +1 @@ +*.wasm From 9b7cc44fd19caa07dab413f63426b1d0430e45b5 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Tue, 7 Jun 2022 15:49:04 -0400 Subject: [PATCH 7/7] chore: clang-format nit --- runtime/include/map.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/include/map.h b/runtime/include/map.h index fa3e784..b923af8 100644 --- a/runtime/include/map.h +++ b/runtime/include/map.h @@ -12,7 +12,7 @@ /* Bucket count is sized to be a prime that is approximately 20% larger than the desired capacity (6k keys) */ #define MAP_BUCKET_COUNT 7907 -#define MAP_HASH jenkins_hash +#define MAP_HASH jenkins_hash struct map_node { struct map_node *next;