feat: Initial scratch storage implementation

master
Sean McBride 3 years ago
parent 5b8a585e87
commit e5b222d83d

@ -88,3 +88,9 @@ stack_overflow.install: ../runtime/bin/stack_overflow.wasm.so
.PHONY: html.install .PHONY: html.install
html.install: ../runtime/bin/html.wasm.so 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

@ -1 +1 @@
Subproject commit 456edf5cecd7edc36314ba87e2d76ebf8cf1b5db Subproject commit 54de4d27f9a6e1f20b65b7110612a658479e6133

@ -26,7 +26,7 @@ dist:
dist/%.o: src/%.c dist dist/%.o: src/%.c dist
clang ${CFLAGS} -c ${INCLUDES} -o $@ $< 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 $^ ar rcs dist/libsledge.a $^
clean: clean:

@ -249,3 +249,10 @@ uint32_t sledge_abi__wasi_snapshot_preview1_sock_send(__wasi_fd_t fd, __wasi_siz
__wasi_size_t nbytes_retoffset); __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__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);

@ -0,0 +1,65 @@
#include <stdint.h>
#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);
}

@ -5,6 +5,7 @@
#include "arch/getcycles.h" #include "arch/getcycles.h"
#include "runtime.h" #include "runtime.h"
#include "generic_thread.h"
typedef ck_spinlock_mcs_t lock_t; typedef ck_spinlock_mcs_t lock_t;

@ -0,0 +1,183 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#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]);
}

@ -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) { if (did_set[route_config_member_http_resp_content_type] == false) {
debuglog("http_resp_content_type not set, defaulting to text/plain\n"); debuglog("http_resp_content_type not set, defaulting to text/plain\n");
config->http_resp_content_type = "text/plain"; config->http_resp_content_type = "text/plain";
return -1;
} }
if (scheduler == SCHEDULER_EDF) { if (scheduler == SCHEDULER_EDF) {

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "http_router.h" #include "http_router.h"
#include "map.h"
#include "module_database.h" #include "module_database.h"
#include "tcp_server.h" #include "tcp_server.h"
@ -9,4 +10,5 @@ struct tenant {
struct tcp_server tcp_server; struct tcp_server tcp_server;
http_router_t router; http_router_t router;
struct module_database module_db; struct module_database module_db;
struct map scratch_storage;
}; };

@ -37,6 +37,7 @@ tenant_alloc(struct tenant_config *config)
tcp_server_init(&tenant->tcp_server, config->port); tcp_server_init(&tenant->tcp_server, config->port);
http_router_init(&tenant->router, config->routes_len); http_router_init(&tenant->router, config->routes_len);
module_database_init(&tenant->module_db); module_database_init(&tenant->module_db);
map_init(&tenant->scratch_storage);
for (int i = 0; i < config->routes_len; i++) { for (int i = 0; i < config->routes_len; i++) {
struct module *module = module_database_find_by_path(&tenant->module_db, config->routes[i].path); struct module *module = module_database_find_by_path(&tenant->module_db, config->routes[i].path);

@ -1,4 +1,5 @@
#include "current_sandbox.h" #include "current_sandbox.h"
#include "map.h"
#include "sandbox_set_as_running_sys.h" #include "sandbox_set_as_running_sys.h"
#include "sandbox_set_as_running_user.h" #include "sandbox_set_as_running_user.h"
#include "sledge_abi.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__); 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);
}

@ -0,0 +1,3 @@
res
perf.data
perf.data.old

@ -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

@ -0,0 +1,2 @@
SLEDGE_SCHEDULER=EDF
SLEDGE_DISABLE_PREEMPTION=true

@ -0,0 +1,3 @@
SLEDGE_SCHEDULER=EDF
SLEDGE_DISABLE_PREEMPTION=false
SLEDGE_SIGALRM_HANDLER=TRIAGED

@ -0,0 +1,2 @@
SLEDGE_SCHEDULER=FIFO
SLEDGE_DISABLE_PREEMPTION=true

@ -0,0 +1,2 @@
SLEDGE_SCHEDULER=FIFO
SLEDGE_DISABLE_PREEMPTION=false

@ -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

@ -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
}
]
}
]
Loading…
Cancel
Save