Compare commits

..

1 Commits

@ -1,3 +1,4 @@
---
BasedOnStyle: Mozilla
IndentWidth: 8
Language: Cpp
@ -9,10 +10,10 @@ AlignConsecutiveMacros: true
AlignEscapedNewlines: Left
AlignTrailingComments: true
AllowShortBlocksOnASingleLine: Always
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: AllDefinitions
@ -37,7 +38,7 @@ BreakBeforeBinaryOperators: NonAssignment
ColumnLimit: 120
Cpp11BracedListStyle: true
Cpp11BracedListStyle: false
IndentCaseLabels: false
IndentWrappedFunctionNames: false
@ -46,9 +47,10 @@ KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 2
DerivePointerAlignment: false
PointerAlignment: Right
SortIncludes: true
SortIncludes: false
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true

@ -4,7 +4,7 @@ on: [push, pull_request]
env:
LLVM_VERSION: 13
WASI_SDK_VERSION: 12
WASI_SDK_URL: https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk-12.0-linux.tar.gz
WASI_SDK_PATH: /opt/wasi-sdk
LANG: C.UTF-8
LANGUAGE: C.UTF-8
@ -13,18 +13,18 @@ env:
# job control
jobs:
format:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- name: Apt Update
run: sudo apt-get update
- uses: actions/checkout@v2
- name: Install Clang Format
- name: Install LLVM
run: |
sudo ./install_llvm.sh $LLVM_VERSION
- name: Run Clang Format
- name: Clang Format
run: ./format.sh -d
test:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- name: Apt Update
run: sudo apt-get update
@ -62,7 +62,6 @@ jobs:
echo "/root/.cargo/bin:$PATH" >> $GITHUB_PATH
- name: Get wasi-sdk
run: |
WASI_SDK_URL=https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$WASI_SDK_VERSION/wasi-sdk-$WASI_SDK_VERSION.0-linux.tar.gz
wget $WASI_SDK_URL -O wasi-sdk.tar.gz
mkdir -p $WASI_SDK_PATH
tar xvfz wasi-sdk.tar.gz --strip-components=1 -C $WASI_SDK_PATH
@ -95,9 +94,6 @@ jobs:
- name: Compile SLEdge
run: |
make runtime
- name: Install wasm_apps link
run: |
make wasm_apps
# TODO:Cache assets before being copied to ./runtime/bin
- name: Cache gocr
uses: actions/cache@v2
@ -186,7 +182,3 @@ jobs:
run: |
make -f test.mk trap_divzero
if: success() || failure()
- name: Wasm Trap Stack Overflow
run: |
make -f test.mk stack_overflow
if: success() || failure()

1
.gitignore vendored

@ -52,7 +52,6 @@ dkms.conf
runtime/tags
runtime/bin
applications/wasm_apps
applications/tmp/
applications/**/*.csv
applications/**/*.txt

4
.gitmodules vendored

@ -12,3 +12,7 @@
[submodule "jsmn"]
path = runtime/thirdparty/jsmn
url = https://github.com/gwsystems/jsmn.git
[submodule "wasm_apps"]
path = applications/wasm_apps
url = https://github.com/gwsystems/wasm_apps.git
branch = master

@ -1,23 +1,24 @@
{
"configurations": [
{
"name": "Linux",
"intelliSenseMode": "clang-x64",
"includePath": [
"/usr/include/",
"${workspaceFolder}/runtime/include/",
"${workspaceFolder}/runtime/thirdparty/ck/include/",
"${workspaceFolder}/runtime/thirdparty/http-parser/",
"${workspaceFolder}/runtime/thirdparty/jsmn/",
"${workspaceFolder}/awsm/runtime/libc/wasi/include/",
"${workspaceFolder}/libsledge/include"
],
"defines": [
"x86_64",
"_GNU_SOURCE"
],
"cStandard": "c17"
}
],
"version": 4
}
"configurations": [
{
"name": "Linux",
"intelliSenseMode": "clang-x64",
"includePath": [
"/usr/include/",
"${workspaceFolder}/runtime/include/",
"${workspaceFolder}/runtime/thirdparty/ck/include/",
"${workspaceFolder}/runtime/thirdparty/http-parser/",
"${workspaceFolder}/runtime/thirdparty/jsmn/",
"${workspaceFolder}/awsm/runtime/libc/wasi/include/",
"${workspaceFolder}/libsledge/include"
],
"defines": [
"x86_64",
"_GNU_SOURCE"
],
"cStandard": "c17",
"compilerPath": "/usr/bin/clang"
}
],
"version": 4
}

@ -110,54 +110,6 @@
"current_wasm_module_instance.h": "c",
"wasm_memory.h": "c",
"sledge_abi.h": "c",
"vec.h": "c",
"perf_window_t.h": "c",
"module_config.h": "c",
"tenant.h": "c",
"route_config.h": "c",
"http_router.h": "c",
"execution_histogram.h": "c",
"tcp_server.h": "c",
"stdint.h": "c",
"scheduler_options.h": "c",
"route_config_parse.h": "c",
"route.h": "c",
"pool.h": "c",
"local_cleanup_queue.h": "c",
"sandbox_state_transition.h": "c",
"http_session_perf_log.h": "c",
"traffic_control.h": "c",
"memory_resource": "c",
"memory": "c",
"istream": "c",
"ostream": "c",
"sstream": "c",
"streambuf": "c",
"sandbox_perf_log.h": "c",
"global_request_scheduler_deque.h": "c",
"message.h": "c",
"dbf.h": "c",
"dbf_generic.h": "c",
"tenant_functions.h": "c",
"thread": "c",
"limits": "c",
"algorithm": "c",
"stdio.h": "c",
"get_time.h": "c",
"unistd.h": "c",
"wasi.h": "c",
"stat.h": "c",
"functional": "c",
"sandbox_state.h": "c",
"ratio": "c",
"tuple": "c",
"type_traits": "c",
"perf_window.h": "c",
"http_route_total.h": "c",
"sledge_abi_symbols.h": "c",
"mutex": "c",
"lock.h": "c",
"route_latency.h": "c"
},
"files.exclude": {
"**/.git": true,
@ -218,7 +170,5 @@
"mctx",
"TKILL",
"WASI"
],
"C_Cpp.errorSquiggles": "Enabled",
"C_Cpp.default.compilerPath": "/usr/bin/clang"
]
}

@ -1,14 +1,11 @@
# using ubuntu 20 docker image
FROM ubuntu:focal
ENV LLVM_VERSION=13
ENV WASI_SDK_VERSION=12
ARG DEBIAN_FRONTEND=noninteractive
ARG HEY_URL=https://hey-release.s3.us-east-2.amazonaws.com/hey_linux_amd64
ARG WASI_SDK_URL=https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$WASI_SDK_VERSION/wasi-sdk_$WASI_SDK_VERSION.0_amd64.deb
ARG WASI_SDK_URL=https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk_12.0_amd64.deb
ARG SHFMT_URL=https://github.com/mvdan/sh/releases/download/v3.2.4/shfmt_v3.2.4_linux_amd64
ARG SHELLCHECK_URL=https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.linux.x86_64.tar.xz
ARG SHELLCHECK_URL=https://github.com/koalaman/shellcheck/releases/download/v0.7.1/shellcheck-v0.7.1.linux.x86_64.tar.xz
# Use bash, not sh
SHELL ["/bin/bash", "-c"]
@ -77,12 +74,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
vim \
wabt
ENV LLVM_VERSION=12
ADD install_llvm.sh /sledge/install_llvm.sh
RUN ./sledge/install_llvm.sh $LLVM_VERSION
# WASI-SDK
RUN curl -sS -L -O $WASI_SDK_URL && dpkg -i wasi-sdk_$WASI_SDK_VERSION.0_amd64.deb && rm -f wasi-sdk_$WASI_SDK_VERSION.0_amd64.deb
ENV WASI_SDK_PATH=/opt/wasi-sdk
RUN curl -sS -L -O $WASI_SDK_URL && dpkg -i wasi-sdk_12.0_amd64.deb && rm -f wasi-sdk_12.0_amd64.deb
ENV WASI_SDK=/opt/wasi-sdk
# Create non-root user and add to sudoers
ARG USERNAME=dev

@ -11,7 +11,7 @@ submodules:
git submodule update --init --recursive
.PHONY: install
install: submodules wasm_apps all
install: submodules all
# aWsm: the WebAssembly to LLVM bitcode compiler
.PHONY: awsm
@ -50,10 +50,6 @@ applications:
applications.clean:
make -C applications clean
# Instead of having two copies of wasm_apps, just link to the awsm repo's copy
wasm_apps:
ln -sr awsm/applications/wasm_apps/ applications/
# Tests
.PHONY: test
test:

@ -80,34 +80,26 @@ And then simply delete this repository.
An SLEdge serverless function consists of a shared library (\*.so) and a JSON configuration file that determines how the runtime should execute the serverless function. As an example, here is the configuration file for our sample fibonacci function:
```json
[
{
"name": "GWU",
"port": 10010,
"routes": [
{
"route": "/fib",
"path": "fibonacci.wasm.so",
"expected-execution-us": 6000,
"relative-deadline-us": 20000,
"http-resp-content-type": "text/plain"
}
]
}
]
{
"name": "fibonacci",
"path": "fibonacci.wasm.so",
"port": 10000,
"expected-execution-us": 600,
"relative-deadline-us": 2000,
"http-req-size": 1024,
"http-resp-size": 1024,
"http-resp-content-type": "text/plain"
}
```
The `port` and `route` fields are used to determine the path where our serverless function will be served served.
The `port` and `name` fields are used to determine the path where our serverless function will be served served.
In our case, we are running the SLEdge runtime on localhost, so our function is available at `localhost:10010/fib`.
In our case, we are running the SLEdge runtime on localhost, so our function is available at `http://localhost:10000/fibonacci`
Our fibonacci function will parse a single argument from the HTTP POST body that we send. The expected Content-Type is "text/plain".
Our fibonacci function will parse a single argument from the HTTP POST body that we send. The expected Content-Type is "text/plain" and the buffer is sized to 1024 bytes for both the request and response. This is sufficient for our simple Fibonacci function, but this must be changed and sized for other functions, such as image processing.
Now that we understand roughly how the SLEdge runtime interacts with serverless function, let's run Fibonacci!
The fastest way to check it out is just to click on the following URL on your Web browser: [http://localhost:10010/fib?10](http://localhost:10010/fib?10)
From the root project directory of the host environment (not the Docker container!), navigate to the binary directory
```bash
@ -117,33 +109,25 @@ cd runtime/bin/
Now run the sledgert binary, passing the JSON file of the serverless function we want to serve. Because serverless functions are loaded by SLEdge as shared libraries, we want to add the `applications/` directory to LD_LIBRARY_PATH.
```bash
LD_LIBRARY_PATH="$(pwd):$LD_LIBRARY_PATH" ./sledgert ../../tests/fibonacci/bimodal/spec.json
LD_LIBRARY_PATH="$(pwd):$LD_LIBRARY_PATH" ./sledgert ../tests/test_fibonacci.json
```
While you don't see any output to the console, the runtime is running in the foreground.
Let's now invoke our serverless function to compute the 10th fibonacci number. We'll use `cURL` and [HTTPie](https://httpie.org/) to send a HTTP GET and POST requests with the parameter we want to pass to my serverless function. Feel free to use whatever other network client you prefer!
Let's now invoke our serverless function to compute the 10th fibonacci number. I'll use [HTTPie](https://httpie.org/) to send a POST request with a body containing the parameter I want to pass to my serverless function. Feel free to use cURL or whatever network client you prefer!
Open a **new** terminal session and execute the following
Open a new terminal session and execute the following
```bash
# HTTP GET method:
http localhost:10010/fib?10
curl localhost:10010/fib?10
# HTTP POST method:
echo "10" | http POST localhost:10010/fib
curl -i -d 10 localhost:10010/fib
echo "10" | http :10000
```
You should receive the following in response. The serverless function says that the 10th fibonacci number is 55, which seems to be correct!
```bash
HTTP/1.1 200 OK
Server: SLEdge
Connection: close
Content-Type: text/plain
Content-Length: 3
Content-length: 3
Content-type: text/plain
55
```

@ -11,11 +11,8 @@ CFLAGS=-O3 -flto
LDFLAGS=-shared -fPIC -Wl,--export-dynamic,--whole-archive -L../libsledge/dist/ -lsledge -Wl,--no-whole-archive
# LDFLAGS=-flto -fvisibility=hidden
# Strips out calls to assert() and disables debuglog
CFLAGS+=-DNDEBUG
dist:
mkdir -p dist
mkdir dist
.PHONY: all
all: \
@ -25,9 +22,7 @@ all: \
gocr.install \
gps_ekf.install \
license_plate_detection.install \
resize_image.install \
cnn_face_detection.install \
get_jpeg_resolution.install \
resize_image.install
.PHONY: clean
clean:
@ -44,28 +39,11 @@ 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 $@
dist/%.ll: dist/%.bc
llvm-dis $< -o $@
llvm-dis-12 $< -o $@
dist/%.wasm.so: dist/%.bc
${CC} ${CFLAGS} ${LDFLAGS} $^ -o $@
@ -102,32 +80,5 @@ gps_ekf.install: ../runtime/bin/gps_ekf.wasm.so
.PHONY: license_plate_detection.install
license_plate_detection.install: ../runtime/bin/license_plate_detection.wasm.so
.PHONY: cnn_face_detection.install
cnn_face_detection.install: ../runtime/bin/cnn_face_detection.wasm.so
.PHONY: get_jpeg_resolution.install
get_jpeg_resolution.install: ../runtime/bin/get_jpeg_resolution.wasm.so
.PHONY: trap_divzero.install
trap_divzero.install: ../runtime/bin/trap_divzero.wasm.so
.PHONY: stack_overflow.install
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
.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
.PHONY: depth_to_xyz.install
depth_to_xyz.install: ../runtime/bin/depth_to_xyz.wasm.so

@ -1,24 +0,0 @@
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 $@

@ -1,32 +0,0 @@
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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 <key>", argv[0]);
return 0;
}
char *key = argv[1];
if (key == NULL || strlen(key) < 0) {
fprintf(stderr, "%s <key>", 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);
}
};

@ -1,38 +0,0 @@
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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 <key>", argv[0]);
return 0;
}
char *key = argv[1];
if (key == NULL || strlen(key) < 0) {
fprintf(stderr, "%s <key>", 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);
}
};

@ -1,35 +0,0 @@
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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 <key> <value>", 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 <key> <value>", 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;
};

@ -1,28 +0,0 @@
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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 <key> <value>", 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 <key> <value>", argv[0]);
return 0;
}
scratch_storage_upsert(key, strlen(key), value, strlen(value));
printf("Key %s set to value %s\n", key, value);
};

@ -0,0 +1 @@
Subproject commit 4e6dc2a2918f787c610b00d4da13b7620d7d43c5

@ -1 +1 @@
Subproject commit 272fcf42b6559ccb5c5213eb78edfc0f703520ab
Subproject commit 3aca5263d346065173d35437c319d6d3d61204b9

@ -136,7 +136,6 @@ envrun() {
echo "Starting ${SYS_DOC_NAME}"
docker run \
--privileged \
--network="host" \
--security-opt seccomp:unconfined \
--name=${SYS_DOC_NAME} \
--detach \

@ -1,59 +0,0 @@
SLEdge only implemented a subset of the WASI syscall interface
## Arguments
The WASI calls `args_sizes_get` and `args_get` are supported. HTTP query parameters are captured and passed as arguments.
## Environment Variables
The WASI calls `environ_get` and `environ_sizes_get` are supported, but mostly unused. The current behavior is to to pass the runtime's environment variables into the sandbox. This is likely undesirable.
Presumably, the runtime should provide a standard set of environment variables and also allow the JSON spec to set additional function-specific environment variables.
See the reference of environment variables generated by WAGI for details: https://github.com/deislabs/wagi/blob/main/docs/environment_variables.md
## Clocks
`clock_time_get` is implemented but untested. `clock_res_get` is unimplemented.
## File System
SLEdge only supports `fd_read` from stdin and `fd_write` to stderr or stdout.
stdin is populated with the body of an HTTP POST request. stdout and stderr are both written in an interleaved fashion into a buffer and sent back to the client as the response body.
Actual access to the file system is unsupported, and sandboxes are not provided any preopened descriptors.
## Poll
`poll_oneoff` is unsupposed because SLEdge serverless functions are short lived. Sandboxed functions are assumed to make blocking reads/writes to stdin/stdout/stderr, and the serverless runtime is responsible for causing serverless functions to sleep and wake.
## Exit
`proc_exit` is supported and causes a sandbox to terminate execution.
## Signals
`proc_raise` is not supported. Signals are used by the runtime to provide preemption and context switching. It would be dangerous to trigger actual host signals from a sandbox.
However, the function could be implemented by creating a switch on the wasi signal and either ignoring or handling the signal within the `proc_raise` function itself.
`SIGABRT` could trigger the sandbox to exit in an abnormal condition.
The default ignore behavior could log the unexpected signal and return.
## Random
`random_get` is supported but largely untested.
## Yield
`sched_yield` is unsupported. This does not match with the run-to-completion nature of serverless.
In the case of EDF, a sandbox would always yield to itself. However, in the case of FIFO, we could enable this call to allow for a worker to "round robin" within a runqueue. However, it is unclear what the rationale would be to allow a serverless function to impact the scheduler.
## Sockets
All socket syscalls are unimplemented because the current logic around `sock_accept` and `sock_shutdown` seems to be focused on long-lived daemon nanoprocesses that handle multiple requests. The `poll_oneoff` call also seems to be based on this usecase.
Generally, a serverless function is expected to only make outbound network requests. However, this use case does not seem to be currently supported by WASI.

@ -1,9 +1,7 @@
#!/bin/bash
LLVM_VERSION=13
validate() {
utility="clang-format"
utility="clang-format-13"
utility_version="$("$utility" --version 2> /dev/null)" || {
echo "$utility not found in path!"
exit 1
@ -13,7 +11,7 @@ validate() {
declare -i major=0
declare -i minor=0
declare -i patch=0
declare -i required_major=$LLVM_VERSION
declare -i required_major=13
declare -i required_minor=0
declare -i required_patch=0
@ -45,14 +43,14 @@ help() {
dry_run() {
find runtime \
\( -path "runtime/thirdparty" \) -prune -false -o \
-type f \( -iname \*.h -o -iname \*.c -o -iname \*.s \) -print0 \
| xargs --null clang-format -Werror -n -ferror-limit=1
\( -path "runtime/thirdparty" -o -path "applications/gocr" -o -path "applications/TinyEKF" -o -path "applications/CMSIS_5_NN" -o -path "applications/sod" -o -path "applications/**/thirdparty" \) -prune -false -o \
-type f \( -iname \*.h -o -iname \*.c -o -iname \*.s \) -print \
| xargs clang-format -Werror -n -ferror-limit=0
}
format() {
find runtime \
\( -path "runtime/thirdparty" \) -prune -false -o \
\( -path "runtime/thirdparty" -o -path "applications/gocr" -o -path "applications/TinyEKF" -o -path "applications/CMSIS_5_NN" -o -path "applications/sod" -o -path "applications/**/thirdparty" \) -prune -false -o \
-type f \( -iname \*.h -o -iname \*.c -o -iname \*.s \) -print0 \
| xargs --null clang-format -i
}

@ -1,14 +1,12 @@
#!/bin/bash
# Note, wasi-sdk versions do NOT match llvm versions, e.g. wasi-sdk-12 actually uses llvm-11
LLVM_VERSION=13
WASI_SDK_VERSION=12
LLVM_VERSION=12
ARCH=$(uname -m)
ARCH=$(uname -p)
if [[ $ARCH = "x86_64" ]]; then
SHFMT_URL=https://github.com/mvdan/sh/releases/download/v3.4.3/shfmt_v3.4.3_linux_amd64
WASI_SDK_URL=https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$WASI_SDK_VERSION/wasi-sdk_$WASI_SDK_VERSION.0_amd64.deb
WASI_SDK_URL=https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk_12.0_amd64.deb
elif [[ $ARCH = "aarch64" ]]; then
SHFMT_URL=https://github.com/patrickvane/shfmt/releases/download/master/shfmt_linux_arm
echo "ARM64 support is still a work in progress!"
@ -66,7 +64,7 @@ wget $SHFMT_URL -O shfmt && chmod +x shfmt && sudo mv shfmt /usr/local/bin/shfmt
sudo ./install_llvm.sh $LLVM_VERSION
curl -sS -L -O $WASI_SDK_URL && sudo dpkg -i wasi-sdk_$WASI_SDK_VERSION.0_amd64.deb && rm -f wasi-sdk_$WASI_SDK_VERSION.0_amd64.deb
curl -sS -L -O $WASI_SDK_URL && sudo dpkg -i wasi-sdk_12.0_amd64.deb && rm -f wasi-sdk_12.0_amd64.deb
if [ -z "${WASI_SDK_PATH}" ]; then
export WASI_SDK_PATH=/opt/wasi-sdk

@ -8,28 +8,28 @@ echo "Installing LLVM $LLVM_VERSION"
# Script Installs clang, lldb, lld, and clangd
curl --proto '=https' --tlsv1.2 -sSf https://apt.llvm.org/llvm.sh | bash -s -- "$LLVM_VERSION"
# Installing "libc++-xx-dev" automagically installs "libc++1-xx", "libunwind-xx" and "libunwind-xx-dev"
apt-get install -y --no-install-recommends \
"libc++-$LLVM_VERSION-dev" \
"libc++abi-$LLVM_VERSION-dev" \
"libc++1-$LLVM_VERSION" \
"libunwind-$LLVM_VERSION" \
"libunwind-$LLVM_VERSION-dev" \
"clang-tools-$LLVM_VERSION" \
"clang-tidy-$LLVM_VERSION" \
"clang-format-$LLVM_VERSION"
update-alternatives --remove-all wasm-ld
update-alternatives --remove-all llvm-config
update-alternatives --remove-all llvm-objdump
update-alternatives --remove-all llvm-dis
update-alternatives --remove-all clang-format
update-alternatives --remove-all clang
update-alternatives --remove-all clang++
update-alternatives --remove-all clang-tidy
sudo update-alternatives --remove-all clang-format
sudo update-alternatives --remove-all clang
sudo update-alternatives --remove-all clang++
sudo update-alternatives --remove-all llvm-config
sudo update-alternatives --remove-all llvm-objdump
sudo update-alternatives --remove-all llvm-objdump
sudo update-alternatives --remove-all clang-tidy
update-alternatives --install /usr/bin/wasm-ld wasm-ld "/usr/bin/wasm-ld-$LLVM_VERSION" 100
update-alternatives --install /usr/bin/llvm-config llvm-config "/usr/bin/llvm-config-$LLVM_VERSION" 100
update-alternatives --install /usr/bin/llvm-objdump llvm-objdump "/usr/bin/llvm-objdump-$LLVM_VERSION" 100
update-alternatives --install /usr/bin/llvm-dis llvm-dis /usr/bin/llvm-dis-$LLVM_VERSION 100
update-alternatives --install /usr/bin/clang-format clang-format "/usr/bin/clang-format-$LLVM_VERSION" 100
update-alternatives --install /usr/bin/clang clang "/usr/bin/clang-$LLVM_VERSION" 100
update-alternatives --install /usr/bin/clang++ clang++ "/usr/bin/clang++-$LLVM_VERSION" 100
update-alternatives --install /usr/bin/llvm-config llvm-config "/usr/bin/llvm-config-$LLVM_VERSION" 100
update-alternatives --install /usr/bin/llvm-objdump llvm-objdump "/usr/bin/llvm-objdump-$LLVM_VERSION" 100
update-alternatives --install /usr/bin/clang-tidy clang-tidy "/usr/bin/clang-tidy-$LLVM_VERSION" 100
update-alternatives --install /usr/bin/wasm-ld wasm-ld "/usr/bin/wasm-ld-$LLVM_VERSION" 100

@ -1,11 +1,7 @@
CFILES := src/*.c
INCLUDES := -Iinclude/
# fPIC = Position Independent Code, necessary for linking to relative addresses.
CFLAGS := -fPIC -O3 -flto -ftls-model=initial-exec
# Strips out calls to assert() and disables debuglog
CFLAGS+=-DNDEBUG
CFLAGS := -fPIC -O3
# CFI Sanitizer
# CFLAGS+=-fvisibility=default -fsanitize=cfi
@ -25,12 +21,12 @@ CFLAGS+=-DNDEBUG
all: dist/libsledge.a
dist:
mkdir -p dist
mkdir 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/sledge_extensions.o
dist/libsledge.a: dist/memory_instructions.o dist/numeric_instructions.o dist/table_instructions.o dist/variable_instructions.o dist/instantiation.o dist/wasi_snapshot_preview1.o
ar rcs dist/libsledge.a $^
clean:

@ -15,7 +15,7 @@ libsledge defines a ABI between the sledgert runtime and a \*.so shared library
A SLEdge \*.so serverless module is generated by the latter portion of the aWsm/SLEdge toolchain.
The first portion of the toolchain is responsible for compiling a source program into a WebAssembly module. This is handled by standard compilers capable of emitting WebAssembly.
The second portion of the toolchain is the aWsm compiler, which generates a \*.bc file with a well defined ABI.
The second portion of the toolchain is the aWsm compiler, which generates a \*.bc file with a well defined ABI
The third portion of the toolchain is the LLVM compiler, which ingests a \*.bc file emitted by aWsm and the libsledge static library, and emits a SLEdge \*.so serverless module.
## Architecture
@ -24,12 +24,16 @@ In order to reduce the overhead of calling sledgert functions, libsledge operate
The `sledge_abi__wasm_module_instance` structure includes the WebAssembly function table and the WebAssembly linear memory. This subset was selected because the author believes that use of function pointers and linear memory is frequent enough that LTO when compiling the \*.so file is beneficial.
All WebAssembly state
## WebAssembly Instruction Implementation
Here is a list of WebAssembly instructions that depend on symbols from libsledge, libc, or sledgert (via the SLEdge ABI).
### [Control Instructions](https://webassembly.github.io/spec/core/syntax/instructions.html#control-instructions)
The ABI includes the
| Instruction | aWsm ABI | libc Dependencies | SLEdge ABI |
| ------------- | ------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| call_indirect | `get_function_from_table` | `stderr`, `fprintf` | `sledge_abi__current_wasm_module_instance.table`, `sledge_abi__wasm_trap_raise`, `WASM_TRAP_INVALID_INDEX`, `WASM_TRAP_MISMATCHED_TYPE` |
@ -111,6 +115,13 @@ Here is a list of WebAssembly instructions that depend on symbols from libsledge
| memory.size | `instruction_memory_size` | | `sledge_abi__current_wasm_module_instance.memory` |
| None | `initialize_region` | | `sledge_abi__current_wasm_module_instance.memory`, `sledge_abi__wasm_memory_initialize_region` |
Discussion:
- Should `instruction_memory_grow` be moved into sledgert? This would simplify the handling of the "cache" and generating a memory profile?
- Rename `sledge_abi__wasm_globals_*` to `sledge_abi__wasm_global_*`
- Implement Unsupported Numeric Instructions
- Should the wasm global table be accessed directly instead of via a runtime function?
- Should the Function Table be handled by the \*.so file or sledgert? Are function pointers really called that frequently?
# SLEdge \*.so Module Loading / Initialization
@ -121,36 +132,33 @@ The `sledgert` runtime is invoked with an argument containing the path to a JSON
"relative-deadline-us"
"expected-execution-us"
"admissions-percentile"
"http-req-size"
"http-resp-size"
"http-resp-content-type"
The path to the JSON file is passed to `module_alloc_from_json`, which uses the Jasmine library to parse the JSON, performs validation, and passes the resulting specification to `module_alloc` for each module definition found. `module_alloc` allocated heap memory for a `struct module` and then calls `module_init`. `module_init` calls `sledge_abi_symbols_init`, which calls `dlopen` on the _.so file at the path specified in the JSON and then calls `dlsym` to resolve symbols within the _.so module.
- `module.abi.initialize_globals` -> `SLEDGE_ABI__INITIALIZE_GLOBALS` -> `populate_globals`
- `module.abi.initialize_memory`-> `SLEDGE_ABI__INITIALIZE_MEMORY` -> `populate_memory`
- `module.abi.initialize_table` -> `SLEDGE_ABI__INITIALIZE_TABLE` -> `populate_table`
- `module.abi.entrypoint` -> `SLEDGE_ABI__ENTRYPOINT` -> `wasmf__start`
- `module.abi.starting_pages` -> `SLEDGE_ABI__STARTING_PAGES` -> `starting_pages`
- `module.abi.max_pages` -> `SLEDGE_ABI__MAX_PAGES` -> `max_pages`
- `module.abi.globals_len` -> `SLEDGE_ABI__GLOBALS_LEN` -> `globals_len`
`module.abi.initialize_globals` -> `SLEDGE_ABI__INITIALIZE_GLOBALS` -> `populate_globals`
`module.abi.initialize_memory`-> `SLEDGE_ABI__INITIALIZE_MEMORY` -> `populate_memory`
`module.abi.initialize_table` -> `SLEDGE_ABI__INITIALIZE_TABLE` -> `populate_table`
`module.abi.entrypoint` -> `SLEDGE_ABI__ENTRYPOINT` -> `wasmf__start`
`module.abi.starting_pages` -> `SLEDGE_ABI__STARTING_PAGES` -> `starting_pages`
`module.abi.max_pages` -> `SLEDGE_ABI__MAX_PAGES` -> `max_pages`
`module.abi.globals_len` -> `SLEDGE_ABI__GLOBALS_LEN` -> `globals_len`
`module_init` then calls `module.abi.initialize_table`, which populates the indirect function table with the actual functions. This is performed once during module initialization because this table does not actually vary between instances of a module.
`module init` then calls `module.abi.initialize_table`, which populates the indirect function table with the actual functions. This is performed once during module initialization because this table does not actually vary between instances of a module.
# SLEdge \*.so Module Instantiation
When `sledgert` receives a request at the registered port specified in the JSON, it performs allocation and initialization steps. The scheduler sets the expected ABI symbols and yields to `current_sandbox_start`, which immediately calls `current_sandbox_init`. This function initializes the associated runtime state and
When `sledgert` receives a request at the registered port specified in the JSON, it performs assorted allocation and initialization steps. The scheduler sets the expected ABI symbols and yields to `current_sandbox_start`, which immediately calls `current_sandbox_init`. This function initializes the associated runtime state and
1. calls `module.abi.initialize_globals` for the current sandbox if not NULL. This is optional because the module might not have been built with the `--runtime-globals`, in which case runtime globals are not used at all. If not NULL, the globals are set in the table.
2. calls `module.abi.initialize_memory`, which copies segments into the linear memory
2. calls `module.abi.initialize_memory`, which copies regions into the linear memory
`current_sandbox_init` calls `wasi_context_init` to initialize the WASI context within the runtime.
`current_sandbox_init` returns to `current_sandbox_start`, which sets up wasm traps using `setjmp` and then calls `module.abi.entrypoint`
# Discussion (follow-up with Github issues):
# Questions:
- Should `sledge_abi__current_wasm_module_instance` be turned into a macro defined int the ABI header? That way it'll be easier to change the ABI symbols (change once, applied everywhere).
- Should `instruction_memory_grow` be moved into sledgert? This would simplify the handling of the "cache" and generating a memory profile?
- Rename `sledge_abi__wasm_globals_*` to `sledge_abi__wasm_global_*`
- Implement Unsupported Numeric Instructions
- Should the wasm global table be accessed directly instead of via a runtime function? If we expose the wasm global table to libsledge, then we have worse ABI stability, but better performance.
- Should the Function Table be handled by the \*.so file or sledgert? Are function pointers really called that frequently?
- Should `sledge_abi__current_wasm_module_instance` be turned into a macro defined int the ABI header?

@ -45,12 +45,12 @@ struct sledge_abi__wasm_module_instance {
/* Based on example traps listed at https://webassembly.org/docs/security/ */
enum sledge_abi__wasm_trap
{
WASM_TRAP_INVALID_INDEX = 1,
WASM_TRAP_MISMATCHED_TYPE = 2,
WASM_TRAP_PROTECTED_CALL_STACK_OVERFLOW = 3,
WASM_TRAP_OUT_OF_BOUNDS_LINEAR_MEMORY = 4,
WASM_TRAP_ILLEGAL_ARITHMETIC_OPERATION = 5,
WASM_TRAP_UNREACHABLE = 6,
WASM_TRAP_EXIT = 1,
WASM_TRAP_INVALID_INDEX = 2,
WASM_TRAP_MISMATCHED_TYPE = 3,
WASM_TRAP_PROTECTED_CALL_STACK_OVERFLOW = 4,
WASM_TRAP_OUT_OF_BOUNDS_LINEAR_MEMORY = 5,
WASM_TRAP_ILLEGAL_ARITHMETIC_OPERATION = 6,
WASM_TRAP_COUNT
};
@ -249,10 +249,3 @@ 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);

@ -1,7 +0,0 @@
#include "sledge_abi.h"
void
awsm_abi__trap_unreachable(void)
{
sledge_abi__wasm_trap_raise(WASM_TRAP_UNREACHABLE);
}

@ -19,6 +19,11 @@ get_f32(uint32_t offset)
{
assert(sledge_abi__current_wasm_module_instance.memory.buffer != NULL);
if (unlikely((uint64_t)offset + (uint64_t)sizeof(float)
> sledge_abi__current_wasm_module_instance.memory.size)) {
sledge_abi__wasm_trap_raise(WASM_TRAP_OUT_OF_BOUNDS_LINEAR_MEMORY);
}
return *(float *)&sledge_abi__current_wasm_module_instance.memory.buffer[offset];
}
@ -27,6 +32,11 @@ get_f64(uint32_t offset)
{
assert(sledge_abi__current_wasm_module_instance.memory.buffer != NULL);
if (unlikely((uint64_t)offset + (uint64_t)sizeof(double)
> sledge_abi__current_wasm_module_instance.memory.size)) {
sledge_abi__wasm_trap_raise(WASM_TRAP_OUT_OF_BOUNDS_LINEAR_MEMORY);
}
return *(double *)&sledge_abi__current_wasm_module_instance.memory.buffer[offset];
}
@ -35,6 +45,11 @@ get_i8(uint32_t offset)
{
assert(sledge_abi__current_wasm_module_instance.memory.buffer != NULL);
if (unlikely((uint64_t)offset + (uint64_t)sizeof(int8_t)
> sledge_abi__current_wasm_module_instance.memory.size)) {
sledge_abi__wasm_trap_raise(WASM_TRAP_OUT_OF_BOUNDS_LINEAR_MEMORY);
}
return *(int8_t *)&sledge_abi__current_wasm_module_instance.memory.buffer[offset];
}
@ -43,6 +58,11 @@ get_i16(uint32_t offset)
{
assert(sledge_abi__current_wasm_module_instance.memory.buffer != NULL);
if (unlikely((uint64_t)offset + (uint64_t)sizeof(int16_t)
> sledge_abi__current_wasm_module_instance.memory.size)) {
sledge_abi__wasm_trap_raise(WASM_TRAP_OUT_OF_BOUNDS_LINEAR_MEMORY);
}
return *(int16_t *)&sledge_abi__current_wasm_module_instance.memory.buffer[offset];
}
@ -51,6 +71,11 @@ get_i32(uint32_t offset)
{
assert(sledge_abi__current_wasm_module_instance.memory.buffer != NULL);
if (unlikely((uint64_t)offset + (uint64_t)sizeof(int32_t)
> sledge_abi__current_wasm_module_instance.memory.size)) {
sledge_abi__wasm_trap_raise(WASM_TRAP_OUT_OF_BOUNDS_LINEAR_MEMORY);
}
return *(int32_t *)&sledge_abi__current_wasm_module_instance.memory.buffer[offset];
}
@ -59,6 +84,11 @@ get_i64(uint32_t offset)
{
assert(sledge_abi__current_wasm_module_instance.memory.buffer != NULL);
if (unlikely((uint64_t)offset + (uint64_t)sizeof(int64_t)
> sledge_abi__current_wasm_module_instance.memory.size)) {
sledge_abi__wasm_trap_raise(WASM_TRAP_OUT_OF_BOUNDS_LINEAR_MEMORY);
}
return *(int64_t *)&sledge_abi__current_wasm_module_instance.memory.buffer[offset];
}
@ -68,6 +98,11 @@ set_f32(uint32_t offset, float value)
{
assert(sledge_abi__current_wasm_module_instance.memory.buffer != NULL);
if (unlikely((uint64_t)offset + (uint64_t)sizeof(float)
> sledge_abi__current_wasm_module_instance.memory.size)) {
sledge_abi__wasm_trap_raise(WASM_TRAP_OUT_OF_BOUNDS_LINEAR_MEMORY);
}
*(float *)&sledge_abi__current_wasm_module_instance.memory.buffer[offset] = value;
}
@ -76,6 +111,11 @@ set_f64(uint32_t offset, double value)
{
assert(sledge_abi__current_wasm_module_instance.memory.buffer != NULL);
if (unlikely((uint64_t)offset + (uint64_t)sizeof(double)
> sledge_abi__current_wasm_module_instance.memory.size)) {
sledge_abi__wasm_trap_raise(WASM_TRAP_OUT_OF_BOUNDS_LINEAR_MEMORY);
}
*(double *)&sledge_abi__current_wasm_module_instance.memory.buffer[offset] = value;
}
@ -84,6 +124,11 @@ set_i8(uint32_t offset, int8_t value)
{
assert(sledge_abi__current_wasm_module_instance.memory.buffer != NULL);
if (unlikely((uint64_t)offset + (uint64_t)sizeof(int8_t)
> sledge_abi__current_wasm_module_instance.memory.size)) {
sledge_abi__wasm_trap_raise(WASM_TRAP_OUT_OF_BOUNDS_LINEAR_MEMORY);
}
*(int8_t *)&sledge_abi__current_wasm_module_instance.memory.buffer[offset] = value;
}
@ -92,6 +137,11 @@ set_i16(uint32_t offset, int16_t value)
{
assert(sledge_abi__current_wasm_module_instance.memory.buffer != NULL);
if (unlikely((uint64_t)offset + (uint64_t)sizeof(int16_t)
> sledge_abi__current_wasm_module_instance.memory.size)) {
sledge_abi__wasm_trap_raise(WASM_TRAP_OUT_OF_BOUNDS_LINEAR_MEMORY);
}
*(int16_t *)&sledge_abi__current_wasm_module_instance.memory.buffer[offset] = value;
}
@ -100,6 +150,11 @@ set_i32(uint32_t offset, int32_t value)
{
assert(sledge_abi__current_wasm_module_instance.memory.buffer != NULL);
if (unlikely((uint64_t)offset + (uint64_t)sizeof(int32_t)
> sledge_abi__current_wasm_module_instance.memory.size)) {
sledge_abi__wasm_trap_raise(WASM_TRAP_OUT_OF_BOUNDS_LINEAR_MEMORY);
}
*(int32_t *)&sledge_abi__current_wasm_module_instance.memory.buffer[offset] = value;
}
@ -108,6 +163,11 @@ set_i64(uint32_t offset, int64_t value)
{
assert(sledge_abi__current_wasm_module_instance.memory.buffer != NULL);
if (unlikely((uint64_t)offset + (uint64_t)sizeof(int64_t)
> sledge_abi__current_wasm_module_instance.memory.size)) {
sledge_abi__wasm_trap_raise(WASM_TRAP_OUT_OF_BOUNDS_LINEAR_MEMORY);
}
*(int64_t *)&sledge_abi__current_wasm_module_instance.memory.buffer[offset] = value;
}

@ -54,48 +54,60 @@ rotr_u64(uint64_t n, uint64_t c_u64)
INLINE uint32_t
u32_div(uint32_t a, uint32_t b)
{
if (unlikely(b == 0)) sledge_abi__wasm_trap_raise(WASM_TRAP_ILLEGAL_ARITHMETIC_OPERATION);
return a / b;
}
INLINE uint32_t
u32_rem(uint32_t a, uint32_t b)
{
if (unlikely(b == 0)) sledge_abi__wasm_trap_raise(WASM_TRAP_ILLEGAL_ARITHMETIC_OPERATION);
return a % b;
}
INLINE int32_t
i32_div(int32_t a, int32_t b)
{
if (unlikely(b == 0 || (a == INT32_MIN && b == -1)))
sledge_abi__wasm_trap_raise(WASM_TRAP_ILLEGAL_ARITHMETIC_OPERATION);
return a / b;
}
INLINE int32_t
i32_rem(int32_t a, int32_t b)
{
if (unlikely(b == 0 || (a == INT32_MIN && b == -1)))
sledge_abi__wasm_trap_raise(WASM_TRAP_ILLEGAL_ARITHMETIC_OPERATION);
return a % b;
}
INLINE uint64_t
u64_div(uint64_t a, uint64_t b)
{
if (unlikely(b == 0)) sledge_abi__wasm_trap_raise(WASM_TRAP_ILLEGAL_ARITHMETIC_OPERATION);
return a / b;
}
INLINE uint64_t
u64_rem(uint64_t a, uint64_t b)
{
if (unlikely(b == 0)) sledge_abi__wasm_trap_raise(WASM_TRAP_ILLEGAL_ARITHMETIC_OPERATION);
return a % b;
}
INLINE int64_t
i64_div(int64_t a, int64_t b)
{
if (unlikely(b == 0 || (a == INT64_MIN && b == -1)))
sledge_abi__wasm_trap_raise(WASM_TRAP_ILLEGAL_ARITHMETIC_OPERATION);
return a / b;
}
INLINE int64_t
i64_rem(int64_t a, int64_t b)
{
if (unlikely(b == 0 || (a == INT64_MIN && b == -1)))
sledge_abi__wasm_trap_raise(WASM_TRAP_ILLEGAL_ARITHMETIC_OPERATION);
return a % b;
}

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

@ -1,10 +1,12 @@
#include <stdint.h>
#include "sledge_abi.h"
// TODO: Validate uint32_t as return value;
uint32_t
wasi_snapshot_preview1_args_get(__wasi_size_t argv_retoffset, __wasi_size_t argv_buf_retoffset)
{
return sledge_abi__wasi_snapshot_preview1_args_get(argv_retoffset, argv_buf_retoffset);
return sledge_abi__wasi_snapshot_preview1_args_get(argv_buf_retoffset, argv_buf_retoffset);
}
uint32_t
@ -266,7 +268,7 @@ wasi_snapshot_preview1_poll_oneoff(__wasi_size_t in_baseoffset, __wasi_size_t ou
void
wasi_snapshot_preview1_proc_exit(__wasi_exitcode_t exitcode)
{
sledge_abi__wasi_snapshot_preview1_proc_exit(exitcode);
sledge_abi__wasi_snapshot_preview1_proc_raise(exitcode);
}
uint32_t

@ -17,7 +17,7 @@ CFLAGS+=-D_GNU_SOURCE
CFLAGS+=-O3 -flto
# Debugging Flags
# CFLAGS+=-O0 -g3
# CFLAGS+=-O0 -g
# CFI Sanitizer
# CFLAGS+=-fvisibility=default -fsanitize=cfi
@ -38,10 +38,6 @@ BINARY_NAME=sledgert
# Feature Toggles
CFLAGS += -DEXECUTION_HISTOGRAM
# CFLAGS += -DEXECUTION_REGRESSION
# It is recommended (not mandatory) to enable this flag along with the EXECUTION_HISTOGRAM flag:
# CFLAGS += -DADMISSIONS_CONTROL
# Debugging Flags
@ -60,11 +56,11 @@ CFLAGS += -DEXECUTION_HISTOGRAM
# CFLAGS += -DLOG_TO_FILE
# Various Informational Logs for Debugging
# CFLAGS += -DLOG_EXECUTION_HISTOGRAM
# CFLAGS += -DLOG_ADMISSIONS_CONTROL
# CFLAGS += -DLOG_CONTEXT_SWITCHES
# CFLAGS += -DLOG_HTTP_PARSER
# CFLAGS += -DLOG_TENANT_LOADING
# CFLAGS += -DLOG_LOCK_OVERHEAD
# CFLAGS += -DLOG_MODULE_LOADING
# CFLAGS += -DLOG_PREEMPTION
# CFLAGS += -DLOG_SANDBOX_ALLOCATION
# CFLAGS += -DLOG_UNSUPPORTED_WASI
@ -80,22 +76,10 @@ CFLAGS += -DEXECUTION_HISTOGRAM
# page is allocated. This helps understand the relationship to memory allocation and execution time.
# CFLAGS += -DLOG_SANDBOX_MEMORY_PROFILE
# This flag enables runtime-level metrics from procfs
# CFLAGS += -DPROC_STAT_METRICS
# This flag enables HTTP-level counters of incoming requests and outgoing responses, broken out by status code
# family, such as 2XX, 4XX, 5XX.
# This flag dumps totals of incoming requests and outgoing responses, broken out by status code
# family, such as 2XX, 4XX, 5XX. It is useful to debug clients hanging waiting for a response.
# To log, run `call http_total_log()` while in GDB
# CFLAGS += -DHTTP_TOTAL_COUNTERS
# This flag enables per-route counters of incoming requests and outgoing responses, broken out by status code
# family, such as 2XX, 4XX, 5XX.
# CFLAGS += -DHTTP_ROUTE_TOTAL_COUNTERS
# This flag enables per-route latency perf-logs.
# This has a perf impact due to the use of locks.
# This flag has a dependency on the flag HTTP_ROUTE_TOTAL_COUNTERS
# CFLAGS += -DROUTE_LATENCY
# CFLAGS += -DLOG_TOTAL_REQS_RESPS
# This flag tracks the total number of sandboxes in the various states
# It is useful to debug if sandboxes are "getting caught" in a particular state
@ -144,13 +128,13 @@ all: thirdparty runtime
clean: thirdparty.clean runtime.clean
# sledgert Rules
bin/${BINARY_NAME}: ${HEADER_DEPS} ${CFILES}
bin/sledgert: ${HEADER_DEPS} ${CFILES}
@echo "Compiling runtime"
@mkdir -p bin/
@${CC} ${INCLUDES} ${CFLAGS} ${LDFLAGS} ${JSMNCFLAGS} -L/usr/lib/ ${CFILES} -o bin/${BINARY_NAME}
${CC} ${INCLUDES} ${CFLAGS} ${LDFLAGS} ${JSMNCFLAGS} -L/usr/lib/ ${CFILES} -o bin/sledgert
.PHONY: runtime
runtime: bin/${BINARY_NAME}
runtime: bin/sledgert
.PHONY: runtime.clean
runtime.clean:

@ -1,19 +1,14 @@
#pragma once
#ifdef ADMISSIONS_CONTROL
#include <stdbool.h>
#include <stdint.h>
#define ADMISSIONS_CONTROL_GRANULARITY 1000000
extern _Atomic uint64_t admissions_control_admitted;
extern uint64_t admissions_control_capacity;
void admissions_control_initialize(void);
void admissions_control_add(uint64_t admissions_estimate);
void admissions_control_subtract(uint64_t admissions_estimate);
uint64_t admissions_control_calculate_estimate(uint64_t estimated_execution, uint64_t relative_deadline);
uint64_t admissions_control_calculate_estimate_us(uint32_t estimated_execution_us, uint32_t relative_deadline_us);
void admissions_control_log_decision(uint64_t admissions_estimate, bool admitted);
uint64_t admissions_control_decide(uint64_t admissions_estimate);
#endif

@ -0,0 +1,15 @@
#pragma once
#include "perf_window_t.h"
struct admissions_info {
struct perf_window perf_window;
int percentile; /* 50 - 99 */
int control_index; /* Precomputed Lookup index when perf_window is full */
uint64_t estimate; /* cycles */
uint64_t relative_deadline; /* Relative deadline in cycles. This is duplicated state */
};
void admissions_info_initialize(struct admissions_info *admissions_info, int percentile, uint64_t expected_execution,
uint64_t relative_deadline);
void admissions_info_update(struct admissions_info *admissions_info, uint64_t execution_duration);

@ -4,7 +4,6 @@
#include <assert.h>
#include "arch/common.h"
#include "current_sandbox.h"
#define ARCH_SIG_JMP_OFF 0x100 /* Based on code generated! */
@ -46,25 +45,22 @@ arch_context_switch(struct arch_context *a, struct arch_context *b)
/*
* Assumption: In the case of a slow context switch, the caller
* set current_sandbox to the sandbox containing the target context
* Temporally comment because it will cause compilation error
*/
if (b->variant == ARCH_CONTEXT_VARIANT_SLOW) {
/*if (b->variant == ARCH_CONTEXT_VARIANT_SLOW) {
struct sandbox *current = current_sandbox_get();
assert(current != NULL && b == &current->ctxt);
}
}*/
#endif
/* if both a and b are NULL, there is no state change */
assert(a != NULL || b != NULL);
assert(a != NULL && b != NULL);
/* Assumption: The caller does not switch to itself */
assert(a != b);
/* Set any NULLs to worker_thread_base_context to resume execution of main */
if (a == NULL) a = &worker_thread_base_context;
if (b == NULL) b = &worker_thread_base_context;
/* A Transition {Unused, Running} -> Fast */
assert(a->variant == ARCH_CONTEXT_VARIANT_UNUSED || a->variant == ARCH_CONTEXT_VARIANT_RUNNING);
assert(a->variant == ARCH_CONTEXT_VARIANT_RUNNING);
/* B Transition {Fast, Slow} -> Running */
assert(b->variant == ARCH_CONTEXT_VARIANT_FAST || b->variant == ARCH_CONTEXT_VARIANT_SLOW);
@ -87,6 +83,8 @@ arch_context_switch(struct arch_context *a, struct arch_context *b)
"ldr x1, [%[bv]]\n\t"
"sub x1, x1, #2\n\t"
"cbz x1, slow%=\n\t"
"mov x3, #3\n\t"
"str x3, [%[bv]]\n\t" /* b->variant = ARCH_CONTEXT_VARIANT_RUNNING; */
"ldr x0, [%[b]]\n\t"
"ldr x1, [%[b], 8]\n\t"
"mov sp, x0\n\t"
@ -95,8 +93,6 @@ arch_context_switch(struct arch_context *a, struct arch_context *b)
"br %[slowpath]\n\t"
".align 8\n\t"
"reset%=:\n\t"
"mov x1, #3\n\t"
"str x1, [%[bv]]\n\t"
".align 8\n\t"
"exit%=:\n\t"
:
@ -109,6 +105,35 @@ arch_context_switch(struct arch_context *a, struct arch_context *b)
return 0;
}
/**
* Load a new sandbox that preempted an existing sandbox, restoring only the
* instruction pointer and stack pointer registers.
* @param active_context - the context of the current worker thread
* @param sandbox_context - the context that we want to restore
*/
static inline void
arch_context_restore_fast(mcontext_t *active_context, struct arch_context *sandbox_context)
{
assert(active_context != NULL);
assert(sandbox_context != NULL);
/* Assumption: Base Context is only ever used by arch_context_switch */
assert(sandbox_context != &worker_thread_base_context);
assert(sandbox_context->regs[UREG_SP]);
assert(sandbox_context->regs[UREG_IP]);
/* Transitioning from Fast -> Running */
assert(sandbox_context->variant == ARCH_CONTEXT_VARIANT_FAST);
sandbox_context->variant = ARCH_CONTEXT_VARIANT_RUNNING;
//active_context->gregs[REG_RSP] = sandbox_context->regs[UREG_SP];
//active_context->gregs[REG_RIP] = sandbox_context->regs[UREG_IP];
active_context->sp = sandbox_context->regs[UREG_SP];
active_context->pc = sandbox_context->regs[UREG_IP];
}
#else
#warning "Neither AARCH64 nor aarch64 was defined, but aarch64/context.h was included!"
#endif

@ -3,9 +3,9 @@
#include <setjmp.h>
#include <ucontext.h>
#include "arch/arch_context_variant_t.h"
#include "arch/reg_t.h"
#include "arch/ureg_t.h"
#include "arch/arch_context_variant_t.h"
struct arch_context {
arch_context_variant_t variant;

@ -1,44 +0,0 @@
#pragma once
#include "likely.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
struct auto_buf {
FILE *handle;
char *data;
size_t size;
};
static inline int
auto_buf_init(struct auto_buf *buf)
{
FILE *res = open_memstream(&buf->data, &buf->size);
if (res == NULL) return errno;
buf->handle = res;
return 0;
}
static inline int
auto_buf_flush(struct auto_buf *buf)
{
return fflush(buf->handle);
}
static inline void
auto_buf_deinit(struct auto_buf *buf)
{
if (likely(buf->handle != NULL)) {
fclose(buf->handle);
buf->handle = NULL;
}
if (likely(buf->data != NULL)) {
free(buf->data);
buf->data = NULL;
}
buf->size = 0;
}

@ -0,0 +1,87 @@
#pragma once
#include <assert.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "debuglog.h"
#include "http.h"
#include "http_total.h"
#include "panic.h"
#include "likely.h"
static inline void
client_socket_close(int client_socket, struct sockaddr *client_address)
{
/* Should never close 0, 1, or 2 */
assert(client_socket != STDIN_FILENO);
assert(client_socket != STDOUT_FILENO);
assert(client_socket != STDERR_FILENO);
if (unlikely(close(client_socket) < 0)) {
char client_address_text[INET6_ADDRSTRLEN] = { '\0' };
if (unlikely(inet_ntop(AF_INET, &client_address, client_address_text, INET6_ADDRSTRLEN) == NULL)) {
debuglog("Failed to log client_address: %s", strerror(errno));
}
debuglog("Error closing client socket %d associated with %s - %s", client_socket, client_address_text,
strerror(errno));
}
}
typedef void (*void_cb)(void);
/**
* Writes buffer to the client socket
* @param client_socket - the client we are rejecting
* @param buffer - buffer to write to socket
* @param on_eagain - cb to execute when client socket returns EAGAIN. If NULL, error out
* @returns 0 on success, -1 on error.
*/
static inline int
client_socket_send(int client_socket, const char *buffer, size_t buffer_len, void_cb on_eagain)
{
int rc;
size_t cursor = 0;
while (cursor < buffer_len) {
ssize_t sent = write(client_socket, &buffer[cursor], buffer_len - cursor);
if (sent < 0) {
if (errno == EAGAIN) {
if (on_eagain == NULL) {
rc = -1;
goto done;
}
on_eagain();
} else {
debuglog("Error sending to client: %s", strerror(errno));
rc = -1;
goto done;
}
}
assert(sent > 0);
cursor += (size_t)sent;
};
rc = 0;
done:
return rc;
}
/**
* Rejects request due to admission control or error
* @param client_socket - the client we are rejecting
* @param buffer - buffer to write to socket
* @returns 0
*/
static inline int
client_socket_send_oneshot(int client_socket, const char *buffer, size_t buffer_len)
{
return client_socket_send(client_socket, buffer, buffer_len, NULL);
}

@ -3,7 +3,6 @@
#include <threads.h>
#include "current_wasm_module_instance.h"
#include "listener_thread.h"
#include "sandbox_types.h"
/* current sandbox that is active.. */
@ -31,25 +30,24 @@ current_sandbox_set(struct sandbox *sandbox)
/* Unpack hierarchy to avoid pointer chasing */
if (sandbox == NULL) {
sledge_abi__current_wasm_module_instance = (struct wasm_module_instance){
/* Public */
.abi =
(struct sledge_abi__wasm_module_instance){
.memory =
(struct sledge_abi__wasm_memory){
.size = 0,
.capacity = 0,
.max = 0,
.buffer = NULL,
},
.table = NULL,
.wasmg_0 = 0,
},
/* Private */
.wasi_context = NULL,
/* Public */
.abi =
(struct sledge_abi__wasm_module_instance){
.memory =
(struct sledge_abi__wasm_memory){
.size = 0,
.capacity = 0,
.max = 0,
.buffer = NULL,
},
.table = NULL,
.wasmg_0 = 0,
},
/* Private */
.wasi_context = NULL,
};
worker_thread_current_sandbox = NULL;
/* This is because the event core does not maintain core-assigned deadline */
if (!listener_thread_is_running()) runtime_worker_threads_deadline[worker_thread_idx] = UINT64_MAX;
worker_thread_current_sandbox = NULL;
runtime_worker_threads_deadline[worker_thread_idx] = UINT64_MAX;
} else {
sledge_abi__current_wasm_module_instance.wasi_context = sandbox->wasi_context;
memcpy(&sledge_abi__current_wasm_module_instance.abi.memory, &sandbox->memory->abi,
@ -57,9 +55,8 @@ current_sandbox_set(struct sandbox *sandbox)
sledge_abi__current_wasm_module_instance.abi.table = sandbox->module->indirect_table;
wasm_globals_update_if_used(&sandbox->globals, 0,
&sledge_abi__current_wasm_module_instance.abi.wasmg_0);
worker_thread_current_sandbox = sandbox;
if (!listener_thread_is_running())
runtime_worker_threads_deadline[worker_thread_idx] = sandbox->absolute_deadline;
worker_thread_current_sandbox = sandbox;
runtime_worker_threads_deadline[worker_thread_idx] = sandbox->absolute_deadline;
}
}
@ -110,7 +107,5 @@ current_sandbox_trap(enum sledge_abi__wasm_trap trapno)
assert(sandbox != NULL);
assert(sandbox->state == SANDBOX_RUNNING_USER || sandbox->state == SANDBOX_RUNNING_SYS);
siglongjmp(sandbox->ctxt.start_buf, trapno);
longjmp(sandbox->ctxt.start_buf, trapno);
}
extern noreturn void current_sandbox_fini();

@ -0,0 +1,59 @@
#pragma once
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "current_sandbox.h"
#include "http.h"
#include "http_total.h"
#include "likely.h"
#include "sandbox_types.h"
#include "scheduler.h"
#include "panic.h"
/**
* Sends Response Back to Client
* @return RC. -1 on Failure
*/
static inline int
current_sandbox_send_response()
{
struct sandbox *sandbox = current_sandbox_get();
assert(sandbox != NULL);
struct vec_u8 *response = &sandbox->response;
assert(response != NULL);
int rc;
/* Determine values to template into our HTTP response */
size_t response_body_size = response->length;
char *module_content_type = sandbox->module->response_content_type;
const char *content_type = strlen(module_content_type) > 0 ? module_content_type : "text/plain";
/* Capture Timekeeping data for end-to-end latency */
uint64_t end_time = __getcycles();
sandbox->total_time = end_time - sandbox->timestamp_of.request_arrival;
/* Send HTTP Response Header and Body */
rc = http_header_200_write(sandbox->client_socket_descriptor, content_type, response_body_size);
if (rc < 0) goto err;
rc = client_socket_send(sandbox->client_socket_descriptor, (const char *)response->buffer, response_body_size,
current_sandbox_sleep);
if (rc < 0) goto err;
http_total_increment_2xx();
rc = 0;
done:
return rc;
err:
debuglog("Error sending to client: %s", strerror(errno));
rc = -1;
goto done;
}

@ -1,9 +0,0 @@
#pragma once
enum epoll_tag
{
EPOLL_TAG_INVALID = 0,
EPOLL_TAG_TENANT_SERVER_SOCKET = 1,
EPOLL_TAG_METRICS_SERVER_SOCKET,
EPOLL_TAG_HTTP_SESSION_CLIENT_SOCKET,
};

@ -1,14 +0,0 @@
#pragma once
#include "perf_window_t.h"
struct execution_histogram {
struct perf_window perf_window;
uint8_t percentile; /* 50 - 99 */
int control_index; /* Precomputed Lookup index when perf_window is full */
uint64_t estimated_execution; /* cycles */
};
void execution_histogram_initialize(struct execution_histogram *execution_histogram, uint8_t percentile,
uint64_t expected_execution);
void execution_histogram_update(struct execution_histogram *execution_histogram, uint64_t execution_duration);

@ -1,26 +0,0 @@
#pragma once
#ifdef EXECUTION_REGRESSION
#include "http_session.h"
#include <stdint.h>
static inline uint64_t
get_regression_prediction(struct http_session *session)
{
/* Default Pre-processing - Extract payload size */
const int payload_size = session->http_request.body_length;
const double regression_params[2] = {payload_size, session->regression_param};
/* Perform Linear Regression using the factors provided by the regressor performed AoT on Matlab using training
* tenant-given dataset */
const struct regression_model model = session->route->regr_model;
const uint64_t prediction = (regression_params[0] / model.scale * model.beta1
+ regression_params[1] / model.scale * model.beta2)
+ model.bias;
return prediction;
}
#endif

@ -0,0 +1,11 @@
#pragma once
#include <stdint.h>
#include <threads.h>
extern thread_local uint64_t generic_thread_lock_duration;
extern thread_local uint64_t generic_thread_lock_longest;
extern thread_local uint64_t generic_thread_start_timestamp;
void generic_thread_dump_lock_overhead(void);
void generic_thread_initialize(void);

@ -5,7 +5,7 @@
#include "sandbox_types.h"
/* Returns pointer back if successful, null otherwise */
typedef struct sandbox *(*global_request_scheduler_add_fn_t)(struct sandbox *);
typedef struct sandbox *(*global_request_scheduler_add_fn_t)(void *);
typedef int (*global_request_scheduler_remove_fn_t)(struct sandbox **);
typedef int (*global_request_scheduler_remove_if_earlier_fn_t)(struct sandbox **, uint64_t);
typedef uint64_t (*global_request_scheduler_peek_fn_t)(void);

@ -1,12 +0,0 @@
#pragma once
#include "global_request_scheduler.h"
void global_request_scheduler_mtds_initialize();
int global_request_scheduler_mtds_remove_with_mt_class(struct sandbox **, uint64_t, enum MULTI_TENANCY_CLASS);
uint64_t global_request_scheduler_mtds_guaranteed_peek();
uint64_t global_request_scheduler_mtds_default_peek();
void global_timeout_queue_add(struct tenant *);
void global_request_scheduler_mtds_promote_lock(struct tenant_global_request_queue *);
void global_request_scheduler_mtds_demote_nolock(struct tenant_global_request_queue *);
void global_timeout_queue_process_promotions();

@ -5,100 +5,104 @@
#include "http_total.h"
#include "panic.h"
#define HTTP_MAX_HEADER_COUNT 32
#define HTTP_MAX_HEADER_LENGTH 64
#define HTTP_MAX_HEADER_VALUE_LENGTH 256
#define HTTP_MAX_FULL_URL_LENGTH 256
#define HTTP_MAX_HEADER_COUNT 16
#define HTTP_MAX_HEADER_LENGTH 32
#define HTTP_MAX_HEADER_VALUE_LENGTH 64
#define HTTP_MAX_QUERY_PARAM_COUNT 16
#define HTTP_MAX_QUERY_PARAM_LENGTH 32
#define HTTP_RESPONSE_200_TEMPLATE \
"HTTP/1.1 200 OK\r\n" \
"Server: SLEdge\r\n" \
"Connection: close\r\n" \
"Content-Type: %s\r\n" \
"Content-Length: %lu\r\n" \
"\r\n"
#define HTTP_RESPONSE_CONTENT_TYPE "Content-Type: %s\r\n"
#define HTTP_RESPONSE_CONTENT_LENGTH "Content-Length: %lu\r\n"
#define HTTP_RESPONSE_TERMINATOR "\r\n"
#define HTTP_RESPONSE_TERMINATOR_LENGTH 2
#define HTTP_RESPONSE_200_OK \
"HTTP/1.1 200 OK\r\n" \
"Server: SLEdge\r\n" \
"Connection: close\r\n"
#define HTTP_RESPONSE_200_OK_LENGTH 52
/* The sum of format specifier characters in the template above */
#define HTTP_RESPONSE_200_TEMPLATE_FORMAT_SPECIFIER_LENGTH 5
#define HTTP_RESPONSE_400_BAD_REQUEST \
"HTTP/1.1 400 Bad Request\r\n" \
"Server: SLEdge\r\n" \
"Connection: close\r\n"
#define HTTP_RESPONSE_400_BAD_REQUEST_LENGTH 61
#define HTTP_RESPONSE_404_NOT_FOUND \
"HTTP/1.1 404 Not Found\r\n" \
"Server: SLEdge\r\n" \
"Connection: close\r\n"
#define HTTP_RESPONSE_404_NOT_FOUND_LENGTH 59
"Connection: close\r\n" \
"\r\n"
#define HTTP_RESPONSE_413_PAYLOAD_TOO_LARGE \
"HTTP/1.1 413 Payload Too Large\r\n" \
"Server: SLEdge\r\n" \
"Connection: close\r\n"
#define HTTP_RESPONSE_413_PAYLOAD_TOO_LARGE_LENGTH 67
"Connection: close\r\n" \
"\r\n"
#define HTTP_RESPONSE_429_TOO_MANY_REQUESTS \
"HTTP/1.1 429 Too Many Requests\r\n" \
"Server: SLEdge\r\n" \
"Connection: close\r\n"
#define HTTP_RESPONSE_429_TOO_MANY_REQUESTS_LENGTH 67
"Connection: close\r\n" \
"\r\n"
#define HTTP_RESPONSE_500_INTERNAL_SERVER_ERROR \
"HTTP/1.1 500 Internal Server Error\r\n" \
"Server: SLEdge\r\n" \
"Connection: close\r\n"
#define HTTP_RESPONSE_500_INTERNAL_SERVER_ERROR_LENGTH 71
"Connection: close\r\n" \
"\r\n"
#define HTTP_RESPONSE_503_SERVICE_UNAVAILABLE \
"HTTP/1.1 503 Service Unavailable\r\n" \
"Server: SLEdge\r\n" \
"Connection: close\r\n"
#define HTTP_RESPONSE_503_SERVICE_UNAVAILABLE_LENGTH 69
"Connection: close\r\n" \
"\r\n"
static inline int
http_header_200_write(int fd, const char *content_type, size_t content_length)
{
return dprintf(fd, HTTP_RESPONSE_200_TEMPLATE, content_type, content_length);
}
static inline const char *
http_header_build(int status_code)
{
const char *response;
int rc;
switch (status_code) {
case 200:
return HTTP_RESPONSE_200_OK;
case 400:
return HTTP_RESPONSE_400_BAD_REQUEST;
case 404:
return HTTP_RESPONSE_404_NOT_FOUND;
response = HTTP_RESPONSE_400_BAD_REQUEST;
http_total_increment_4XX();
break;
case 413:
return HTTP_RESPONSE_413_PAYLOAD_TOO_LARGE;
response = HTTP_RESPONSE_413_PAYLOAD_TOO_LARGE;
http_total_increment_4XX();
break;
case 429:
return HTTP_RESPONSE_429_TOO_MANY_REQUESTS;
response = HTTP_RESPONSE_429_TOO_MANY_REQUESTS;
http_total_increment_4XX();
break;
case 500:
return HTTP_RESPONSE_500_INTERNAL_SERVER_ERROR;
response = HTTP_RESPONSE_500_INTERNAL_SERVER_ERROR;
http_total_increment_5XX();
break;
case 503:
return HTTP_RESPONSE_503_SERVICE_UNAVAILABLE;
response = HTTP_RESPONSE_503_SERVICE_UNAVAILABLE;
http_total_increment_5XX();
break;
default:
panic("%d is not a valid status code\n", status_code);
}
return response;
}
static inline size_t
static inline int
http_header_len(int status_code)
{
switch (status_code) {
case 400:
return HTTP_RESPONSE_400_BAD_REQUEST_LENGTH;
case 404:
return HTTP_RESPONSE_404_NOT_FOUND_LENGTH;
return strlen(HTTP_RESPONSE_400_BAD_REQUEST);
case 413:
return HTTP_RESPONSE_413_PAYLOAD_TOO_LARGE_LENGTH;
return strlen(HTTP_RESPONSE_413_PAYLOAD_TOO_LARGE);
case 429:
return HTTP_RESPONSE_429_TOO_MANY_REQUESTS_LENGTH;
return strlen(HTTP_RESPONSE_429_TOO_MANY_REQUESTS);
case 500:
return HTTP_RESPONSE_500_INTERNAL_SERVER_ERROR_LENGTH;
return strlen(HTTP_RESPONSE_500_INTERNAL_SERVER_ERROR);
case 503:
return HTTP_RESPONSE_503_SERVICE_UNAVAILABLE_LENGTH;
return strlen(HTTP_RESPONSE_503_SERVICE_UNAVAILABLE);
default:
panic("%d is not a valid status code\n", status_code);
}

@ -12,32 +12,19 @@ struct http_header {
int value_length;
};
struct http_query_param {
char value[HTTP_MAX_QUERY_PARAM_LENGTH];
int value_length;
};
struct http_request {
char full_url[HTTP_MAX_FULL_URL_LENGTH];
struct http_header headers[HTTP_MAX_HEADER_COUNT];
int header_count;
uint32_t method;
struct http_query_param query_params[HTTP_MAX_QUERY_PARAM_COUNT];
int query_params_count;
char *body;
int body_length;
int body_length_read; /* Amount read into buffer from socket */
struct http_header headers[HTTP_MAX_HEADER_COUNT];
int header_count;
char *body;
int body_length;
int body_read_length; /* How far we've read */
/* additional members for http-parser */
int length_parsed; /* Amount parsed */
bool last_was_value; /* http-parser flag used to help the http-parser callbacks differentiate between header
fields and values to know when to allocate a new header */
bool header_end; /* boolean flag set when header processing is complete */
bool message_begin; /* boolean flag set when body processing begins */
bool message_end; /* boolean flag set when body processing is complete */
/* Runtime state used by WASI */
int cursor; /* Sandbox cursor (offset from body pointer) */
};
void http_request_print(struct http_request *http_request);

@ -1,48 +0,0 @@
#pragma once
#include <stdatomic.h>
#ifdef HTTP_ROUTE_TOTAL_COUNTERS
struct http_route_total {
atomic_ulong total_requests;
atomic_ulong total_2XX;
atomic_ulong total_4XX;
atomic_ulong total_5XX;
};
#else
struct http_route_total {
};
#endif
static inline void
http_route_total_init(struct http_route_total *rm)
{
#ifdef HTTP_ROUTE_TOTAL_COUNTERS
atomic_init(&rm->total_requests, 0);
atomic_init(&rm->total_2XX, 0);
atomic_init(&rm->total_4XX, 0);
atomic_init(&rm->total_5XX, 0);
#endif
}
static inline void
http_route_total_increment_request(struct http_route_total *rm)
{
#ifdef HTTP_ROUTE_TOTAL_COUNTERS
atomic_fetch_add(&rm->total_requests, 1);
#endif
}
static inline void
http_route_total_increment(struct http_route_total *rm, int status_code)
{
#ifdef HTTP_ROUTE_TOTAL_COUNTERS
if (status_code >= 200 && status_code <= 299) {
atomic_fetch_add(&rm->total_2XX, 1);
} else if (status_code >= 400 && status_code <= 499) {
atomic_fetch_add(&rm->total_4XX, 1);
} else if (status_code >= 500 && status_code <= 599) {
atomic_fetch_add(&rm->total_5XX, 1);
}
#endif
}

@ -1,84 +0,0 @@
#pragma once
#include "http.h"
#include "module.h"
#include "route.h"
#include "route_config.h"
#include "route_latency.h"
#include "vec.h"
#include <stdlib.h>
typedef struct route route_t;
VEC(route_t)
typedef struct vec_route_t http_router_t;
static inline void
http_router_init(http_router_t *router, size_t capacity)
{
vec_route_t_init(router, capacity);
}
static inline int
http_router_add_route(http_router_t *router, struct route_config *config, struct module *module,
struct module *module_proprocess)
{
assert(router != NULL);
assert(config != NULL);
assert(module != NULL);
assert(config->route != NULL);
assert(config->http_resp_content_type != NULL);
struct route route = {.route = config->route,
.module = module,
.relative_deadline_us = config->relative_deadline_us,
.relative_deadline = (uint64_t)config->relative_deadline_us * runtime_processor_speed_MHz,
.response_content_type = config->http_resp_content_type};
route_latency_init(&route.latency);
http_route_total_init(&route.metrics);
#ifdef EXECUTION_REGRESSION
/* Execution Regression setup */
route.module_proprocess = module_proprocess;
route.regr_model.bias = config->model_bias / 1000.0;
route.regr_model.scale = config->model_scale / 1000.0;
route.regr_model.num_of_param = config->model_num_of_param;
route.regr_model.beta1 = config->model_beta1 / 1000.0;
route.regr_model.beta2 = config->model_beta2 / 1000.0;
#endif
const uint64_t expected_execution = route.relative_deadline / 2;
#ifdef ADMISSIONS_CONTROL
/* Addmissions Control setup */
route.execution_histogram.estimated_execution = expected_execution;
#endif
#ifdef EXECUTION_HISTOGRAM
/* Execution Histogram setup */
execution_histogram_initialize(&route.execution_histogram, config->admissions_percentile, expected_execution);
#endif
int rc = vec_route_t_push(router, route);
if (unlikely(rc == -1)) { return -1; }
return 0;
}
static inline struct route *
http_router_match_route(http_router_t *router, char *route)
{
for (int i = 0; i < router->length; i++) {
if (strncmp(route, router->buffer[i].route, strlen(router->buffer[i].route)) == 0) {
return &router->buffer[i];
}
}
return NULL;
}
static inline void
http_router_foreach(http_router_t *router, void (*cb)(route_t *, void *, void *), void *arg_one, void *arg_two)
{
for (int i = 0; i < router->length; i++) { cb(&router->buffer[i], arg_one, arg_two); }
}

@ -1,459 +0,0 @@
#pragma once
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "auto_buf.h"
#include "debuglog.h"
#include "epoll_tag.h"
#include "http_parser.h"
#include "http_parser_settings.h"
#include "http_request.h"
#include "http_route_total.h"
#include "http_session_perf_log.h"
#include "http_total.h"
#include "route.h"
#include "tcp_session.h"
#include "tenant.h"
enum http_session_state
{
HTTP_SESSION_UNINITIALIZED = 0,
HTTP_SESSION_INITIALIZED,
HTTP_SESSION_RECEIVING_REQUEST,
HTTP_SESSION_RECEIVE_REQUEST_BLOCKED,
HTTP_SESSION_RECEIVED_REQUEST,
HTTP_SESSION_EXECUTING,
HTTP_SESSION_EXECUTION_COMPLETE,
HTTP_SESSION_SENDING_RESPONSE_HEADER,
HTTP_SESSION_SEND_RESPONSE_HEADER_BLOCKED,
HTTP_SESSION_SENT_RESPONSE_HEADER,
HTTP_SESSION_SENDING_RESPONSE_BODY,
HTTP_SESSION_SEND_RESPONSE_BODY_BLOCKED,
HTTP_SESSION_SENT_RESPONSE_BODY
};
struct http_session {
enum epoll_tag tag;
enum http_session_state state;
struct sockaddr client_address; /* client requesting connection! */
int socket;
struct http_parser http_parser;
struct http_request http_request;
struct auto_buf request_buffer;
struct auto_buf response_header;
size_t response_header_written;
struct auto_buf response_body;
size_t response_body_written;
struct tenant *tenant; /* Backlink required when read blocks on listener core */
struct route *route; /* Backlink required to handle http metrics */
uint64_t request_arrival_timestamp;
uint64_t request_downloaded_timestamp;
uint64_t response_takeoff_timestamp;
uint64_t response_sent_timestamp;
bool did_preprocessing;
uint64_t preprocessing_duration;
double regression_param; /* Calculated in tenant preprocessing logic if provided */
};
extern void http_session_perf_log_print_entry(struct http_session *http_session);
/**
* Initalize state associated with an http parser
* Because the http_parser structure uses pointers to the request buffer, if realloc moves the request
* buffer, this should be called to clear stale state to force parsing to restart
*/
static inline void
http_session_parser_init(struct http_session *session)
{
memset(&session->http_request, 0, sizeof(struct http_request));
http_parser_init(&session->http_parser, HTTP_REQUEST);
/* Set the session as the data the http-parser has access to */
session->http_parser.data = &session->http_request;
}
/**
* @param session session that we want to init
* @returns 0 on success, -1 on error
*/
static inline int
http_session_init(struct http_session *session, int socket_descriptor, const struct sockaddr *socket_address,
struct tenant *tenant, uint64_t request_arrival_timestamp)
{
assert(session != NULL);
assert(session->state == HTTP_SESSION_UNINITIALIZED);
assert(socket_descriptor >= 0);
assert(socket_address != NULL);
session->tag = EPOLL_TAG_HTTP_SESSION_CLIENT_SOCKET;
session->tenant = tenant;
session->route = NULL;
session->socket = socket_descriptor;
session->request_arrival_timestamp = request_arrival_timestamp;
memcpy(&session->client_address, socket_address, sizeof(struct sockaddr));
http_session_parser_init(session);
int rc = auto_buf_init(&session->request_buffer);
if (rc < 0) return -1;
/* Defer initializing response_body until we've matched a route */
auto_buf_init(&session->response_header);
session->state = HTTP_SESSION_INITIALIZED;
return 0;
}
static inline int
http_session_init_response_body(struct http_session *session)
{
assert(session != NULL);
assert(session->response_body.data == NULL);
assert(session->response_body.size == 0);
assert(session->response_body_written == 0);
int rc = auto_buf_init(&session->response_body);
if (rc < 0) {
auto_buf_deinit(&session->request_buffer);
return -1;
}
return 0;
}
static inline struct http_session *
http_session_alloc(int socket_descriptor, const struct sockaddr *socket_address, struct tenant *tenant,
uint64_t request_arrival_timestamp)
{
assert(socket_descriptor >= 0);
assert(socket_address != NULL);
struct http_session *session = calloc(1, sizeof(struct http_session));
if (session == NULL) return NULL;
int rc = http_session_init(session, socket_descriptor, socket_address, tenant, request_arrival_timestamp);
if (rc != 0) {
free(session);
return NULL;
}
return session;
}
/**
* Deinitialize Linear Memory, cleaning up the backing buffer
* @param sandbox
*/
static inline void
http_session_deinit(struct http_session *session)
{
assert(session);
auto_buf_deinit(&session->request_buffer);
auto_buf_deinit(&session->response_header);
auto_buf_deinit(&session->response_body);
}
static inline void
http_session_free(struct http_session *session)
{
assert(session);
http_session_deinit(session);
free(session);
}
/**
* Set Response Header
* @param session - the HTTP session we want to set the response header of
* @param status_code
*/
static inline void
http_session_set_response_header(struct http_session *session, int status_code)
{
assert(session != NULL);
assert(status_code >= 200 && status_code <= 599);
http_total_increment_response(status_code);
/* We might not have actually matched a route */
if (likely(session->route != NULL)) { http_route_total_increment(&session->route->metrics, status_code); }
int rc = fputs(http_header_build(status_code), session->response_header.handle);
assert(rc != EOF);
if (status_code == 200) {
/* Make sure the response_body is flushed */
int rc = auto_buf_flush(&session->response_body);
if (unlikely(rc != 0)) { panic("response_body auto_buf failed to flush: %s\n", strerror(errno)); };
/* Technically fprintf can truncate, but I assume this won't happen with a memstream */
rc = fprintf(session->response_header.handle, HTTP_RESPONSE_CONTENT_TYPE,
session->route->response_content_type);
assert(rc > 0);
rc = fprintf(session->response_header.handle, HTTP_RESPONSE_CONTENT_LENGTH,
session->response_body.size);
assert(rc > 0);
}
rc = fputs(HTTP_RESPONSE_TERMINATOR, session->response_header.handle);
assert(rc != EOF);
rc = auto_buf_flush(&session->response_header);
if (unlikely(rc != 0)) { panic("response_header auto_buf failed to flush: %s\n", strerror(errno)); };
session->response_takeoff_timestamp = __getcycles();
}
static inline void
http_session_close(struct http_session *session)
{
assert(session != NULL);
return tcp_session_close(session->socket, &session->client_address);
}
/**
* Writes an HTTP header to the client
* @param client_socket - the client
* @param on_eagain - cb to execute when client socket returns EAGAIN. If NULL, error out
* @returns 0 on success, -errno on error
*/
static inline int
http_session_send_response_header(struct http_session *session, void_star_cb on_eagain)
{
assert(session != NULL);
assert(session->state == HTTP_SESSION_EXECUTION_COMPLETE
|| session->state == HTTP_SESSION_SEND_RESPONSE_HEADER_BLOCKED);
session->state = HTTP_SESSION_SENDING_RESPONSE_HEADER;
while (session->response_header.size > session->response_header_written) {
ssize_t sent =
tcp_session_send(session->socket,
(const char *)&session->response_header.data[session->response_header_written],
session->response_header.size - session->response_header_written, on_eagain,
session);
if (sent < 0) {
return (int)sent;
} else {
session->response_header_written += (size_t)sent;
}
}
session->state = HTTP_SESSION_SENT_RESPONSE_HEADER;
return 0;
}
/**
* Writes an HTTP body to the client
* @param client_socket - the client
* @param on_eagain - cb to execute when client socket returns EAGAIN. If NULL, error out
* @returns 0 on success, -errno on error
*/
static inline int
http_session_send_response_body(struct http_session *session, void_star_cb on_eagain)
{
assert(session != NULL);
assert(session->state == HTTP_SESSION_SENT_RESPONSE_HEADER
|| session->state == HTTP_SESSION_SEND_RESPONSE_BODY_BLOCKED);
session->state = HTTP_SESSION_SENDING_RESPONSE_BODY;
/* Assumption: Already flushed in order to write content-length to header */
// TODO: Test if body is empty
while (session->response_body_written < session->response_body.size) {
ssize_t sent =
tcp_session_send(session->socket,
(const char *)&session->response_body.data[session->response_body_written],
session->response_body.size - session->response_body_written, on_eagain, session);
if (sent < 0) {
return (int)sent;
} else {
session->response_body_written += (size_t)sent;
}
}
session->state = HTTP_SESSION_SENT_RESPONSE_BODY;
return 0;
}
typedef void (*http_session_cb)(struct http_session *);
static inline ssize_t
http_session_parse(struct http_session *session, ssize_t bytes_received)
{
assert(session != 0);
assert(bytes_received > 0);
const http_parser_settings *settings = http_parser_settings_get();
#ifdef LOG_HTTP_PARSER
debuglog("http_parser_execute(%p, %p, %p, %zu\n)", &session->http_parser, settings,
&session->request_buffer.buffer[session->request_buffer.length], bytes_received);
#endif
size_t bytes_parsed =
http_parser_execute(&session->http_parser, settings,
(const char *)&session->request_buffer.data[session->http_request.length_parsed],
(size_t)session->request_buffer.size - session->http_request.length_parsed);
if (session->http_parser.http_errno != HPE_OK) {
debuglog("Error: %s, Description: %s\n",
http_errno_name((enum http_errno)session->http_parser.http_errno),
http_errno_description((enum http_errno)session->http_parser.http_errno));
debuglog("Length Parsed %zu, Length Read %zu\n", bytes_parsed, (size_t)bytes_received);
debuglog("Error parsing socket %d\n", session->socket);
return -1;
}
session->http_request.length_parsed += bytes_parsed;
return (ssize_t)bytes_parsed;
}
static inline void
http_session_log_query_params(struct http_session *session)
{
#ifdef LOG_HTTP_PARSER
for (int i = 0; i < session->http_request.query_params_count; i++) {
debuglog("Argument %d, Len: %d, %.*s\n", i, session->http_request.query_params[i].value_length,
session->http_request.query_params[i].value_length,
session->http_request.query_params[i].value);
}
#endif
}
static inline void
http_session_log_malformed_request(struct http_session *session)
{
#ifndef NDEBUG
char client_address_text[INET6_ADDRSTRLEN] = {};
if (unlikely(inet_ntop(AF_INET, &session->client_address, client_address_text, INET6_ADDRSTRLEN) == NULL)) {
debuglog("Failed to log client_address: %s", strerror(errno));
}
debuglog("socket: %d. Address: %s\n", session->socket, client_address_text);
http_request_print(&session->http_request);
#endif
}
/**
* Receive and Parse the Request for the current sandbox
* @return 0 if message parsing complete, -1 on error, -EAGAIN if would block
*/
static inline int
http_session_receive_request(struct http_session *session, void_star_cb on_eagain)
{
assert(session != NULL);
assert(session->request_buffer.handle != NULL);
assert(session->state == HTTP_SESSION_INITIALIZED || session->state == HTTP_SESSION_RECEIVE_REQUEST_BLOCKED);
session->state = HTTP_SESSION_RECEIVING_REQUEST;
struct http_request *http_request = &session->http_request;
int rc = 0;
char temp[BUFSIZ];
while (!http_request->message_end) {
ssize_t bytes_received = tcp_session_recv(session->socket, temp, BUFSIZ, on_eagain, session);
if (unlikely(bytes_received == -EAGAIN))
goto err_eagain;
else if (unlikely(bytes_received < 0))
goto err;
/* If we received an EOF before we were able to parse a complete HTTP message, request is malformed */
else if (unlikely(bytes_received == 0 && !http_request->message_end))
goto err;
assert(bytes_received > 0);
const char *old_buffer = session->request_buffer.data;
const ssize_t header_length = session->request_buffer.size - http_request->body_length_read;
assert(!http_request->header_end || header_length > 0);
/* Write temp buffer to memstream */
fwrite(temp, 1, bytes_received, session->request_buffer.handle);
/* fflush memstream managed buffer */
fflush(session->request_buffer.handle);
/* Update parser structure if buffer moved */
if (old_buffer != session->request_buffer.data) {
http_request->body = header_length ? session->request_buffer.data + header_length : NULL;
}
if (http_session_parse(session, bytes_received) == -1) goto err;
}
assert(http_request->message_end == true);
session->state = HTTP_SESSION_RECEIVED_REQUEST;
http_session_log_query_params(session);
rc = 0;
done:
return rc;
err_eagain:
rc = -EAGAIN;
goto done;
err:
http_session_log_malformed_request(session);
rc = -1;
goto done;
}
/**
* Writes to the HTTP response buffer
* On success, the number of bytes written is returned. On error, -1 is returned,
*/
static inline int
http_session_write_response(struct http_session *session, const uint8_t *source, size_t n)
{
assert(session);
assert(session->response_body.handle != NULL);
assert(source);
return fwrite(source, 1, n, session->response_body.handle);
}
static inline void
http_session_send_response(struct http_session *session, void_star_cb on_eagain)
{
assert(session->state == HTTP_SESSION_EXECUTION_COMPLETE);
int rc = http_session_send_response_header(session, on_eagain);
/* session blocked and registered to epoll so continue to next handle */
if (unlikely(rc == -EAGAIN)) {
goto DONE;
} else if (unlikely(rc < 0)) {
goto CLOSE;
}
assert(session->state == HTTP_SESSION_SENT_RESPONSE_HEADER);
rc = http_session_send_response_body(session, on_eagain);
/* session blocked and registered to epoll so continue to next handle */
if (unlikely(rc == -EAGAIN)) {
goto DONE;
} else if (unlikely(rc < 0)) {
goto CLOSE;
}
assert(session->state == HTTP_SESSION_SENT_RESPONSE_BODY);
/* Terminal State Logging for Http Session */
session->response_sent_timestamp = __getcycles();
http_session_perf_log_print_entry(session);
CLOSE:
http_session_close(session);
http_session_free(session);
DONE:
return;
}

@ -1,43 +0,0 @@
#pragma once
#include "http_session.h"
#include "pretty_print.h"
#include "runtime.h"
extern FILE *http_session_perf_log;
typedef struct http_session http_session;
void http_session_perf_log_print_entry(struct http_session *http_session);
/**
* @brief Prints headers for the per-session perf logs
*/
static inline void
http_session_perf_log_print_header()
{
if (http_session_perf_log == NULL) { perror("http_session perf log"); }
fprintf(http_session_perf_log, "tenant,route,state,header_len,resp_body_len,receive_duration,sent_duration,"
"total_lifetime,preprocessing,proc_MHz\n");
}
static inline void
http_session_perf_log_init()
{
char *http_session_perf_log_path = getenv("SLEDGE_HTTP_SESSION_PERF_LOG");
if (http_session_perf_log_path != NULL) {
pretty_print_key_value("HTTP Session Performance Log", "%s\n", http_session_perf_log_path);
http_session_perf_log = fopen(http_session_perf_log_path, "w");
if (http_session_perf_log == NULL) perror("http_session_perf_log_init\n");
http_session_perf_log_print_header();
} else {
pretty_print_key_disabled("HTTP Session Performance Log");
}
}
static inline void
http_session_perf_log_cleanup()
{
if (http_session_perf_log != NULL) {
fflush(http_session_perf_log);
fclose(http_session_perf_log);
}
}

@ -9,9 +9,10 @@
* behind a compiler flag. 2XX and 4XX can be incremented by worker cores, so they are behind a flag because
* of concerns about contention
*/
#ifdef HTTP_TOTAL_COUNTERS
extern _Atomic uint32_t http_total_requests;
extern _Atomic uint32_t http_total_5XX;
#ifdef LOG_TOTAL_REQS_RESPS
extern _Atomic uint32_t http_total_2XX;
extern _Atomic uint32_t http_total_4XX;
#endif
@ -19,32 +20,38 @@ extern _Atomic uint32_t http_total_4XX;
static inline void
http_total_init()
{
#ifdef HTTP_TOTAL_COUNTERS
atomic_init(&http_total_requests, 0);
atomic_init(&http_total_5XX, 0);
#ifdef LOG_TOTAL_REQS_RESPS
atomic_init(&http_total_2XX, 0);
atomic_init(&http_total_4XX, 0);
atomic_init(&http_total_5XX, 0);
#endif
}
static inline void
http_total_increment_request()
{
#ifdef HTTP_TOTAL_COUNTERS
atomic_fetch_add(&http_total_requests, 1);
}
static inline void
http_total_increment_2xx()
{
#ifdef LOG_TOTAL_REQS_RESPS
atomic_fetch_add(&http_total_2XX, 1);
#endif
}
static inline void
http_total_increment_response(int status_code)
http_total_increment_4XX()
{
#ifdef HTTP_TOTAL_COUNTERS
if (status_code >= 200 && status_code <= 299) {
atomic_fetch_add(&http_total_2XX, 1);
} else if (status_code >= 400 && status_code <= 499) {
atomic_fetch_add(&http_total_4XX, 1);
} else if (status_code >= 500 && status_code <= 599) {
atomic_fetch_add(&http_total_5XX, 1);
}
#ifdef LOG_TOTAL_REQS_RESPS
atomic_fetch_add(&http_total_4XX, 1);
#endif
}
static inline void
http_total_increment_5XX()
{
atomic_fetch_add(&http_total_5XX, 1);
}

@ -1,173 +0,0 @@
#pragma once
#include <errno.h>
#include <inttypes.h>
#include <jsmn.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static inline char *
jsmn_type(jsmntype_t type)
{
switch (type) {
case JSMN_UNDEFINED:
return "Undefined";
case JSMN_OBJECT:
return "Object";
case JSMN_ARRAY:
return "Array";
case JSMN_STRING:
return "String";
case JSMN_PRIMITIVE:
return "Primitive";
default:
return "Invalid";
}
}
static inline bool
has_valid_size(jsmntok_t tok, char *key, int expected_size)
{
if (tok.size != expected_size) {
fprintf(stderr, "%s has size %d, expected %d\n", key, tok.size, expected_size);
return false;
}
return true;
}
static inline bool
has_valid_type(jsmntok_t tok, char *key, jsmntype_t expected_type, const char *json_buf)
{
if (tok.type != expected_type) {
fprintf(stderr, "The value of the key %s should be a %s, was a %s\n", key, jsmn_type(expected_type),
jsmn_type(tok.type));
if (json_buf != NULL) fprintf(stderr, "Token: %.*s\n", tok.end - tok.start, &json_buf[tok.start]);
return false;
}
return true;
}
static inline bool
is_nonempty_string(jsmntok_t tok, char *key)
{
if (!has_valid_type(tok, key, JSMN_STRING, NULL)) return false;
if (tok.end - tok.start < 1) {
fprintf(stderr, "The value of the key %s was an empty string\n", key);
return false;
}
return true;
}
static inline bool
is_nonempty_object(jsmntok_t tok, char *key)
{
if (!has_valid_type(tok, key, JSMN_OBJECT, NULL)) return false;
if (tok.size == 0) {
fprintf(stderr, "The value of the key %s was an empty object\n", key);
return false;
}
return true;
}
static inline bool
is_nonempty_array(jsmntok_t tok, char *key)
{
if (!has_valid_type(tok, key, JSMN_ARRAY, NULL)) return false;
if (tok.size == 0) {
fprintf(stderr, "The value of the key %s was an empty array\n", key);
return false;
}
return true;
}
static inline bool
is_valid_key(jsmntok_t tok)
{
if (tok.type != JSMN_STRING) {
fprintf(stderr, "Expected token to be a key with a type of string, was a %s\n", jsmn_type(tok.type));
return false;
}
if (tok.end - tok.start < 1) {
fprintf(stderr, "Key was an empty string\n");
return false;
}
return true;
}
static inline int
parse_uint8_t(jsmntok_t tok, const char *json_buf, const char *key, uint8_t *ret)
{
char *end = NULL;
intmax_t temp = strtoimax(&json_buf[tok.start], &end, 10);
if (end != &json_buf[tok.end] || temp < 0 || temp > UINT8_MAX) {
fprintf(stderr, "Unable to parse uint8_t for key %s\n", key);
return -1;
}
*ret = (uint8_t)temp;
return 0;
}
static inline int
parse_uint16_t(jsmntok_t tok, const char *json_buf, const char *key, uint16_t *ret)
{
char *end = NULL;
intmax_t temp = strtoimax(&json_buf[tok.start], &end, 10);
if (end != &json_buf[tok.end] || temp < 0 || temp > UINT16_MAX) {
fprintf(stderr, "Unable to parse uint16_t for key %s\n", key);
return -1;
}
*ret = (uint16_t)temp;
return 0;
}
static inline int
parse_uint32_t(jsmntok_t tok, const char *json_buf, const char *key, uint32_t *ret)
{
char *end = NULL;
intmax_t temp = strtoimax(&json_buf[tok.start], &end, 10);
if (end != &json_buf[tok.end] || temp < 0 || temp > UINT32_MAX) {
fprintf(stderr, "Unable to parse uint32_t for key %s\n", key);
return -1;
}
*ret = (uint32_t)temp;
return 0;
}
static inline int
parse_uint64_t(jsmntok_t tok, const char *json_buf, const char *key, uint64_t *ret)
{
if (json_buf[tok.start] == '-') {
fprintf(stderr, "Unable to parse uint64_t for key %s, had negative number\n", key);
}
errno = 0;
char *end = NULL;
uintmax_t temp = strtoumax(&json_buf[tok.start], &end, 10);
if (end != &json_buf[tok.end] || (temp == UINT64_MAX && errno == ERANGE)) {
fprintf(stderr, "Unable to parse uint64_t for key %s\n", key);
return -1;
}
*ret = temp;
return 0;
}

@ -1,61 +0,0 @@
#pragma once
#include <assert.h>
#include <jsmn.h>
#include <stdio.h>
#include <stdlib.h>
#include "tenant_config_parse.h"
#define JSON_TOKENS_CAPACITY 16384
/**
* Parses a JSON file into an array of tenant configs
* @param file_name The path of the JSON file
* @return tenant_config_vec_len on success. -1 on Error
*/
static inline int
parse_json(const char *json_buf, ssize_t json_buf_size, struct tenant_config **tenant_config_vec)
{
assert(json_buf != NULL);
assert(json_buf_size > 0);
assert(tenant_config_vec != NULL);
jsmntok_t tokens[JSON_TOKENS_CAPACITY];
int tenant_config_vec_len = 0;
int i = 0;
/* Initialize the Jasmine Parser and an array to hold the tokens */
jsmn_parser parser;
jsmn_init(&parser);
/* Use Jasmine to parse the JSON */
int total_tokens = jsmn_parse(&parser, json_buf, json_buf_size, tokens, JSON_TOKENS_CAPACITY);
if (total_tokens < 0) {
if (total_tokens == JSMN_ERROR_INVAL) {
fprintf(stderr, "Error parsing %s: bad token, JSON string is corrupted\n", json_buf);
} else if (total_tokens == JSMN_ERROR_PART) {
fprintf(stderr, "Error parsing %s: JSON string is too short, expecting more JSON data\n",
json_buf);
} else if (total_tokens == JSMN_ERROR_NOMEM) {
/*
* According to the README at https://github.com/zserge/jsmn, this is a potentially recoverable
* error. More tokens can be allocated and jsmn_parse can be re-invoked.
*/
fprintf(stderr, "Error parsing %s: Not enough tokens, JSON string is too large\n", json_buf);
}
goto err;
}
i = tenant_config_vec_parse(tenant_config_vec, &tenant_config_vec_len, json_buf, tokens, i, total_tokens);
if (i != total_tokens - 1) goto json_parse_err;
done:
return tenant_config_vec_len;
json_parse_err:
free(*tenant_config_vec);
err:
fprintf(stderr, "JSON:\n%s\n", json_buf);
tenant_config_vec_len = -1;
goto done;
}

@ -3,7 +3,7 @@
#include <stdbool.h>
#include <stdnoreturn.h>
#include "http_session.h"
#include "generic_thread.h"
#include "module.h"
#define LISTENER_THREAD_CORE_ID 1
@ -12,7 +12,7 @@ extern pthread_t listener_thread_id;
void listener_thread_initialize(void);
noreturn void *listener_thread_main(void *dummy);
void listener_thread_register_http_session(struct http_session *http);
int listener_thread_register_module(struct module *mod);
/**
* Used to determine if running in the context of a listener thread

@ -1,7 +0,0 @@
#pragma once
#include "sandbox_types.h"
void local_cleanup_queue_add(struct sandbox *sandbox);
void local_cleanup_queue_free();
void local_cleanup_queue_initialize();

@ -0,0 +1,7 @@
#pragma once
#include "sandbox_types.h"
void local_completion_queue_add(struct sandbox *sandbox);
void local_completion_queue_free();
void local_completion_queue_initialize();

@ -1,9 +0,0 @@
#pragma once
#include "tenant.h"
void local_runqueue_mtds_initialize();
void local_runqueue_mtds_promote(struct perworker_tenant_sandbox_queue *);
void local_runqueue_mtds_demote(struct perworker_tenant_sandbox_queue *);
void local_timeout_queue_add(struct tenant *);
void local_timeout_queue_process_promotions();

@ -1,81 +1,67 @@
#pragma once
#include <assert.h>
#include <spinlock/mcs.h>
#include <stdint.h>
#include "arch/getcycles.h"
#include "runtime.h"
/* A linked list of nodes */
struct lock_wrapper {
uint64_t longest_held;
uint64_t total_held;
ck_spinlock_mcs_t lock;
};
/* A node on the linked list */
struct lock_node {
struct ck_spinlock_mcs node;
uint64_t time_locked;
};
typedef struct lock_wrapper lock_t;
typedef struct lock_node lock_node_t;
typedef ck_spinlock_mcs_t lock_t;
/**
* Initializes a lock
* Initializes a lock of type lock_t
* @param lock - the address of the lock
*/
static inline void
lock_init(lock_t *self)
{
self->total_held = 0;
self->longest_held = 0;
ck_spinlock_mcs_init(&self->lock);
}
#define LOCK_INIT(lock) ck_spinlock_mcs_init((lock))
/**
* Checks if a lock is locked
* @param lock - the address of the lock
* @returns bool if lock is locked
*/
static inline bool
lock_is_locked(lock_t *self)
{
return ck_spinlock_mcs_locked(&self->lock);
}
#define LOCK_IS_LOCKED(lock) ck_spinlock_mcs_locked((lock))
/**
* Locks a lock, keeping track of overhead
* @param lock - the address of the lock
* @param node - node to add to lock
* @param unique_variable_name - a unique prefix to hygienically namespace an associated lock/unlock pair
*/
static inline void
lock_lock(lock_t *self, lock_node_t *node)
{
assert(node->time_locked == 0);
node->time_locked = __getcycles();
ck_spinlock_mcs_lock(&self->lock, &node->node);
}
#define LOCK_LOCK_WITH_BOOKKEEPING(lock, unique_variable_name) \
struct ck_spinlock_mcs _hygiene_##unique_variable_name##_node; \
uint64_t _hygiene_##unique_variable_name##_pre = __getcycles(); \
ck_spinlock_mcs_lock((lock), &(_hygiene_##unique_variable_name##_node)); \
uint64_t _hygiene_##unique_variable_name##_duration = (__getcycles() - _hygiene_##unique_variable_name##_pre); \
if (_hygiene_##unique_variable_name##_duration > generic_thread_lock_longest) { \
generic_thread_lock_longest = _hygiene_##unique_variable_name##_duration; \
} \
generic_thread_lock_duration += _hygiene_##unique_variable_name##_duration;
/**
* Unlocks a lock
* @param lock - the address of the lock
* @param node - node used when calling lock_lock
* @param unique_variable_name - a unique prefix to hygienically namespace an associated lock/unlock pair
*/
static inline void
lock_unlock(lock_t *self, lock_node_t *node)
{
assert(node->time_locked > 0);
#define LOCK_UNLOCK_WITH_BOOKKEEPING(lock, unique_variable_name) \
ck_spinlock_mcs_unlock(lock, &(_hygiene_##unique_variable_name##_node));
ck_spinlock_mcs_unlock(&self->lock, &node->node);
uint64_t now = __getcycles();
assert(node->time_locked < now);
uint64_t duration = now - node->time_locked;
node->time_locked = 0;
if (unlikely(duration > self->longest_held)) { self->longest_held = duration; }
self->total_held += duration;
}
/**
* Locks a lock, keeping track of overhead
* Assumes the availability of DEFAULT as a hygienic prefix for DEFAULT_node and DEFAULT_pre
*
* As such, this API can only be used once in a lexical scope.
*
* Use LOCK_LOCK_WITH_BOOKKEEPING and LOCK_UNLOCK_WITH_BOOKKEEPING if multiple locks are required
* @param lock - the address of the lock
*/
#define LOCK_LOCK(lock) LOCK_LOCK_WITH_BOOKKEEPING(lock, DEFAULT)
/**
* Unlocks a lock
* Uses lock node NODE_DEFAULT and timestamp PRE_DEFAULT, so this assumes use of LOCK_LOCK
* This API can only be used once in a lexical scope. If this isn't true, use LOCK_LOCK_WITH_BOOKKEEPING and
* LOCK_UNLOCK_WITH_BOOKKEEPING
* @param lock - the address of the lock
*/
#define LOCK_UNLOCK(lock) LOCK_UNLOCK_WITH_BOOKKEEPING(lock, DEFAULT)

@ -1,195 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#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_HASH jenkins_hash
struct map_node {
struct map_node *next;
uint8_t *key;
uint8_t *value;
uint32_t key_len;
uint32_t value_len;
uint32_t hash;
};
struct map_bucket {
lock_t lock;
struct map_node *head;
};
struct map {
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].head = NULL;
lock_init(&map->buckets[i].lock);
}
};
/* 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);
struct map_bucket *bucket = &map->buckets[hash % MAP_BUCKET_COUNT];
lock_node_t node = {};
lock_lock(&bucket->lock, &node);
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;
goto DONE;
}
}
if (value == NULL) *ret_value_len = 0;
DONE:
lock_unlock(&bucket->lock, &node);
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);
struct map_bucket *bucket = &map->buckets[hash % MAP_BUCKET_COUNT];
lock_node_t node;
lock_lock(&bucket->lock, &node);
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 *)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);
bucket->head = new_node;
did_set = true;
DONE:
lock_unlock(&bucket->lock, &node);
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);
struct map_bucket *bucket = &map->buckets[hash % MAP_BUCKET_COUNT];
lock_node_t node;
lock_lock(&bucket->lock, &node);
struct map_node *prev = bucket->head;
if (prev != NULL && prev->hash == hash) {
bucket->head = 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(&bucket->lock, &node);
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);
struct map_bucket *bucket = &map->buckets[hash % MAP_BUCKET_COUNT];
lock_node_t node;
lock_lock(&bucket->lock, &node);
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;
}
}
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};
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);
bucket->head = new_node;
DONE:
lock_unlock(&bucket->lock, &node);
}

@ -1,17 +0,0 @@
#pragma once
#include "epoll_tag.h"
#include "tcp_server.h"
struct metrics_server {
enum epoll_tag tag;
struct tcp_server tcp;
pthread_attr_t thread_settings;
};
extern struct metrics_server metrics_server;
void metrics_server_init();
void metrics_server_thread_spawn(int client_socket);
int metrics_server_close();

@ -1,31 +1,72 @@
#pragma once
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include "admissions_control.h"
#include "admissions_info.h"
#include "current_wasm_module_instance.h"
#include "http.h"
#include "panic.h"
#include "pool.h"
#include "sledge_abi_symbols.h"
#include "types.h"
#include "wasm_memory.h"
#include "sledge_abi_symbols.h"
#include "wasm_stack.h"
#include "wasm_memory.h"
#include "wasm_table.h"
#define MODULE_DEFAULT_REQUEST_RESPONSE_SIZE (PAGE_SIZE)
#define MODULE_MAX_NAME_LENGTH 32
#define MODULE_MAX_PATH_LENGTH 256
extern thread_local int worker_thread_idx;
INIT_POOL(wasm_memory, wasm_memory_free)
INIT_POOL(wasm_stack, wasm_stack_free)
struct module_pool {
/*
* Defines the listen backlog, the queue length for completely established socketeds waiting to be accepted
* If this value is greater than the value in /proc/sys/net/core/somaxconn (typically 128), then it is silently
* truncated to this value. See man listen(2) for info
*
* When configuring the number of sockets to handle, the queue length of incomplete sockets defined in
* /proc/sys/net/ipv4/tcp_max_syn_backlog should also be considered. Optionally, enabling syncookies removes this
* maximum logical length. See tcp(7) for more info.
*/
#define MODULE_MAX_PENDING_CLIENT_REQUESTS 128
#if MODULE_MAX_PENDING_CLIENT_REQUESTS > 128
#warning \
"MODULE_MAX_PENDING_CLIENT_REQUESTS likely exceeds the value in /proc/sys/net/core/somaxconn and thus may be silently truncated";
#endif
/* TODO: Dynamically size based on number of threads */
#define MAX_WORKER_THREADS 64
struct module_pools {
struct wasm_memory_pool memory;
struct wasm_stack_pool stack;
} CACHE_PAD_ALIGNED;
} __attribute__((aligned(CACHE_PAD)));
enum module_type
{
APP_MODULE,
PREPROCESS_MODULE
};
struct module {
char *path;
uint32_t stack_size; /* a specification? */
enum module_type type;
/* Metadata from JSON Config */
char name[MODULE_MAX_NAME_LENGTH];
char path[MODULE_MAX_PATH_LENGTH];
uint32_t stack_size; /* a specification? */
uint32_t relative_deadline_us;
int port;
struct admissions_info admissions_info;
uint64_t relative_deadline; /* cycles */
/* HTTP State */
size_t max_request_size;
size_t max_response_size;
char response_content_type[HTTP_MAX_HEADER_VALUE_LENGTH];
struct sockaddr_in socket_address;
int socket_descriptor;
/* Handle and ABI Symbols for *.so file */
struct sledge_abi_symbols abi;
@ -33,16 +74,8 @@ struct module {
_Atomic uint32_t reference_count; /* ref count how many instances exist here. */
struct sledge_abi__wasm_table *indirect_table;
struct module_pool *pools;
} CACHE_PAD_ALIGNED;
/********************************
* Public Methods from module.c *
*******************************/
void module_free(struct module *module);
struct module *module_alloc(char *path, enum module_type type);
struct module_pools pools[MAX_WORKER_THREADS];
};
/*************************
* Public Static Inlines *
@ -111,24 +144,12 @@ module_alloc_table(struct module *module)
static inline void
module_initialize_pools(struct module *module)
{
/* Create only a single pool for the preprocessing module, since it is executed only by the event core. */
const int n = module->type == APP_MODULE ? runtime_worker_threads_count : 1;
for (int i = 0; i < n; i++) {
for (int i = 0; i < MAX_WORKER_THREADS; i++) {
wasm_memory_pool_init(&module->pools[i].memory, false);
wasm_stack_pool_init(&module->pools[i].stack, false);
}
}
static inline void
module_deinitialize_pools(struct module *module)
{
const int n = module->type == APP_MODULE ? runtime_worker_threads_count : 1;
for (int i = 0; i < n; i++) {
wasm_memory_pool_deinit(&module->pools[i].memory);
wasm_stack_pool_deinit(&module->pools[i].stack);
}
}
/**
* Invoke a module's initialize_memory
* @param module - the module whose memory we are initializing
@ -142,6 +163,8 @@ module_initialize_memory(struct module *module)
/**
* Invoke a module's entry function, forwarding on argc and argv
* @param module
* @param argc standard UNIX count of arguments
* @param argv standard UNIX vector of arguments
* @return return code of module's main function
*/
static inline int32_t
@ -213,3 +236,12 @@ module_free_linear_memory(struct module *module, struct wasm_memory *memory)
wasm_memory_reinit(memory, module->abi.starting_pages * WASM_PAGE_SIZE);
wasm_memory_pool_add_nolock(&module->pools[worker_thread_idx].memory, memory);
}
/********************************
* Public Methods from module.c *
*******************************/
void module_free(struct module *module);
struct module *module_alloc(char *mod_name, char *mod_path, uint32_t stack_sz, uint32_t relative_deadline_us, int port,
int req_sz, int resp_sz, int admissions_percentile, uint32_t expected_execution_us);
int module_alloc_from_json(char *filename);

@ -4,11 +4,6 @@
#define MODULE_DATABASE_CAPACITY 128
struct module_database {
struct module *modules[MODULE_DATABASE_CAPACITY];
size_t count;
};
struct module *module_database_find_by_path(struct module_database *db, char *path);
void module_database_init(struct module_database *db);
int module_database_add(struct module_database *db, struct module *module);
int module_database_add(struct module *module);
struct module *module_database_find_by_name(char *name);
struct module *module_database_find_by_socket_descriptor(int socket_descriptor);

@ -19,10 +19,10 @@ perf_window_initialize(struct perf_window *perf_window)
{
assert(perf_window != NULL);
lock_init(&perf_window->lock);
LOCK_INIT(&perf_window->lock);
perf_window->count = 0;
memset(perf_window->by_duration, 0, sizeof(struct execution_node) * PERF_WINDOW_CAPACITY);
memset(perf_window->by_termination, 0, sizeof(uint16_t) * PERF_WINDOW_CAPACITY);
memset(perf_window->by_duration, 0, sizeof(struct execution_node) * PERF_WINDOW_BUFFER_SIZE);
memset(perf_window->by_termination, 0, sizeof(uint16_t) * PERF_WINDOW_BUFFER_SIZE);
}
@ -36,10 +36,10 @@ perf_window_initialize(struct perf_window *perf_window)
static inline void
perf_window_swap(struct perf_window *perf_window, uint16_t first_by_duration_idx, uint16_t second_by_duration_idx)
{
assert(lock_is_locked(&perf_window->lock));
assert(LOCK_IS_LOCKED(&perf_window->lock));
assert(perf_window != NULL);
assert(first_by_duration_idx < PERF_WINDOW_CAPACITY);
assert(second_by_duration_idx < PERF_WINDOW_CAPACITY);
assert(first_by_duration_idx >= 0 && first_by_duration_idx < PERF_WINDOW_BUFFER_SIZE);
assert(second_by_duration_idx >= 0 && second_by_duration_idx < PERF_WINDOW_BUFFER_SIZE);
uint16_t first_by_termination_idx = perf_window->by_duration[first_by_duration_idx].by_termination_idx;
uint16_t second_by_termination_idx = perf_window->by_duration[second_by_duration_idx].by_termination_idx;
@ -51,11 +51,11 @@ perf_window_swap(struct perf_window *perf_window, uint16_t first_by_duration_idx
uint64_t first_execution_time = perf_window->by_duration[first_by_duration_idx].execution_time;
uint64_t second_execution_time = perf_window->by_duration[second_by_duration_idx].execution_time;
/* Swap indices */
/* Swap Indices in Buffer*/
perf_window->by_termination[first_by_termination_idx] = second_by_duration_idx;
perf_window->by_termination[second_by_termination_idx] = first_by_duration_idx;
/* Swap nodes */
/* Swap by_termination_idx */
struct execution_node tmp_node = perf_window->by_duration[first_by_duration_idx];
perf_window->by_duration[first_by_duration_idx] = perf_window->by_duration[second_by_duration_idx];
perf_window->by_duration[second_by_duration_idx] = tmp_node;
@ -67,73 +67,66 @@ perf_window_swap(struct perf_window *perf_window, uint16_t first_by_duration_idx
== second_execution_time);
}
static inline void
perf_window_fill(struct perf_window *perf_window, uint64_t newest_execution_time)
{
for (uint16_t i = 0; i < PERF_WINDOW_CAPACITY; i++) {
perf_window->by_termination[i] = i;
perf_window->by_duration[i] = (struct execution_node){.execution_time = newest_execution_time,
.by_termination_idx = i};
}
perf_window->count = PERF_WINDOW_CAPACITY;
}
/**
* Adds newest_execution_time to the perf window
* Adds a new value to the perf window
* Not intended to be called directly!
* @param perf_window
* @param newest_execution_time
* @param value
*/
static inline void
perf_window_add(struct perf_window *perf_window, uint64_t newest_execution_time)
perf_window_add(struct perf_window *perf_window, uint64_t value)
{
assert(perf_window != NULL);
/* Assumption: A successful invocation should run for a non-zero amount of time */
assert(newest_execution_time > 0);
uint16_t idx_to_replace;
uint64_t previous_execution_time;
uint16_t idx_of_oldest;
bool check_up;
if (unlikely(!lock_is_locked(&perf_window->lock))) panic("lock not held when calling perf_window_add\n");
if (unlikely(!LOCK_IS_LOCKED(&perf_window->lock))) panic("lock not held when calling perf_window_add\n");
/* A successful invocation should run for a non-zero amount of time */
assert(value > 0);
/* If perf window is empty, fill all elements with newest_execution_time */
/* If count is 0, then fill entire array with initial execution times */
if (perf_window->count == 0) {
perf_window_fill(perf_window, newest_execution_time);
for (int i = 0; i < PERF_WINDOW_BUFFER_SIZE; i++) {
perf_window->by_termination[i] = i;
perf_window->by_duration[i] = (struct execution_node){ .execution_time = value,
.by_termination_idx = i };
}
perf_window->count = PERF_WINDOW_BUFFER_SIZE;
goto done;
}
/* If full, replace the oldest execution_time. Save the old execution time to know which direction to swap */
idx_to_replace = perf_window->by_termination[perf_window->count % PERF_WINDOW_CAPACITY];
previous_execution_time = perf_window->by_duration[idx_to_replace].execution_time;
perf_window->by_duration[idx_to_replace].execution_time = newest_execution_time;
/* Otherwise, replace the oldest value, and then sort */
idx_of_oldest = perf_window->by_termination[perf_window->count % PERF_WINDOW_BUFFER_SIZE];
check_up = value > perf_window->by_duration[idx_of_oldest].execution_time;
perf_window->by_duration[idx_of_oldest].execution_time = value;
/* At this point, the by_duration array is partially sorted. The node we overwrote needs to be shifted left or
* right. We can determine which direction to shift by comparing with the previous execution time. */
if (newest_execution_time > previous_execution_time) {
for (uint16_t i = idx_to_replace;
i + 1 < PERF_WINDOW_CAPACITY
if (check_up) {
for (uint16_t i = idx_of_oldest;
i + 1 < PERF_WINDOW_BUFFER_SIZE
&& perf_window->by_duration[i + 1].execution_time < perf_window->by_duration[i].execution_time;
i++) {
perf_window_swap(perf_window, i, i + 1);
}
} else {
for (uint16_t i = idx_to_replace;
i >= 1
for (int i = idx_of_oldest;
i - 1 >= 0
&& perf_window->by_duration[i - 1].execution_time > perf_window->by_duration[i].execution_time;
i--) {
perf_window_swap(perf_window, i, i - 1);
}
}
/* The idx that we replaces should still point to the same newest_execution_time */
assert(perf_window->by_duration[perf_window->by_termination[perf_window->count % PERF_WINDOW_CAPACITY]]
/* The idx that we replaces should still point to the same value */
assert(perf_window->by_duration[perf_window->by_termination[perf_window->count % PERF_WINDOW_BUFFER_SIZE]]
.execution_time
== newest_execution_time);
== value);
/* The by_duration array should be ordered by execution time */
#ifndef NDEBUG
for (int i = 1; i < PERF_WINDOW_CAPACITY; i++) {
for (int i = 1; i < PERF_WINDOW_BUFFER_SIZE; i++) {
assert(perf_window->by_duration[i - 1].execution_time <= perf_window->by_duration[i].execution_time);
}
#endif
@ -152,22 +145,16 @@ done:
* @returns execution time
*/
static inline uint64_t
perf_window_get_percentile(struct perf_window *perf_window, uint8_t percentile, int precomputed_index)
perf_window_get_percentile(struct perf_window *perf_window, int percentile, int precomputed_index)
{
assert(perf_window != NULL);
assert(percentile >= 50 && percentile <= 99);
int size = perf_window->count;
assert(size > 0);
if (unlikely(perf_window->count == 0)) return 0;
uint64_t perf_window_size = PERF_WINDOW_CAPACITY;
if (unlikely(perf_window->count < PERF_WINDOW_CAPACITY)) perf_window_size = perf_window->count;
int idx = precomputed_index;
if (precomputed_index == 0 || perf_window->count < PERF_WINDOW_CAPACITY) {
idx = perf_window_size * percentile / 100;
}
if (likely(size >= PERF_WINDOW_BUFFER_SIZE)) return perf_window->by_duration[precomputed_index].execution_time;
return perf_window->by_duration[idx].execution_time;
return perf_window->by_duration[size * percentile / 100].execution_time;
}
/**

@ -1,19 +1,15 @@
#pragma once
#include <assert.h>
#include <stdint.h>
#include "lock.h"
enum
{
PERF_WINDOW_CAPACITY = 256
};
static_assert(PERF_WINDOW_CAPACITY && !(PERF_WINDOW_CAPACITY & (PERF_WINDOW_CAPACITY - 1)),
"PERF_WINDOW_CAPACITY must be power of 2!");
/* Should be Power of 2! */
#define PERF_WINDOW_BUFFER_SIZE 16
static_assert(PERF_WINDOW_CAPACITY <= UINT16_MAX, "PERF_WINDOW_CAPACITY must be indexable by a 16-bit unsigned int");
#if ((PERF_WINDOW_BUFFER_SIZE == 0) || (PERF_WINDOW_BUFFER_SIZE & (PERF_WINDOW_BUFFER_SIZE - 1)) != 0)
#error "PERF_WINDOW_BUFFER_SIZE must be power of 2!"
#endif
/*
* The by_duration array sorts the last N executions by execution time
@ -28,8 +24,8 @@ struct execution_node {
};
struct perf_window {
struct execution_node by_duration[PERF_WINDOW_CAPACITY];
uint16_t by_termination[PERF_WINDOW_CAPACITY];
struct execution_node by_duration[PERF_WINDOW_BUFFER_SIZE];
uint16_t by_termination[PERF_WINDOW_BUFFER_SIZE];
uint64_t count;
lock_t lock;
};

@ -4,6 +4,7 @@
#include <stdbool.h>
#include <stdlib.h>
#include "generic_thread.h"
#include "lock.h"
#include "ps_list.h"
@ -25,7 +26,7 @@
{ \
ps_list_head_init(&self->list); \
self->use_lock = use_lock; \
if (use_lock) lock_init(&self->lock); \
if (use_lock) LOCK_INIT(&self->lock); \
} \
\
static inline void STRUCT_NAME##_pool_deinit(struct STRUCT_NAME##_pool *self) \
@ -43,7 +44,7 @@
static inline struct STRUCT_NAME *STRUCT_NAME##_pool_remove_nolock(struct STRUCT_NAME##_pool *self) \
{ \
assert(self != NULL); \
assert(!self->use_lock || lock_is_locked(&self->lock)); \
assert(!self->use_lock || LOCK_IS_LOCKED(&self->lock)); \
\
struct STRUCT_NAME *obj = NULL; \
\
@ -65,10 +66,9 @@
bool is_empty = STRUCT_NAME##_pool_is_empty(self); \
if (is_empty) return obj; \
\
lock_node_t node = {}; \
lock_lock(&self->lock, &node); \
LOCK_LOCK(&self->lock); \
obj = STRUCT_NAME##_pool_remove_nolock(self); \
lock_unlock(&self->lock, &node); \
LOCK_UNLOCK(&self->lock); \
return obj; \
} \
\
@ -76,7 +76,7 @@
{ \
assert(self != NULL); \
assert(obj != NULL); \
assert(!self->use_lock || lock_is_locked(&self->lock)); \
assert(!self->use_lock || LOCK_IS_LOCKED(&self->lock)); \
\
ps_list_head_add_d(&self->list, obj); \
} \
@ -87,8 +87,7 @@
assert(obj != NULL); \
assert(self->use_lock); \
\
lock_node_t node = {}; \
lock_lock(&self->lock, &node); \
LOCK_LOCK(&self->lock); \
STRUCT_NAME##_pool_add_nolock(self, obj); \
lock_unlock(&self->lock, &node); \
LOCK_UNLOCK(&self->lock); \
}

@ -1,7 +1,7 @@
#pragma once
#include <stdarg.h>
#include <stdio.h>
#include <stdarg.h>
#define PRETTY_PRINT_COLOR_CODE_RED "\033[1;31m"
#define PRETTY_COLOR_CODE_GREEN "\033[0;32m"

@ -3,8 +3,8 @@
#include <errno.h>
#include "listener_thread.h"
#include "lock.h"
#include "listener_thread.h"
#include "panic.h"
#include "runtime.h"
#include "worker_thread.h"
@ -60,12 +60,12 @@ priority_queue_append(struct priority_queue *priority_queue, void *new_item)
{
assert(priority_queue != NULL);
assert(new_item != NULL);
assert(!priority_queue->use_lock || lock_is_locked(&priority_queue->lock));
assert(!priority_queue->use_lock || LOCK_IS_LOCKED(&priority_queue->lock));
int rc;
if (unlikely(priority_queue->size > priority_queue->capacity)) panic("PQ overflow");
if (unlikely(priority_queue->size == priority_queue->capacity)) goto err_enospc;
if (unlikely(priority_queue->size + 1 > priority_queue->capacity)) panic("PQ overflow");
if (unlikely(priority_queue->size + 1 == priority_queue->capacity)) goto err_enospc;
priority_queue->items[++priority_queue->size] = new_item;
rc = 0;
@ -85,7 +85,7 @@ static inline bool
priority_queue_is_empty(struct priority_queue *priority_queue)
{
assert(priority_queue != NULL);
assert(!priority_queue->use_lock || lock_is_locked(&priority_queue->lock));
assert(!priority_queue->use_lock || LOCK_IS_LOCKED(&priority_queue->lock));
return priority_queue->size == 0;
}
@ -99,7 +99,7 @@ priority_queue_percolate_up(struct priority_queue *priority_queue)
{
assert(priority_queue != NULL);
assert(priority_queue->get_priority_fn != NULL);
assert(!priority_queue->use_lock || lock_is_locked(&priority_queue->lock));
assert(!priority_queue->use_lock || LOCK_IS_LOCKED(&priority_queue->lock));
/* If there's only one element, set memoized lookup and early out */
if (priority_queue->size == 1) {
@ -135,7 +135,7 @@ priority_queue_find_smallest_child(struct priority_queue *priority_queue, const
assert(priority_queue != NULL);
assert(parent_index >= 1 && parent_index <= priority_queue->size);
assert(priority_queue->get_priority_fn != NULL);
assert(!priority_queue->use_lock || lock_is_locked(&priority_queue->lock));
assert(!priority_queue->use_lock || LOCK_IS_LOCKED(&priority_queue->lock));
int left_child_index = 2 * parent_index;
int right_child_index = 2 * parent_index + 1;
@ -167,7 +167,7 @@ priority_queue_percolate_down(struct priority_queue *priority_queue, int parent_
{
assert(priority_queue != NULL);
assert(priority_queue->get_priority_fn != NULL);
assert(!priority_queue->use_lock || lock_is_locked(&priority_queue->lock));
assert(!priority_queue->use_lock || LOCK_IS_LOCKED(&priority_queue->lock));
assert(!listener_thread_is_running());
bool update_highest_value = parent_index == 1;
@ -217,7 +217,7 @@ priority_queue_dequeue_if_earlier_nolock(struct priority_queue *priority_queue,
assert(dequeued_element != NULL);
assert(priority_queue->get_priority_fn != NULL);
assert(!listener_thread_is_running());
assert(!priority_queue->use_lock || lock_is_locked(&priority_queue->lock));
assert(!priority_queue->use_lock || LOCK_IS_LOCKED(&priority_queue->lock));
int return_code;
@ -251,10 +251,9 @@ priority_queue_dequeue_if_earlier(struct priority_queue *priority_queue, void **
{
int return_code;
lock_node_t node = {};
lock_lock(&priority_queue->lock, &node);
LOCK_LOCK(&priority_queue->lock);
return_code = priority_queue_dequeue_if_earlier_nolock(priority_queue, dequeued_element, target_deadline);
lock_unlock(&priority_queue->lock, &node);
LOCK_UNLOCK(&priority_queue->lock);
return return_code;
}
@ -272,47 +271,24 @@ priority_queue_initialize(size_t capacity, bool use_lock, priority_queue_get_pri
assert(get_priority_fn != NULL);
/* Add one to capacity because this data structure ignores the element at 0 */
struct priority_queue *priority_queue = (struct priority_queue *)calloc(1, sizeof(struct priority_queue)
+ sizeof(void *) * (capacity + 1));
size_t one_based_capacity = capacity + 1;
struct priority_queue *priority_queue = (struct priority_queue *)
calloc(1, sizeof(struct priority_queue) + sizeof(void *) * one_based_capacity);
/* We're assuming a min-heap implementation, so set to larget possible value */
priority_queue_update_highest_priority(priority_queue, ULONG_MAX);
priority_queue->size = 0;
priority_queue->capacity = capacity;
priority_queue->capacity = one_based_capacity; // Add one because we skip element 0
priority_queue->get_priority_fn = get_priority_fn;
priority_queue->use_lock = use_lock;
if (use_lock) lock_init(&priority_queue->lock);
if (use_lock) LOCK_INIT(&priority_queue->lock);
return priority_queue;
}
/**
* Double capacity of priority queue
* Note: currently there is no equivalent call for PQs that are not thread-local and need to be locked because it is
* unclear if the fact that the lock is a member in the struct that might be moved by realloc breaks the guarantees of
* the lock.
* @param priority_queue to resize
* @returns pointer to PR or NULL if realloc fails. This may have been moved by realloc!
*/
static inline struct priority_queue *
priority_queue_grow_nolock(struct priority_queue *priority_queue)
{
assert(priority_queue != NULL);
if (unlikely(priority_queue->capacity == 0)) {
priority_queue->capacity++;
debuglog("Growing to 1\n");
} else {
priority_queue->capacity *= 2;
debuglog("Growing to %zu\n", priority_queue->capacity);
}
/* capacity is padded by 1 because idx 0 is unused */
return (struct priority_queue *)realloc(priority_queue, sizeof(struct priority_queue)
+ sizeof(void *) * (priority_queue->capacity + 1));
}
/**
* Free the Priority Queue Data structure
* @param priority_queue the priority_queue to initialize
@ -333,7 +309,8 @@ static inline int
priority_queue_length_nolock(struct priority_queue *priority_queue)
{
assert(priority_queue != NULL);
assert(!priority_queue->use_lock || lock_is_locked(&priority_queue->lock));
assert(!listener_thread_is_running());
assert(!priority_queue->use_lock || LOCK_IS_LOCKED(&priority_queue->lock));
return priority_queue->size;
}
@ -345,10 +322,9 @@ priority_queue_length_nolock(struct priority_queue *priority_queue)
static inline int
priority_queue_length(struct priority_queue *priority_queue)
{
lock_node_t node = {};
lock_lock(&priority_queue->lock, &node);
LOCK_LOCK(&priority_queue->lock);
int size = priority_queue_length_nolock(priority_queue);
lock_unlock(&priority_queue->lock, &node);
LOCK_UNLOCK(&priority_queue->lock);
return size;
}
@ -362,7 +338,7 @@ priority_queue_enqueue_nolock(struct priority_queue *priority_queue, void *value
{
assert(priority_queue != NULL);
assert(value != NULL);
assert(!priority_queue->use_lock || lock_is_locked(&priority_queue->lock));
assert(!priority_queue->use_lock || LOCK_IS_LOCKED(&priority_queue->lock));
int rc;
@ -388,10 +364,9 @@ priority_queue_enqueue(struct priority_queue *priority_queue, void *value)
{
int rc;
lock_node_t node = {};
lock_lock(&priority_queue->lock, &node);
LOCK_LOCK(&priority_queue->lock);
rc = priority_queue_enqueue_nolock(priority_queue, value);
lock_unlock(&priority_queue->lock, &node);
LOCK_UNLOCK(&priority_queue->lock);
return rc;
}
@ -406,7 +381,8 @@ priority_queue_delete_nolock(struct priority_queue *priority_queue, void *value)
{
assert(priority_queue != NULL);
assert(value != NULL);
assert(!priority_queue->use_lock || lock_is_locked(&priority_queue->lock));
assert(!listener_thread_is_running());
assert(!priority_queue->use_lock || LOCK_IS_LOCKED(&priority_queue->lock));
for (int i = 1; i <= priority_queue->size; i++) {
if (priority_queue->items[i] == value) {
@ -430,10 +406,9 @@ priority_queue_delete(struct priority_queue *priority_queue, void *value)
{
int rc;
lock_node_t node = {};
lock_lock(&priority_queue->lock, &node);
LOCK_LOCK(&priority_queue->lock);
rc = priority_queue_delete_nolock(priority_queue, value);
lock_unlock(&priority_queue->lock, &node);
LOCK_UNLOCK(&priority_queue->lock);
return rc;
}
@ -472,7 +447,8 @@ priority_queue_top_nolock(struct priority_queue *priority_queue, void **dequeued
assert(priority_queue != NULL);
assert(dequeued_element != NULL);
assert(priority_queue->get_priority_fn != NULL);
assert(!priority_queue->use_lock || lock_is_locked(&priority_queue->lock));
assert(!listener_thread_is_running());
assert(!priority_queue->use_lock || LOCK_IS_LOCKED(&priority_queue->lock));
int return_code;
@ -499,10 +475,9 @@ priority_queue_top(struct priority_queue *priority_queue, void **dequeued_elemen
{
int return_code;
lock_node_t node = {};
lock_lock(&priority_queue->lock, &node);
LOCK_LOCK(&priority_queue->lock);
return_code = priority_queue_top_nolock(priority_queue, dequeued_element);
lock_unlock(&priority_queue->lock, &node);
LOCK_UNLOCK(&priority_queue->lock);
return return_code;
}

@ -1,103 +0,0 @@
#pragma once
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "runtime.h" /* For runtime_pid */
/* Used to read process-level metrics associated with sledgert from procfs
* The parsing behavior is based on prtstat -r
*/
enum PROC_STAT
{
PROC_STAT_PID = 0, /* Process ID */
PROC_STAT_COMM = 1, /* Process Name */
PROC_STAT_STATE = 2, /* State */
PROC_STAT_PPID, /* Parent Process ID */
PROC_STAT_PGRP, /* Group ID */
PROC_STAT_SESSION, /* Session ID */
PROC_STAT_TTY_NR, /* ??? */
PROC_STAT_TPGID, /* ??? */
PROC_STAT_FLAGS, /* ??? */
PROC_STAT_MINFLT, /* Minor Page Faults */
PROC_STAT_CMINFLT, /* Minor Page Faults of children */
PROC_STAT_MAJFLT, /* Major Page Faults */
PROC_STAT_CMAJFLT, /* Major Page Faults of children */
PROC_STAT_UTIME, /* User Time */
PROC_STAT_STIME, /* System Time */
PROC_STAT_CUTIME, /* Child User Time */
PROC_STAT_CSTIME, /* Child System Time */
PROC_STAT_PRIORITY,
PROC_STAT_NICE,
PROC_STAT_NUM_THREADS,
PROC_STAT_ITREALVALUE,
PROC_STAT_STARTTIME, /* Start Time */
PROC_STAT_VSIZE, /* Virtual Memory */
PROC_STAT_RSS,
PROC_STAT_RSSLIM,
PROC_STAT_STARTCODE,
PROC_STAT_ENDCODE,
PROC_STAT_STARTSTACK,
PROC_STAT_KSTKESP,
PROC_STAT_KSTKEIP,
PROC_STAT_WCHAN,
PROC_STAT_NSWAP,
PROC_STAT_CNSWAP,
PROC_STAT_EXIT_SIGNAL,
PROC_STAT_PROCESSOR,
PROC_STAT_RT_PRIORITY,
PROC_STAT_POLICY,
PROC_STAT_DELAYACCR_BLKIO_TICKS,
PROC_STAT_GUEST_TIME,
PROC_STAT_CGUEST_TIME,
PROC_STAT_COUNT
};
struct proc_stat_metrics {
uint64_t minor_page_faults;
uint64_t major_page_faults;
uint64_t child_minor_page_faults;
uint64_t child_major_page_faults;
uint64_t user_time;
uint64_t system_time;
uint64_t guest_time;
};
static inline void
proc_stat_metrics_init(struct proc_stat_metrics *stat)
{
assert(runtime_pid > 0);
// Open sledgert's stat file in procfs
char path[256];
snprintf(path, 256, "/proc/%d/stat", runtime_pid);
FILE *proc_stat = fopen(path, "r");
/* Read stat file into in-memory buffer */
char buf[BUFSIZ];
fgets(buf, BUFSIZ, proc_stat);
fclose(proc_stat);
/* Parse into an array of tokens with indices aligning to the PROC_STAT enum */
char *pos = NULL;
char *proc_stat_values[PROC_STAT_COUNT];
for (int i = 0; i < PROC_STAT_COUNT; i++) {
char *tok = i == 0 ? strtok_r(buf, " ", &pos) : strtok_r(NULL, " ", &pos);
proc_stat_values[i] = tok;
}
/* Fill the proc_state_metrics struct with metrics of interest */
/* Minor Page Faults, Major Page Faults, Vsize, User, System, Guest, Uptime */
stat->minor_page_faults = strtoul(proc_stat_values[PROC_STAT_MINFLT], NULL, 10);
stat->major_page_faults = strtoul(proc_stat_values[PROC_STAT_MAJFLT], NULL, 10);
stat->child_minor_page_faults = strtoul(proc_stat_values[PROC_STAT_CMINFLT], NULL, 10);
stat->child_major_page_faults = strtoul(proc_stat_values[PROC_STAT_CMAJFLT], NULL, 10);
stat->user_time = strtoul(proc_stat_values[PROC_STAT_UTIME], NULL, 10);
stat->system_time = strtoul(proc_stat_values[PROC_STAT_STIME], NULL, 10);
stat->guest_time = strtoul(proc_stat_values[PROC_STAT_GUEST_TIME], NULL, 10);
}

@ -1,32 +0,0 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "execution_histogram.h"
#include "http_route_total.h"
#include "module.h"
#include "perf_window.h"
struct regression_model {
double bias;
double scale;
uint32_t num_of_param;
double beta1;
double beta2;
};
/* Assumption: entrypoint is always _start. This should be enhanced later */
struct route {
char *route;
struct http_route_total metrics;
struct module *module;
/* HTTP State */
uint32_t relative_deadline_us;
uint64_t relative_deadline; /* cycles */
char *response_content_type;
struct execution_histogram execution_histogram;
struct perf_window latency;
struct module *module_proprocess;
struct regression_model regr_model;
};

@ -1,179 +0,0 @@
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "admissions_control.h"
#include "runtime.h"
#include "scheduler_options.h"
enum route_config_member
{
route_config_member_route,
route_config_member_path,
route_config_member_admissions_percentile,
route_config_member_relative_deadline_us,
route_config_member_path_preprocess,
route_config_member_model_bias,
route_config_member_model_scale,
route_config_member_model_num_of_param,
route_config_member_model_beta1,
route_config_member_model_beta2,
route_config_member_http_resp_content_type,
route_config_member_len
};
struct route_config {
char *route;
char *path;
uint8_t admissions_percentile;
uint32_t relative_deadline_us;
char *path_preprocess;
uint32_t model_bias;
uint32_t model_scale;
uint32_t model_num_of_param;
uint32_t model_beta1;
uint32_t model_beta2;
char *http_resp_content_type;
};
static inline void
route_config_deinit(struct route_config *config)
{
/* ownership of the route and http_resp_content_type strings was moved during http_router_add_route */
assert(config->route == NULL);
assert(config->http_resp_content_type == NULL);
/* ownership of the path stringswas moved during module_alloc */
assert(config->path == NULL);
}
static inline void
route_config_print(struct route_config *config)
{
printf("[Route] Route: %s\n", config->route);
printf("[Route] Path: %s\n", config->path);
printf("[Route] Admissions Percentile: %hhu\n", config->admissions_percentile);
printf("[Route] Relative Deadline (us): %u\n", config->relative_deadline_us);
printf("[Route] HTTP Response Content Type: %s\n", config->http_resp_content_type);
#ifdef EXECUTION_HISTOGRAM
printf("[Route] Path of Preprocessing Module: %s\n", config->path_preprocess);
printf("[Route] Model Bias: %u\n", config->model_bias);
printf("[Route] Model Scale: %u\n", config->model_scale);
printf("[Route] Model Num of Parameters: %u\n", config->model_num_of_param);
printf("[Route] Model Betas: [%u, %u]\n", config->model_beta1, config->model_beta2);
#endif
}
/**
* Validates a route config generated by a parser
* @param config
* @param did_set boolean array of size route_config_member_len indicating if parser set the associated member
*/
static inline int
route_config_validate(struct route_config *config, bool *did_set)
{
if (did_set[route_config_member_route] == false) {
fprintf(stderr, "route field is required\n");
return -1;
}
if (did_set[route_config_member_path] == false) {
fprintf(stderr, "path field is required\n");
return -1;
}
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";
}
if (scheduler != SCHEDULER_FIFO && scheduler != SCHEDULER_SJF) {
if (did_set[route_config_member_relative_deadline_us] == false) {
fprintf(stderr, "relative_deadline_us is required for the selected scheduler\n");
return -1;
}
if (config->relative_deadline_us > (uint32_t)RUNTIME_RELATIVE_DEADLINE_US_MAX) {
fprintf(stderr, "Relative-deadline-us must be between 0 and %u, was %u\n",
(uint32_t)RUNTIME_RELATIVE_DEADLINE_US_MAX, config->relative_deadline_us);
return -1;
}
}
#ifdef EXECUTION_HISTOGRAM
if (config->admissions_percentile > 99 || config->admissions_percentile < 50) {
fprintf(stderr, "admissions-percentile must be > 50 and <= 99 but was %u, defaulting to 70\n",
config->admissions_percentile);
config->admissions_percentile = 70;
}
#endif
#ifdef EXECUTION_REGRESSION
if (did_set[route_config_member_path_preprocess] == false) {
fprintf(stderr, "model path_preprocess field is required. Put zero if just default preprocessing\n");
return -1;
} else if (strcmp(config->path_preprocess, "0") == 0) {
config->path_preprocess = NULL;
}
if (did_set[route_config_member_model_bias] == false) {
fprintf(stderr, "model bias field is required\n");
return -1;
}
if (did_set[route_config_member_model_scale] == false) {
fprintf(stderr, "model scale field is required\n");
return -1;
}
if (config->model_scale == 0) {
fprintf(stderr, "model scale cannot be zero (to avoid divide by zero)\n");
return -1;
}
if (did_set[route_config_member_model_num_of_param] == false) {
fprintf(stderr, "model num_of_param field is required\n");
return -1;
}
if (did_set[route_config_member_model_beta1] == false) {
fprintf(stderr, "model beta1 field is required\n");
return -1;
}
if (config->model_beta1 == 0) {
fprintf(stderr, "model beta1 cannot be zero (to avoid divide by zero)\n");
return -1;
}
if (did_set[route_config_member_model_beta2] == false) {
fprintf(stderr, "model beta2 field is required. Put zero for just default preprocessing\n");
return -1;
}
if (config->model_num_of_param < 1) {
fprintf(stderr, "model num_of_param must be at least 1 (just default preprocessing)\n");
return -1;
} else if (config->model_num_of_param == 1) {
if (config->path_preprocess) {
fprintf(stderr, "model_num_of_param cannot be 1 when using tenant preprocessing\n");
return -1;
}
config->model_beta2 = 1; /* This is to avoid divide-by-zero */
} else {
/* For now we just support up to two params */
assert(config->model_num_of_param == 2);
if (config->path_preprocess == NULL) {
fprintf(stderr, "model_num_of_param cannot be more than 1 when just default preprocessing\n");
return -1;
}
if (config->model_beta2 == 0) {
fprintf(stderr, "model beta2 cannot be zero (to avoid divide by zero)\n");
return -1;
}
}
#endif
return 0;
}

@ -1,144 +0,0 @@
#pragma once
#include <stdio.h>
#include <string.h>
#include "json.h"
#include "route_config.h"
static const char *route_config_json_keys[route_config_member_len] =
{"route", "path", "admissions-percentile", "relative-deadline-us",
"path_preprocess", "model-bias", "model-scale", "model-num-of-param",
"model-beta1", "model-beta2", "http-resp-content-type"};
static inline int
route_config_set_key_once(bool *did_set, enum route_config_member member)
{
if (did_set[member]) {
debuglog("Redundant key %s\n", route_config_json_keys[member]);
return -1;
}
did_set[member] = true;
return 0;
}
static inline int
route_config_parse(struct route_config *config, const char *json_buf, jsmntok_t *tokens, size_t tokens_base,
int tokens_size)
{
int i = tokens_base;
char key[32] = {0};
bool did_set[route_config_member_len] = {false};
if (!has_valid_type(tokens[i], "Anonymous Route Config Object", JSMN_OBJECT, json_buf)) return -1;
if (!is_nonempty_object(tokens[i], "Anonymous Route Config Object")) return -1;
int route_key_count = tokens[i].size;
for (int route_key_idx = 0; route_key_idx < route_key_count; route_key_idx++) {
/* Advance to key */
i++;
if (!is_valid_key(tokens[i])) return -1;
if (!has_valid_size(tokens[i], key, 1)) return -1;
/* Copy Key */
sprintf(key, "%.*s", tokens[i].end - tokens[i].start, json_buf + tokens[i].start);
/* Advance to Value */
i++;
if (strcmp(key, route_config_json_keys[route_config_member_route]) == 0) {
if (!is_nonempty_string(tokens[i], key)) return -1;
if (route_config_set_key_once(did_set, route_config_member_route) == -1) return -1;
config->route = strndup(json_buf + tokens[i].start, tokens[i].end - tokens[i].start);
} else if (strcmp(key, route_config_json_keys[route_config_member_path]) == 0) {
if (!is_nonempty_string(tokens[i], key)) return -1;
if (route_config_set_key_once(did_set, route_config_member_path) == -1) return -1;
config->path = strndup(json_buf + tokens[i].start, tokens[i].end - tokens[i].start);
} else if (strcmp(key, route_config_json_keys[route_config_member_path_preprocess]) == 0) {
if (!is_nonempty_string(tokens[i], key)) return -1;
if (route_config_set_key_once(did_set, route_config_member_path_preprocess) == -1) return -1;
config->path_preprocess = strndup(json_buf + tokens[i].start, tokens[i].end - tokens[i].start);
} else if (strcmp(key, route_config_json_keys[route_config_member_admissions_percentile]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
if (route_config_set_key_once(did_set, route_config_member_admissions_percentile) == -1)
return -1;
int rc = parse_uint8_t(tokens[i], json_buf,
route_config_json_keys[route_config_member_admissions_percentile],
&config->admissions_percentile);
if (rc < 0) return -1;
} else if (strcmp(key, route_config_json_keys[route_config_member_relative_deadline_us]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
if (route_config_set_key_once(did_set, route_config_member_relative_deadline_us) == -1)
return -1;
int rc = parse_uint32_t(tokens[i], json_buf,
route_config_json_keys[route_config_member_relative_deadline_us],
&config->relative_deadline_us);
if (rc < 0) return -1;
} else if (strcmp(key, "expected-execution-us") == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
printf("The \"expected-execution-us\" field has been deprecated, so no need.\n");
} else if (strcmp(key, route_config_json_keys[route_config_member_model_bias]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
if (route_config_set_key_once(did_set, route_config_member_model_bias) == -1) return -1;
int rc = parse_uint32_t(tokens[i], json_buf,
route_config_json_keys[route_config_member_model_bias],
&config->model_bias);
if (rc < 0) return -1;
} else if (strcmp(key, route_config_json_keys[route_config_member_model_scale]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
if (route_config_set_key_once(did_set, route_config_member_model_scale) == -1) return -1;
int rc = parse_uint32_t(tokens[i], json_buf,
route_config_json_keys[route_config_member_model_scale],
&config->model_scale);
if (rc < 0) return -1;
} else if (strcmp(key, route_config_json_keys[route_config_member_model_num_of_param]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
if (route_config_set_key_once(did_set, route_config_member_model_num_of_param) == -1) return -1;
int rc = parse_uint32_t(tokens[i], json_buf,
route_config_json_keys[route_config_member_model_num_of_param],
&config->model_num_of_param);
if (rc < 0) return -1;
} else if (strcmp(key, route_config_json_keys[route_config_member_model_beta1]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
if (route_config_set_key_once(did_set, route_config_member_model_beta1) == -1) return -1;
int rc = parse_uint32_t(tokens[i], json_buf,
route_config_json_keys[route_config_member_model_beta1],
&config->model_beta1);
if (rc < 0) return -1;
} else if (strcmp(key, route_config_json_keys[route_config_member_model_beta2]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
if (route_config_set_key_once(did_set, route_config_member_model_beta2) == -1) return -1;
int rc = parse_uint32_t(tokens[i], json_buf,
route_config_json_keys[route_config_member_model_beta2],
&config->model_beta2);
if (rc < 0) return -1;
} else if (strcmp(key, route_config_json_keys[route_config_member_http_resp_content_type]) == 0) {
if (!is_nonempty_string(tokens[i], key)) return -1;
if (route_config_set_key_once(did_set, route_config_member_http_resp_content_type) == -1)
return -1;
config->http_resp_content_type = strndup(json_buf + tokens[i].start,
tokens[i].end - tokens[i].start);
} else {
fprintf(stderr, "%s is not a valid key\n", key);
return -1;
}
}
if (route_config_validate(config, did_set) < 0) return -1;
return i;
}

@ -1,38 +0,0 @@
#pragma once
#include <stdint.h>
#include "perf_window.h"
static inline void
route_latency_init(struct perf_window *route_latency)
{
#ifdef ROUTE_LATENCY
perf_window_initialize(route_latency);
#endif
}
static inline uint64_t
route_latency_get(struct perf_window *route_latency, uint8_t percentile, int precomputed_index)
{
#ifdef ROUTE_LATENCY
lock_node_t node = {};
lock_lock(&route_latency->lock, &node);
uint64_t res = perf_window_get_percentile(route_latency, percentile, precomputed_index);
lock_unlock(&route_latency->lock, &node);
return res;
#else
return 0;
#endif
}
static inline void
route_latency_add(struct perf_window *route_latency, uint64_t value)
{
#ifdef ROUTE_LATENCY
lock_node_t node = {};
lock_lock(&route_latency->lock, &node);
perf_window_add(route_latency, value);
lock_unlock(&route_latency->lock, &node);
#endif
}

@ -1,10 +1,9 @@
#pragma once
#include <pthread.h>
#include <sys/epoll.h> /* for epoll_create1(), epoll_ctl(), struct epoll_event */
#include <stdatomic.h>
#include <stdbool.h>
#include <sys/epoll.h> /* for epoll_create1(), epoll_ctl(), struct epoll_event */
#include <sys/types.h> /* for pid_t */
#include "likely.h"
#include "types.h"
@ -22,12 +21,14 @@
*/
#define barrier() __asm__ __volatile__("" ::: "memory")
#define RUNTIME_LOG_FILE "sledge.log"
#define RUNTIME_MAX_EPOLL_EVENTS 128
#define RUNTIME_MAX_TENANT_COUNT 32
#define RUNTIME_RELATIVE_DEADLINE_US_MAX 3600000000 /* One Hour. Fits in uint32_t */
#define RUNTIME_RUNQUEUE_SIZE 256 /* Minimum guaranteed size. Might grow! */
#define RUNTIME_TENANT_QUEUE_SIZE 4096
#define RUNTIME_EXPECTED_EXECUTION_US_MAX 3600000000
#define RUNTIME_HTTP_REQUEST_SIZE_MAX 100000000 /* 100 MB */
#define RUNTIME_HTTP_RESPONSE_SIZE_MAX 100000000 /* 100 MB */
#define RUNTIME_LOG_FILE "sledge.log"
#define RUNTIME_MAX_EPOLL_EVENTS 128
#define RUNTIME_MAX_WORKER_COUNT 32 /* Static buffer size for per-worker globals */
#define RUNTIME_READ_WRITE_VECTOR_LENGTH 16
#define RUNTIME_RELATIVE_DEADLINE_US_MAX 3600000000 /* One Hour. Fits in uint32_t */
enum RUNTIME_SIGALRM_HANDLER
{
@ -35,9 +36,7 @@ enum RUNTIME_SIGALRM_HANDLER
RUNTIME_SIGALRM_HANDLER_TRIAGED = 1
};
extern pid_t runtime_pid;
extern bool runtime_preemption_enabled;
extern bool runtime_worker_spinloop_pause_enabled;
extern uint32_t runtime_processor_speed_MHz;
extern uint32_t runtime_quantum_us;
extern enum RUNTIME_SIGALRM_HANDLER runtime_sigalrm_handler;
@ -45,7 +44,6 @@ extern pthread_t *runtime_worker_threads;
extern uint32_t runtime_worker_threads_count;
extern int *runtime_worker_threads_argument;
extern uint64_t *runtime_worker_threads_deadline;
extern uint64_t runtime_boot_timestamp;
extern void runtime_initialize(void);
extern void runtime_set_pthread_prio(pthread_t thread, unsigned int nice);

@ -1,23 +1,33 @@
#pragma once
#include <sys/mman.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/mman.h>
#include "client_socket.h"
#include "panic.h"
#include "sandbox_types.h"
#include "tenant.h"
/***************************
* Public API *
**************************/
struct sandbox *sandbox_alloc(struct module *module, struct http_session *session, struct route *route,
struct tenant *tenant, uint64_t admissions_estimate);
struct sandbox *sandbox_alloc(struct module *module, int socket_descriptor, const struct sockaddr *socket_address,
uint64_t request_arrival_timestamp, uint64_t admissions_estimate);
int sandbox_prepare_execution_environment(struct sandbox *sandbox);
void sandbox_free(struct sandbox *sandbox);
void sandbox_main(struct sandbox *sandbox);
void sandbox_switch_to(struct sandbox *next_sandbox);
static inline void
sandbox_close_http(struct sandbox *sandbox)
{
assert(sandbox != NULL);
int rc = epoll_ctl(worker_thread_epoll_file_descriptor, EPOLL_CTL_DEL, sandbox->client_socket_descriptor, NULL);
if (unlikely(rc < 0)) panic_err();
client_socket_close(sandbox->client_socket_descriptor, &sandbox->client_address);
}
/**
* Free Linear Memory, leaving stack in place
@ -32,6 +42,18 @@ sandbox_free_linear_memory(struct sandbox *sandbox)
sandbox->memory = NULL;
}
/**
* Deinitialize Linear Memory, cleaning up the backing buffer
* @param sandbox
*/
static inline void
sandbox_deinit_http_buffers(struct sandbox *sandbox)
{
assert(sandbox);
vec_u8_deinit(&sandbox->request);
vec_u8_deinit(&sandbox->response);
}
/**
* Given a sandbox, returns the module that sandbox is executing
* @param sandbox the sandbox whose module we want
@ -49,12 +71,23 @@ sandbox_get_priority(void *element)
{
struct sandbox *sandbox = (struct sandbox *)element;
return sandbox->absolute_deadline;
}
};
static inline void
sandbox_process_scheduler_updates(struct sandbox *sandbox)
sandbox_open_http(struct sandbox *sandbox)
{
if (tenant_is_paid(sandbox->tenant)) {
atomic_fetch_sub(&sandbox->tenant->remaining_budget, sandbox->last_state_duration);
}
assert(sandbox != NULL);
http_parser_init(&sandbox->http_parser, HTTP_REQUEST);
/* Set the sandbox as the data the http-parser has access to */
sandbox->http_parser.data = sandbox;
/* Freshly allocated sandbox going runnable for first time, so register client socket with epoll */
struct epoll_event accept_evt;
accept_evt.data.ptr = (void *)sandbox;
accept_evt.events = EPOLLIN | EPOLLOUT | EPOLLET;
int rc = epoll_ctl(worker_thread_epoll_file_descriptor, EPOLL_CTL_ADD, sandbox->client_socket_descriptor,
&accept_evt);
if (unlikely(rc < 0)) panic_err();
}

@ -13,10 +13,9 @@ static inline void
sandbox_perf_log_print_header()
{
if (sandbox_perf_log == NULL) { perror("sandbox perf log"); }
fprintf(sandbox_perf_log,
"id,tenant,route,state,deadline,actual,queued,uninitialized,allocated,initialized,"
"runnable,interrupted,preempted,"
"running_sys,running_user,asleep,returned,complete,error,proc_MHz,payload_size,regression_param\n");
fprintf(sandbox_perf_log, "id,module,port,state,deadline,actual,queued,uninitialized,allocated,initialized,"
"runnable,interrupted,preempted,"
"running_sys,running_user,asleep,returned,complete,error,proc_MHz,memory\n");
}
/**
@ -30,25 +29,23 @@ sandbox_perf_log_print_entry(struct sandbox *sandbox)
/* If the log was not defined by an environment variable, early out */
if (sandbox_perf_log == NULL) return;
uint64_t queued_duration = sandbox->timestamp_of.dispatched - sandbox->timestamp_of.allocation;
uint64_t queued_duration = sandbox->timestamp_of.allocation - sandbox->timestamp_of.request_arrival;
/*
* Assumption: A sandbox is never able to free pages. If linear memory management
* becomes more intelligent, then peak linear memory size needs to be tracked
* seperately from current linear memory size.
*/
fprintf(sandbox_perf_log,
"%lu,%s,%s,%s,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%u,%u,%d,%d,%lf\n",
sandbox->id, sandbox->tenant->name, sandbox->route->route, sandbox_state_stringify(sandbox->state),
sandbox->route->relative_deadline, sandbox->total_time, queued_duration,
fprintf(sandbox_perf_log, "%lu,%s,%d,%s,%lu,%lu,%lu,,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%u\n",
sandbox->id, sandbox->module->name, sandbox->module->port, sandbox_state_stringify(sandbox->state),
sandbox->module->relative_deadline, sandbox->total_time, queued_duration,
sandbox->duration_of_state[SANDBOX_UNINITIALIZED], sandbox->duration_of_state[SANDBOX_ALLOCATED],
sandbox->duration_of_state[SANDBOX_INITIALIZED], sandbox->duration_of_state[SANDBOX_RUNNABLE],
sandbox->duration_of_state[SANDBOX_INTERRUPTED], sandbox->duration_of_state[SANDBOX_PREEMPTED],
sandbox->duration_of_state[SANDBOX_RUNNING_SYS], sandbox->duration_of_state[SANDBOX_RUNNING_USER],
sandbox->duration_of_state[SANDBOX_ASLEEP], sandbox->duration_of_state[SANDBOX_RETURNED],
sandbox->duration_of_state[SANDBOX_COMPLETE], sandbox->duration_of_state[SANDBOX_ERROR],
runtime_processor_speed_MHz, sandbox->response_code, 0, sandbox->payload_size,
sandbox->regression_param);
runtime_processor_speed_MHz);
}
static inline void

@ -0,0 +1,108 @@
#pragma once
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include "current_sandbox.h"
#include "debuglog.h"
#include "http_parser.h"
#include "http_request.h"
#include "http_parser_settings.h"
#include "likely.h"
#include "sandbox_types.h"
#include "scheduler.h"
/**
* Receive and Parse the Request for the current sandbox
* @return 0 if message parsing complete, -1 on error, -2 if buffers run out of space
*/
static inline int
sandbox_receive_request(struct sandbox *sandbox)
{
assert(sandbox != NULL);
int rc = 0;
struct vec_u8 *request = &sandbox->request;
assert(request->length == 0);
assert(request->capacity > 0);
while (!sandbox->http_request.message_end) {
/* Read from the Socket */
/* Structured to closely follow usage example at https://github.com/nodejs/http-parser */
http_parser *parser = &sandbox->http_parser;
const http_parser_settings *settings = http_parser_settings_get();
size_t request_length = request->length;
size_t request_capacity = request->capacity;
if (request_length >= request_capacity) {
debuglog("Sandbox %lu: Ran out of Request Buffer before message end\n", sandbox->id);
goto err_nobufs;
}
ssize_t bytes_received = recv(sandbox->client_socket_descriptor, &request->buffer[request_length],
request_capacity - request_length, 0);
if (bytes_received < 0) {
if (errno == EAGAIN) {
current_sandbox_sleep();
continue;
} else {
debuglog("Error reading socket %d - %s\n", sandbox->client_socket_descriptor,
strerror(errno));
goto err;
}
}
/* If we received an EOF before we were able to parse a complete HTTP header, request is malformed */
if (bytes_received == 0 && !sandbox->http_request.message_end) {
char client_address_text[INET6_ADDRSTRLEN] = {};
if (unlikely(inet_ntop(AF_INET, &sandbox->client_address, client_address_text, INET6_ADDRSTRLEN)
== NULL)) {
debuglog("Failed to log client_address: %s", strerror(errno));
}
debuglog("Sandbox %lu: recv returned 0 before a complete request was received\n", sandbox->id);
debuglog("Socket: %d. Address: %s\n", sandbox->client_socket_descriptor, client_address_text);
http_request_print(&sandbox->http_request);
goto err;
}
assert(bytes_received > 0);
#ifdef LOG_HTTP_PARSER
debuglog("Sandbox: %lu http_parser_execute(%p, %p, %p, %zu\n)", sandbox->id, parser, settings,
&sandbox->request.base[sandbox->request.length], bytes_received);
#endif
size_t bytes_parsed = http_parser_execute(parser, settings,
(const char *)&request->buffer[request_length],
(size_t)bytes_received);
if (bytes_parsed != (size_t)bytes_received) {
debuglog("Error: %s, Description: %s\n",
http_errno_name((enum http_errno)sandbox->http_parser.http_errno),
http_errno_description((enum http_errno)sandbox->http_parser.http_errno));
debuglog("Length Parsed %zu, Length Read %zu\n", bytes_parsed, (size_t)bytes_received);
debuglog("Error parsing socket %d\n", sandbox->client_socket_descriptor);
goto err;
}
request->length += bytes_parsed;
}
rc = 0;
done:
return rc;
err_nobufs:
rc = -2;
goto done;
err:
rc = -1;
goto done;
}

@ -6,8 +6,8 @@
#include "arch/context.h"
#include "current_sandbox.h"
#include "ps_list.h"
#include "sandbox_state_history.h"
#include "sandbox_state_transition.h"
#include "sandbox_types.h"
/**
@ -24,13 +24,8 @@ sandbox_set_as_allocated(struct sandbox *sandbox)
/* State Change Bookkeeping */
assert(now > sandbox->timestamp_of.last_state_change);
sandbox->timestamp_of.allocation = now;
sandbox->timestamp_of.last_state_change = now;
sandbox_state_history_init(&sandbox->state_history);
sandbox_state_history_append(&sandbox->state_history, SANDBOX_ALLOCATED);
sandbox_state_totals_increment(SANDBOX_ALLOCATED);
/* State Change Hooks */
sandbox_state_transition_from_hook(sandbox, SANDBOX_UNINITIALIZED);
sandbox_state_transition_to_hook(sandbox, SANDBOX_ALLOCATED);
}

@ -5,10 +5,9 @@
#include "arch/getcycles.h"
#include "local_runqueue.h"
#include "sandbox_types.h"
#include "sandbox_state.h"
#include "sandbox_state_history.h"
#include "sandbox_state_transition.h"
#include "sandbox_types.h"
/**
* Transitions a sandbox to the SANDBOX_ASLEEP state.
@ -38,16 +37,11 @@ sandbox_set_as_asleep(struct sandbox *sandbox, sandbox_state_t last_state)
/* State Change Bookkeeping */
assert(now > sandbox->timestamp_of.last_state_change);
sandbox->last_state_duration = now - sandbox->timestamp_of.last_state_change;
sandbox->duration_of_state[last_state] += sandbox->last_state_duration;
sandbox->duration_of_state[last_state] += (now - sandbox->timestamp_of.last_state_change);
sandbox->timestamp_of.last_state_change = now;
sandbox_state_history_append(&sandbox->state_history, SANDBOX_ASLEEP);
sandbox_state_totals_increment(SANDBOX_ASLEEP);
sandbox_state_totals_decrement(last_state);
/* State Change Hooks */
sandbox_state_transition_from_hook(sandbox, last_state);
sandbox_state_transition_to_hook(sandbox, SANDBOX_ASLEEP);
}
static inline void

@ -3,20 +3,19 @@
#include <assert.h>
#include <stdint.h>
#include "admissions_control.h"
#include "arch/getcycles.h"
#include "execution_histogram.h"
#include "panic.h"
#include "local_completion_queue.h"
#include "sandbox_functions.h"
#include "sandbox_perf_log.h"
#include "sandbox_state.h"
#include "sandbox_state_history.h"
#include "sandbox_state_transition.h"
#include "sandbox_summarize_page_allocations.h"
#include "sandbox_types.h"
/**
* Transitions a sandbox from the SANDBOX_RETURNED state to the SANDBOX_COMPLETE state.
* Adds the sandbox to the completion queue
* @param sandbox
* @param last_state the state the sandbox is transitioning from. This is expressed as a constant to
* enable the compiler to perform constant propagation optimizations.
@ -41,37 +40,22 @@ sandbox_set_as_complete(struct sandbox *sandbox, sandbox_state_t last_state)
/* State Change Bookkeeping */
assert(now > sandbox->timestamp_of.last_state_change);
sandbox->last_state_duration = now - sandbox->timestamp_of.last_state_change;
sandbox->duration_of_state[last_state] += sandbox->last_state_duration;
sandbox->duration_of_state[last_state] += (now - sandbox->timestamp_of.last_state_change);
sandbox->timestamp_of.last_state_change = now;
sandbox_state_history_append(&sandbox->state_history, SANDBOX_COMPLETE);
sandbox_state_totals_increment(SANDBOX_COMPLETE);
sandbox_state_totals_decrement(last_state);
struct route *route = sandbox->route;
#ifdef EXECUTION_HISTOGRAM
/* Execution Histogram Post Processing */
const uint64_t execution_duration = sandbox->duration_of_state[SANDBOX_RUNNING_USER]
+ sandbox->duration_of_state[SANDBOX_RUNNING_SYS];
execution_histogram_update(&route->execution_histogram, execution_duration);
#endif
#ifdef ADMISSIONS_CONTROL
/* Admissions Control Post Processing */
admissions_info_update(&sandbox->module->admissions_info, sandbox->duration_of_state[SANDBOX_RUNNING_USER]
+ sandbox->duration_of_state[SANDBOX_RUNNING_SYS]);
admissions_control_subtract(sandbox->admissions_estimate);
#endif
/* Terminal State Logging for Sandbox */
/* Terminal State Logging */
sandbox_perf_log_print_entry(sandbox);
sandbox_summarize_page_allocations(sandbox);
route_latency_add(&route->latency, sandbox->total_time);
/* State Change Hooks */
sandbox_state_transition_from_hook(sandbox, last_state);
sandbox_state_transition_to_hook(sandbox, SANDBOX_COMPLETE);
/* Does not add to cleanup queue until in cooperative scheduler */
/* Does not add to completion queue until in cooperative scheduler */
}
static inline void

@ -3,22 +3,20 @@
#include <assert.h>
#include <stdint.h>
#include "admissions_control.h"
#include "arch/getcycles.h"
#include "listener_thread.h"
#include "local_completion_queue.h"
#include "local_runqueue.h"
#include "panic.h"
#include "sandbox_state.h"
#include "sandbox_functions.h"
#include "sandbox_perf_log.h"
#include "sandbox_state.h"
#include "sandbox_state_history.h"
#include "sandbox_state_transition.h"
#include "sandbox_summarize_page_allocations.h"
#include "panic.h"
/**
* Transitions a sandbox to the SANDBOX_ERROR state.
* This can occur during initialization or execution
* Unmaps linear memory, removes from the runqueue (if on it)
* Unmaps linear memory, removes from the runqueue (if on it), and adds to the completion queue
* Because the stack is still in use, freeing the stack is deferred until later
*
* @param sandbox the sandbox erroring out
@ -38,6 +36,7 @@ sandbox_set_as_error(struct sandbox *sandbox, sandbox_state_t last_state)
case SANDBOX_RUNNING_SYS: {
local_runqueue_delete(sandbox);
sandbox_free_linear_memory(sandbox);
sandbox_deinit_http_buffers(sandbox);
break;
}
default: {
@ -48,38 +47,20 @@ sandbox_set_as_error(struct sandbox *sandbox, sandbox_state_t last_state)
/* State Change Bookkeeping */
assert(now > sandbox->timestamp_of.last_state_change);
sandbox->last_state_duration = now - sandbox->timestamp_of.last_state_change;
if (last_state == SANDBOX_RUNNING_SYS) {
sandbox->remaining_exec = (sandbox->remaining_exec > sandbox->last_state_duration)
? sandbox->remaining_exec - sandbox->last_state_duration
: 0;
}
sandbox->duration_of_state[last_state] += sandbox->last_state_duration;
sandbox->timestamp_of.last_state_change = now;
uint64_t duration_of_last_state = now - sandbox->timestamp_of.last_state_change;
sandbox->duration_of_state[last_state] += duration_of_last_state;
sandbox_state_history_append(&sandbox->state_history, SANDBOX_ERROR);
sandbox_state_totals_increment(SANDBOX_ERROR);
sandbox_state_totals_decrement(last_state);
#ifdef ADMISSIONS_CONTROL
/* Admissions Control Post Processing */
admissions_control_subtract(sandbox->admissions_estimate);
#endif
/* Return HTTP session to listener core to be written back to client */
http_session_set_response_header(sandbox->http, 500);
sandbox->http->state = HTTP_SESSION_EXECUTION_COMPLETE;
http_session_send_response(sandbox->http, (void_star_cb)listener_thread_register_http_session);
sandbox->http = NULL;
/* Terminal State Logging */
sandbox_perf_log_print_entry(sandbox);
sandbox_summarize_page_allocations(sandbox);
/* State Change Hooks */
sandbox_state_transition_from_hook(sandbox, last_state);
sandbox_state_transition_to_hook(sandbox, SANDBOX_ERROR);
/* Does not add to cleanup queue until in cooperative scheduler */
/* Does not add to completion queue until in cooperative scheduler */
}
static inline void
@ -87,6 +68,4 @@ sandbox_exit_error(struct sandbox *sandbox)
{
assert(sandbox->state == SANDBOX_RUNNING_SYS);
sandbox_set_as_error(sandbox, SANDBOX_RUNNING_SYS);
sandbox_process_scheduler_updates(sandbox);
}

@ -6,8 +6,8 @@
#include "arch/context.h"
#include "current_sandbox.h"
#include "ps_list.h"
#include "sandbox_state_history.h"
#include "sandbox_state_transition.h"
#include "sandbox_types.h"
/**
@ -34,15 +34,10 @@ sandbox_set_as_initialized(struct sandbox *sandbox, sandbox_state_t last_state)
}
/* State Change Bookkeeping */
assert(now > sandbox->timestamp_of.last_state_change);
sandbox->last_state_duration = now - sandbox->timestamp_of.last_state_change;
sandbox->duration_of_state[last_state] += sandbox->last_state_duration;
assert(now >= sandbox->timestamp_of.last_state_change);
sandbox->duration_of_state[last_state] += (now - sandbox->timestamp_of.last_state_change);
sandbox->timestamp_of.last_state_change = now;
sandbox_state_history_append(&sandbox->state_history, SANDBOX_INITIALIZED);
sandbox_state_totals_increment(SANDBOX_INITIALIZED);
sandbox_state_totals_decrement(last_state);
/* State Change Hooks */
sandbox_state_transition_from_hook(sandbox, last_state);
sandbox_state_transition_to_hook(sandbox, SANDBOX_INITIALIZED);
}

@ -8,7 +8,6 @@
#include "panic.h"
#include "sandbox_functions.h"
#include "sandbox_state_history.h"
#include "sandbox_state_transition.h"
#include "sandbox_types.h"
static inline void
@ -24,28 +23,17 @@ sandbox_set_as_interrupted(struct sandbox *sandbox, sandbox_state_t last_state)
/* State Change Bookkeeping */
assert(now > sandbox->timestamp_of.last_state_change);
sandbox->last_state_duration = now - sandbox->timestamp_of.last_state_change;
assert(last_state == SANDBOX_RUNNING_USER);
sandbox->remaining_exec = (sandbox->remaining_exec > sandbox->last_state_duration)
? sandbox->remaining_exec - sandbox->last_state_duration
: 0;
sandbox->duration_of_state[last_state] += sandbox->last_state_duration;
sandbox->duration_of_state[last_state] += (now - sandbox->timestamp_of.last_state_change);
sandbox->timestamp_of.last_state_change = now;
/* We do not append SANDBOX_INTERRUPTED to the sandbox_state_history because it would quickly fill the buffer */
sandbox_state_totals_increment(SANDBOX_INTERRUPTED);
sandbox_state_totals_decrement(last_state);
/* State Change Hooks */
sandbox_state_transition_from_hook(sandbox, last_state);
sandbox_state_transition_to_hook(sandbox, SANDBOX_INTERRUPTED);
}
static inline void
sandbox_interrupt(struct sandbox *sandbox)
{
sandbox_set_as_interrupted(sandbox, sandbox->state);
sandbox_process_scheduler_updates(sandbox);
}
@ -69,10 +57,6 @@ sandbox_interrupt_return(struct sandbox *sandbox, sandbox_state_t interrupted_st
sandbox_state_totals_increment(interrupted_state);
sandbox_state_totals_decrement(SANDBOX_INTERRUPTED);
if (sandbox->absolute_deadline < now) {
// printf("Interrupted Sandbox missed deadline already!\n");
}
barrier();
/* WARNING: Code after this assignment may be preemptable */
sandbox->state = interrupted_state;

@ -7,7 +7,6 @@
#include "local_runqueue.h"
#include "panic.h"
#include "sandbox_state_history.h"
#include "sandbox_state_transition.h"
#include "sandbox_types.h"
/**
@ -38,16 +37,11 @@ sandbox_set_as_preempted(struct sandbox *sandbox, sandbox_state_t last_state)
/* State Change Bookkeeping */
assert(now > sandbox->timestamp_of.last_state_change);
sandbox->last_state_duration = now - sandbox->timestamp_of.last_state_change;
sandbox->duration_of_state[last_state] += sandbox->last_state_duration;
sandbox->duration_of_state[last_state] += (now - sandbox->timestamp_of.last_state_change);
sandbox->timestamp_of.last_state_change = now;
sandbox_state_history_append(&sandbox->state_history, SANDBOX_PREEMPTED);
sandbox_state_totals_increment(SANDBOX_PREEMPTED);
sandbox_state_totals_decrement(last_state);
/* State Change Hooks */
sandbox_state_transition_from_hook(sandbox, last_state);
sandbox_state_transition_to_hook(sandbox, SANDBOX_PREEMPTED);
}
static inline void

@ -4,14 +4,11 @@
#include <stdint.h>
#include "arch/getcycles.h"
#include "auto_buf.h"
#include "listener_thread.h"
#include "local_runqueue.h"
#include "panic.h"
#include "sandbox_functions.h"
#include "sandbox_state.h"
#include "sandbox_state_history.h"
#include "sandbox_state_transition.h"
#include "sandbox_types.h"
/**
@ -32,8 +29,11 @@ sandbox_set_as_returned(struct sandbox *sandbox, sandbox_state_t last_state)
switch (last_state) {
case SANDBOX_RUNNING_SYS: {
sandbox->timestamp_of.response = now;
sandbox->total_time = now - sandbox->timestamp_of.request_arrival;
local_runqueue_delete(sandbox);
sandbox_free_linear_memory(sandbox);
sandbox_deinit_http_buffers(sandbox);
break;
}
default: {
@ -44,26 +44,9 @@ sandbox_set_as_returned(struct sandbox *sandbox, sandbox_state_t last_state)
/* State Change Bookkeeping */
assert(now > sandbox->timestamp_of.last_state_change);
sandbox->last_state_duration = now - sandbox->timestamp_of.last_state_change;
sandbox->remaining_exec = (sandbox->remaining_exec > sandbox->last_state_duration)
? sandbox->remaining_exec - sandbox->last_state_duration
: 0;
sandbox->duration_of_state[last_state] += sandbox->last_state_duration;
sandbox->duration_of_state[last_state] += (now - sandbox->timestamp_of.last_state_change);
sandbox->timestamp_of.last_state_change = now;
sandbox_state_history_append(&sandbox->state_history, SANDBOX_RETURNED);
sandbox_state_totals_increment(SANDBOX_RETURNED);
sandbox_state_totals_decrement(last_state);
http_session_set_response_header(sandbox->http, 200);
sandbox->http->state = HTTP_SESSION_EXECUTION_COMPLETE;
http_session_send_response(sandbox->http, (void_star_cb)listener_thread_register_http_session);
sandbox->http = NULL;
/* State Change Hooks */
sandbox_state_transition_from_hook(sandbox, last_state);
sandbox_state_transition_to_hook(sandbox, SANDBOX_RETURNED);
assert(sandbox->response_code == 0);
sandbox->response_code = 200;
sandbox_process_scheduler_updates(sandbox);
}

@ -7,7 +7,6 @@
#include "local_runqueue.h"
#include "panic.h"
#include "sandbox_state_history.h"
#include "sandbox_state_transition.h"
#include "sandbox_types.h"
/**
@ -30,7 +29,6 @@ sandbox_set_as_runnable(struct sandbox *sandbox, sandbox_state_t last_state)
switch (last_state) {
case SANDBOX_INITIALIZED: {
sandbox->timestamp_of.dispatched = now;
local_runqueue_add(sandbox);
break;
}
@ -46,16 +44,11 @@ sandbox_set_as_runnable(struct sandbox *sandbox, sandbox_state_t last_state)
/* State Change Bookkeeping */
assert(now > sandbox->timestamp_of.last_state_change);
sandbox->last_state_duration = now - sandbox->timestamp_of.last_state_change;
sandbox->duration_of_state[last_state] += sandbox->last_state_duration;
sandbox->duration_of_state[last_state] += (now - sandbox->timestamp_of.last_state_change);
sandbox->timestamp_of.last_state_change = now;
sandbox_state_history_append(&sandbox->state_history, SANDBOX_RUNNABLE);
sandbox_state_totals_increment(SANDBOX_RUNNABLE);
sandbox_state_totals_decrement(last_state);
/* State Change Hooks */
sandbox_state_transition_from_hook(sandbox, last_state);
sandbox_state_transition_to_hook(sandbox, SANDBOX_RUNNABLE);
}

@ -8,7 +8,6 @@
#include "panic.h"
#include "sandbox_functions.h"
#include "sandbox_state_history.h"
#include "sandbox_state_transition.h"
#include "sandbox_types.h"
static inline void
@ -40,30 +39,16 @@ sandbox_set_as_running_sys(struct sandbox *sandbox, sandbox_state_t last_state)
/* State Change Bookkeeping */
assert(now > sandbox->timestamp_of.last_state_change);
sandbox->last_state_duration = now - sandbox->timestamp_of.last_state_change;
if (last_state == SANDBOX_RUNNING_USER) {
sandbox->remaining_exec = (sandbox->remaining_exec > sandbox->last_state_duration)
? sandbox->remaining_exec - sandbox->last_state_duration
: 0;
}
sandbox->duration_of_state[last_state] += sandbox->last_state_duration;
sandbox->duration_of_state[last_state] += (now - sandbox->timestamp_of.last_state_change);
sandbox->timestamp_of.last_state_change = now;
sandbox_state_history_append(&sandbox->state_history, SANDBOX_RUNNING_SYS);
sandbox_state_totals_increment(SANDBOX_RUNNING_SYS);
sandbox_state_totals_decrement(last_state);
/* State Change Hooks */
sandbox_state_transition_from_hook(sandbox, last_state);
sandbox_state_transition_to_hook(sandbox, SANDBOX_RUNNING_SYS);
}
static inline void
sandbox_syscall(struct sandbox *sandbox)
{
if (sandbox->module->type == PREPROCESS_MODULE) return;
assert(sandbox->state == SANDBOX_RUNNING_USER);
sandbox_set_as_running_sys(sandbox, SANDBOX_RUNNING_USER);
sandbox_process_scheduler_updates(sandbox);
}

@ -6,10 +6,9 @@
#include "arch/getcycles.h"
#include "current_sandbox.h"
#include "panic.h"
#include "sandbox_functions.h"
#include "sandbox_state_history.h"
#include "sandbox_state_transition.h"
#include "sandbox_types.h"
#include "sandbox_functions.h"
static inline void
sandbox_set_as_running_user(struct sandbox *sandbox, sandbox_state_t last_state)
@ -36,25 +35,12 @@ sandbox_set_as_running_user(struct sandbox *sandbox, sandbox_state_t last_state)
/* State Change Bookkeeping */
assert(now > sandbox->timestamp_of.last_state_change);
sandbox->last_state_duration = now - sandbox->timestamp_of.last_state_change;
if (last_state == SANDBOX_RUNNING_SYS) {
sandbox->remaining_exec = (sandbox->remaining_exec > sandbox->last_state_duration)
? sandbox->remaining_exec - sandbox->last_state_duration
: 0;
}
sandbox->duration_of_state[last_state] += sandbox->last_state_duration;
sandbox->duration_of_state[last_state] += (now - sandbox->timestamp_of.last_state_change);
sandbox->timestamp_of.last_state_change = now;
sandbox_state_history_append(&sandbox->state_history, SANDBOX_RUNNING_USER);
sandbox_state_totals_increment(SANDBOX_RUNNING_USER);
sandbox_state_totals_decrement(last_state);
/* State Change Hooks */
sandbox_state_transition_from_hook(sandbox, last_state);
sandbox_state_transition_to_hook(sandbox, SANDBOX_RUNNING_USER);
if (last_state == SANDBOX_RUNNING_SYS)
sandbox_process_scheduler_updates(sandbox); // TODO: is this code preemptable? Ok to be?
barrier();
sandbox->state = SANDBOX_RUNNING_USER;
/* WARNING: All code after this assignment is preemptable */
@ -66,8 +52,6 @@ sandbox_set_as_running_user(struct sandbox *sandbox, sandbox_state_t last_state)
static inline void
sandbox_return(struct sandbox *sandbox)
{
if (sandbox->module->type == PREPROCESS_MODULE) return;
assert(sandbox->state == SANDBOX_RUNNING_SYS);
sandbox_set_as_running_user(sandbox, SANDBOX_RUNNING_SYS);
}

@ -1,47 +0,0 @@
#pragma once
#include <assert.h>
#include "sandbox_state.h"
#include "sandbox_types.h"
typedef void (*sandbox_state_transition_hook_t)(struct sandbox *);
extern sandbox_state_transition_hook_t sandbox_state_transition_from_hooks[SANDBOX_STATE_COUNT];
extern sandbox_state_transition_hook_t sandbox_state_transition_to_hooks[SANDBOX_STATE_COUNT];
static inline void
sandbox_state_transition_from_hook(struct sandbox *sandbox, sandbox_state_t state)
{
assert(sandbox != NULL);
assert(state < SANDBOX_STATE_COUNT);
sandbox_state_transition_from_hooks[state](sandbox);
}
static inline void
sandbox_state_transition_to_hook(struct sandbox *sandbox, sandbox_state_t state)
{
assert(sandbox != NULL);
assert(state < SANDBOX_STATE_COUNT);
sandbox_state_transition_to_hooks[state](sandbox);
}
static inline void
sandbox_state_transition_from_hook_register(sandbox_state_t state, sandbox_state_transition_hook_t cb)
{
assert(state < SANDBOX_STATE_COUNT);
assert(cb != NULL);
sandbox_state_transition_from_hooks[state] = cb;
}
static inline void
sandbox_state_transition_to_hook_register(sandbox_state_t state, sandbox_state_transition_hook_t cb)
{
assert(state < SANDBOX_STATE_COUNT);
assert(cb != NULL);
sandbox_state_transition_to_hooks[state] = cb;
}

@ -4,7 +4,7 @@
#include <stdint.h>
/* Count of the total number of requests we've ever allocated. Never decrements as it is used to generate IDs */
extern _Atomic uint64_t sandbox_total;
extern _Atomic uint32_t sandbox_total;
static inline void
sandbox_total_initialize()
@ -12,8 +12,8 @@ sandbox_total_initialize()
atomic_init(&sandbox_total, 0);
}
static inline uint64_t
static inline uint32_t
sandbox_total_postfix_increment()
{
return atomic_fetch_add(&sandbox_total, 1) + 1;
return atomic_fetch_add(&sandbox_total, 1);
}

@ -1,19 +1,27 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <sys/socket.h>
#include <ucontext.h>
#include <unistd.h>
#include "arch/context.h"
#include "http_session.h"
#include "http_parser.h"
#include "http_request.h"
#include "module.h"
#include "ps_list.h"
#include "sandbox_state.h"
#include "sandbox_state_history.h"
#include "tenant.h"
#include "wasi.h"
#include "wasm_globals.h"
#include "vec.h"
#include "wasm_memory.h"
#include "wasm_stack.h"
#include "wasm_types.h"
#include "wasm_stack.h"
#include "wasm_globals.h"
#include "wasi.h"
#define u8 uint8_t
VEC(u8)
/*********************
* Structs and Types *
@ -21,8 +29,9 @@
struct sandbox_timestamps {
uint64_t last_state_change; /* Used for bookkeeping of actual execution time */
uint64_t request_arrival; /* Timestamp when request is received */
uint64_t allocation; /* Timestamp when sandbox is allocated */
uint64_t dispatched; /* Timestamp when a sandbox is first added to a worker's runqueue */
uint64_t response; /* Timestamp when response is sent */
uint64_t completion; /* Timestamp when sandbox runs to completion */
#ifdef LOG_SANDBOX_MEMORY_PROFILE
uint32_t page_allocations[SANDBOX_PAGE_ALLOCATION_TIMESTAMP_COUNT];
@ -31,22 +40,19 @@ struct sandbox_timestamps {
};
struct sandbox {
/* used by ps_list's default name-based MACROS for the scheduling runqueue */
/* Keep as first member of sandbox struct to ensure ps_list maintains alignment */
struct ps_list list;
uint64_t id;
sandbox_state_t state;
struct sandbox_state_history state_history;
uint16_t response_code;
/* Accounting Info */
struct route *route;
struct tenant *tenant;
struct ps_list list; /* used by ps_list's default name-based MACROS for the scheduling runqueue */
/* HTTP State */
struct http_session *http;
struct sockaddr client_address; /* client requesting connection! */
int client_socket_descriptor;
http_parser http_parser;
struct http_request http_request;
struct vec_u8 request;
struct vec_u8 response;
/* WebAssembly Module State */
struct module *module; /* the module this is an instance of */
@ -60,15 +66,11 @@ struct sandbox {
/* Scheduling and Temporal State */
struct sandbox_timestamps timestamp_of;
uint64_t duration_of_state[SANDBOX_STATE_COUNT];
uint64_t last_state_duration;
uint64_t remaining_exec;
uint64_t absolute_deadline;
uint64_t admissions_estimate; /* estimated execution time (cycles) * runtime_admissions_granularity / relative
deadline (cycles) */
uint64_t total_time; /* Total time from Request to Response */
int payload_size;
double regression_param; /* Calculated in tenant preprocessing logic if provided */
/* System Interface State */
int32_t return_value;

@ -4,25 +4,23 @@
#include <errno.h>
#include <stdint.h>
#include "client_socket.h"
#include "current_sandbox.h"
#include "global_request_scheduler.h"
#include "global_request_scheduler_deque.h"
#include "global_request_scheduler_minheap.h"
#include "global_request_scheduler_mtds.h"
#include "local_cleanup_queue.h"
#include "local_runqueue.h"
#include "local_runqueue_list.h"
#include "local_runqueue_minheap.h"
#include "local_runqueue_mtds.h"
#include "local_runqueue_list.h"
#include "panic.h"
#include "sandbox_functions.h"
#include "sandbox_set_as_interrupted.h"
#include "sandbox_types.h"
#include "sandbox_set_as_preempted.h"
#include "sandbox_set_as_runnable.h"
#include "sandbox_set_as_running_sys.h"
#include "sandbox_set_as_interrupted.h"
#include "sandbox_set_as_running_user.h"
#include "sandbox_types.h"
#include "scheduler_options.h"
#include "scheduler_execute_epoll_loop.h"
/**
@ -63,78 +61,18 @@
* initialize a sandbox.
*/
static inline struct sandbox *
scheduler_mtdbf_get_next()
enum SCHEDULER
{
return NULL;
}
static inline struct sandbox *
scheduler_mtds_get_next()
{
/* Get the deadline of the sandbox at the head of the local queue */
struct sandbox *local = local_runqueue_get_next();
uint64_t local_deadline = local == NULL ? UINT64_MAX : local->absolute_deadline;
enum MULTI_TENANCY_CLASS local_mt_class = MT_DEFAULT;
struct sandbox *global = NULL;
if (local) local_mt_class = local->tenant->pwt_sandboxes[worker_thread_idx].mt_class;
uint64_t global_guaranteed_deadline = global_request_scheduler_mtds_guaranteed_peek();
uint64_t global_default_deadline = global_request_scheduler_mtds_default_peek();
/* Try to pull and allocate from the global queue if earlier
* This will be placed at the head of the local runqueue */
switch (local_mt_class) {
case MT_GUARANTEED:
if (global_guaranteed_deadline >= local_deadline) goto done;
break;
case MT_DEFAULT:
if (global_guaranteed_deadline == UINT64_MAX && global_default_deadline >= local_deadline) goto done;
break;
}
SCHEDULER_FIFO = 0,
SCHEDULER_EDF = 1
};
if (global_request_scheduler_mtds_remove_with_mt_class(&global, local_deadline, local_mt_class) == 0) {
assert(global != NULL);
sandbox_prepare_execution_environment(global);
assert(global->state == SANDBOX_INITIALIZED);
sandbox_set_as_runnable(global, SANDBOX_INITIALIZED);
}
/* Return what is at the head of the local runqueue or NULL if empty */
done:
return local_runqueue_get_next();
}
static inline struct sandbox *
scheduler_sjf_get_next()
{
struct sandbox *local = local_runqueue_get_next();
uint64_t local_rem_exec = local == NULL ? UINT64_MAX : local->remaining_exec;
struct sandbox *global = NULL;
uint64_t global_remaining_exec = global_request_scheduler_peek();
/* Try to pull and allocate from the global queue if earlier
* This will be placed at the head of the local runqueue */
if (global_remaining_exec < local_rem_exec) {
if (global_request_scheduler_remove_if_earlier(&global, local_rem_exec) == 0) {
assert(global != NULL);
assert(global->remaining_exec < local_rem_exec);
sandbox_prepare_execution_environment(global);
assert(global->state == SANDBOX_INITIALIZED);
sandbox_set_as_runnable(global, SANDBOX_INITIALIZED);
}
}
/* Return what is at the head of the local runqueue or NULL if empty */
return local_runqueue_get_next();
}
extern enum SCHEDULER scheduler;
static inline struct sandbox *
scheduler_edf_get_next()
{
/* Get the deadline of the sandbox at the head of the local queue */
/* Get the deadline of the sandbox at the head of the local request queue */
struct sandbox *local = local_runqueue_get_next();
uint64_t local_deadline = local == NULL ? UINT64_MAX : local->absolute_deadline;
struct sandbox *global = NULL;
@ -184,12 +122,6 @@ static inline struct sandbox *
scheduler_get_next()
{
switch (scheduler) {
case SCHEDULER_MTDBF:
return scheduler_mtdbf_get_next();
case SCHEDULER_MTDS:
return scheduler_mtds_get_next();
case SCHEDULER_SJF:
return scheduler_sjf_get_next();
case SCHEDULER_EDF:
return scheduler_edf_get_next();
case SCHEDULER_FIFO:
@ -203,14 +135,7 @@ static inline void
scheduler_initialize()
{
switch (scheduler) {
case SCHEDULER_MTDBF:
/* TODO: loading */
break;
case SCHEDULER_MTDS:
global_request_scheduler_mtds_initialize();
break;
case SCHEDULER_EDF:
case SCHEDULER_SJF:
global_request_scheduler_minheap_initialize();
break;
case SCHEDULER_FIFO:
@ -225,14 +150,7 @@ static inline void
scheduler_runqueue_initialize()
{
switch (scheduler) {
case SCHEDULER_MTDBF:
// local_runqueue_mtdbf_initialize();
break;
case SCHEDULER_MTDS:
local_runqueue_mtds_initialize();
break;
case SCHEDULER_EDF:
case SCHEDULER_SJF:
local_runqueue_minheap_initialize();
break;
case SCHEDULER_FIFO:
@ -251,12 +169,6 @@ scheduler_print(enum SCHEDULER variant)
return "FIFO";
case SCHEDULER_EDF:
return "EDF";
case SCHEDULER_SJF:
return "SJF";
case SCHEDULER_MTDS:
return "MTDS";
case SCHEDULER_MTDBF:
return "MTDBF";
}
}
@ -307,31 +219,6 @@ scheduler_preemptive_switch_to(ucontext_t *interrupted_context, struct sandbox *
}
}
/**
* Call either at preemptions or blockings to update the scheduler-specific
* properties for the given tenant.
*/
static inline void
scheduler_process_policy_specific_updates_on_interrupts(struct sandbox *interrupted_sandbox)
{
switch (scheduler) {
case SCHEDULER_FIFO:
return;
case SCHEDULER_EDF:
case SCHEDULER_SJF:
return;
case SCHEDULER_MTDS:
local_timeout_queue_process_promotions();
return;
case SCHEDULER_MTDBF:
// scheduler_check_messages_from_listener();
if (interrupted_sandbox->state != SANDBOX_ERROR) {
sandbox_process_scheduler_updates(interrupted_sandbox);
}
return;
}
}
/**
* Called by the SIGALRM handler after a quantum
* Assumes the caller validates that there is something to preempt
@ -344,12 +231,11 @@ scheduler_preemptive_sched(ucontext_t *interrupted_context)
assert(interrupted_context != NULL);
/* Process epoll to make sure that all runnable jobs are considered for execution */
scheduler_execute_epoll_loop();
struct sandbox *interrupted_sandbox = current_sandbox_get();
assert(interrupted_sandbox != NULL);
assert(interrupted_sandbox->state == SANDBOX_INTERRUPTED);
// printf ("Worker #%d interrupted sandbox #%lu\n", worker_thread_idx, interrupted_sandbox->id);
scheduler_process_policy_specific_updates_on_interrupts(interrupted_sandbox);
struct sandbox *next = scheduler_get_next();
/* Assumption: the current sandbox is on the runqueue, so the scheduler should always return something */
@ -431,29 +317,29 @@ scheduler_idle_loop()
/* Deferred signals should have been cleared by this point */
assert(deferred_sigalrm == 0);
/* Try to wakeup sleeping sandboxes */
scheduler_execute_epoll_loop();
/* Switch to a sandbox if one is ready to run */
struct sandbox *next_sandbox = scheduler_get_next();
if (next_sandbox != NULL) {
scheduler_cooperative_switch_to(&worker_thread_base_context, next_sandbox);
}
/* Clear the cleanup queue */
local_cleanup_queue_free();
/* Improve the performance of spin-wait loops (works only if preemptions enabled) */
if (runtime_worker_spinloop_pause_enabled) pause();
/* Clear the completion queue */
local_completion_queue_free();
}
}
/**
* @brief Used to cooperative switch sandboxes when a sandbox sleeps or exits
* Because of use-after-free bugs that interfere with our loggers, when a sandbox exits and switches away never to
* return, the boolean add_to_cleanup_queue needs to be set to true. Otherwise, we will leak sandboxes.
* @param add_to_cleanup_queue - Indicates that the sandbox should be added to the cleanup queue before switching
* return, the boolean add_to_completion_queue needs to be set to true. Otherwise, we will leak sandboxes.
* @param add_to_completion_queue - Indicates that the sandbox should be added to the completion queue before switching
* away
*/
static inline void
scheduler_cooperative_sched(bool add_to_cleanup_queue)
scheduler_cooperative_sched(bool add_to_completion_queue)
{
struct sandbox *exiting_sandbox = current_sandbox_get();
assert(exiting_sandbox != NULL);
@ -471,8 +357,11 @@ scheduler_cooperative_sched(bool add_to_cleanup_queue)
/* Deferred signals should have been cleared by this point */
assert(deferred_sigalrm == 0);
/* We have not added ourself to the cleanup queue, so we can free */
local_cleanup_queue_free();
/* Try to wakeup sleeping sandboxes */
scheduler_execute_epoll_loop();
/* We have not added ourself to the completion queue, so we can free */
local_completion_queue_free();
/* Switch to a sandbox if one is ready to run */
struct sandbox *next_sandbox = scheduler_get_next();
@ -489,7 +378,7 @@ scheduler_cooperative_sched(bool add_to_cleanup_queue)
// Write back global at idx 0
wasm_globals_set_i64(&exiting_sandbox->globals, 0, sledge_abi__current_wasm_module_instance.abi.wasmg_0, true);
if (add_to_cleanup_queue) local_cleanup_queue_add(exiting_sandbox);
if (add_to_completion_queue) local_completion_queue_add(exiting_sandbox);
/* Do not touch sandbox struct after this point! */
if (next_sandbox != NULL) {

@ -0,0 +1,76 @@
#pragma once
#include <assert.h>
#include <errno.h>
#include "client_socket.h"
#include "panic.h"
#include "runtime.h"
#include "sandbox_functions.h"
#include "sandbox_set_as_error.h"
#include "sandbox_set_as_runnable.h"
#include "sandbox_state.h"
#include "sandbox_types.h"
#include "worker_thread.h"
/**
* Run all outstanding events in the local thread's epoll loop
*/
static inline void
scheduler_execute_epoll_loop(void)
{
while (true) {
struct epoll_event epoll_events[RUNTIME_MAX_EPOLL_EVENTS];
int descriptor_count = epoll_wait(worker_thread_epoll_file_descriptor, epoll_events,
RUNTIME_MAX_EPOLL_EVENTS, 0);
if (descriptor_count < 0) {
if (errno == EINTR) continue;
panic_err();
}
if (descriptor_count == 0) break;
for (int i = 0; i < descriptor_count; i++) {
if (epoll_events[i].events & (EPOLLIN | EPOLLOUT)) {
/* Re-add to runqueue if asleep */
struct sandbox *sandbox = (struct sandbox *)epoll_events[i].data.ptr;
assert(sandbox);
if (sandbox->state == SANDBOX_ASLEEP) { sandbox_wakeup(sandbox); }
} else if (epoll_events[i].events & (EPOLLERR | EPOLLHUP)) {
/* Mystery: This seems to never fire. Why? Issue #130 */
/* Close socket and set as error on socket error or unexpected client hangup */
struct sandbox *sandbox = (struct sandbox *)epoll_events[i].data.ptr;
int error = 0;
socklen_t errlen = sizeof(error);
getsockopt(epoll_events[i].data.fd, SOL_SOCKET, SO_ERROR, (void *)&error, &errlen);
if (error > 0) {
debuglog("Socket error: %s", strerror(error));
} else if (epoll_events[i].events & EPOLLHUP) {
debuglog("Client Hungup");
} else {
debuglog("Unknown Socket error");
}
switch (sandbox->state) {
case SANDBOX_RETURNED:
case SANDBOX_COMPLETE:
case SANDBOX_ERROR:
panic("Expected to have closed socket");
default:
client_socket_send_oneshot(sandbox->client_socket_descriptor,
http_header_build(503), http_header_len(503));
sandbox_close_http(sandbox);
sandbox_set_as_error(sandbox, sandbox->state);
}
} else {
panic("Mystery epoll event!\n");
};
}
}
}

@ -1,12 +0,0 @@
#pragma once
enum SCHEDULER
{
SCHEDULER_FIFO,
SCHEDULER_EDF,
SCHEDULER_SJF,
SCHEDULER_MTDS,
SCHEDULER_MTDBF
};
extern enum SCHEDULER scheduler;

@ -5,8 +5,8 @@
#include <stdint.h>
#include "debuglog.h"
#include "sledge_abi.h"
#include "wasm_types.h"
#include "sledge_abi.h"
struct sledge_abi_symbols {
void *handle;

@ -31,7 +31,7 @@ software_interrupt_mask_signal(int signal)
sigset_t set;
int return_code;
assert(signal == SIGALRM || signal == SIGUSR1 || signal == SIGFPE || signal == SIGSEGV);
assert(signal == SIGALRM || signal == SIGUSR1);
/* all threads created by the calling thread will have signal blocked */
sigemptyset(&set);
sigaddset(&set, signal);
@ -55,7 +55,7 @@ software_interrupt_unmask_signal(int signal)
sigset_t set;
int return_code;
assert(signal == SIGALRM || signal == SIGUSR1 || signal == SIGFPE || signal == SIGSEGV);
assert(signal == SIGALRM || signal == SIGUSR1);
/* all threads created by the calling thread will have signal unblocked */
sigemptyset(&set);
sigaddset(&set, signal);

@ -1,7 +1,7 @@
#pragma once
#include <signal.h>
#include <stdatomic.h>
#include <signal.h>
#include <stdlib.h>
#include "worker_thread.h"
@ -10,8 +10,6 @@ extern _Atomic volatile sig_atomic_t *software_interrupt_counts_deferred_sigalrm
extern _Atomic volatile sig_atomic_t *software_interrupt_counts_deferred_sigalrm_replay;
extern _Atomic volatile sig_atomic_t *software_interrupt_counts_sigalrm_kernel;
extern _Atomic volatile sig_atomic_t *software_interrupt_counts_sigalrm_thread;
extern _Atomic volatile sig_atomic_t *software_interrupt_counts_sigfpe;
extern _Atomic volatile sig_atomic_t *software_interrupt_counts_sigsegv;
extern _Atomic volatile sig_atomic_t *software_interrupt_counts_sigusr;
static inline void
@ -22,8 +20,6 @@ software_interrupt_counts_alloc()
software_interrupt_counts_deferred_sigalrm_replay = calloc(runtime_worker_threads_count, sizeof(sig_atomic_t));
software_interrupt_counts_sigalrm_kernel = calloc(runtime_worker_threads_count, sizeof(sig_atomic_t));
software_interrupt_counts_sigalrm_thread = calloc(runtime_worker_threads_count, sizeof(sig_atomic_t));
software_interrupt_counts_sigfpe = calloc(runtime_worker_threads_count, sizeof(sig_atomic_t));
software_interrupt_counts_sigsegv = calloc(runtime_worker_threads_count, sizeof(sig_atomic_t));
software_interrupt_counts_sigusr = calloc(runtime_worker_threads_count, sizeof(sig_atomic_t));
#endif
}
@ -33,11 +29,8 @@ software_interrupt_counts_free()
{
#ifdef LOG_SOFTWARE_INTERRUPT_COUNTS
free((void *)software_interrupt_counts_deferred_sigalrm_max);
free((void *)software_interrupt_counts_deferred_sigalrm_replay);
free((void *)software_interrupt_counts_sigalrm_kernel);
free((void *)software_interrupt_counts_sigalrm_thread);
free((void *)software_interrupt_counts_sigfpe);
free((void *)software_interrupt_counts_sigsegv);
free((void *)software_interrupt_counts_sigusr);
#endif
}
@ -76,22 +69,6 @@ software_interrupt_counts_sigalrm_thread_increment()
#endif
}
static inline void
software_interrupt_counts_sigfpe_increment()
{
#ifdef LOG_SOFTWARE_INTERRUPT_COUNTS
atomic_fetch_add(&software_interrupt_counts_sigfpe[worker_thread_idx], 1);
#endif
}
static inline void
software_interrupt_counts_sigsegv_increment()
{
#ifdef LOG_SOFTWARE_INTERRUPT_COUNTS
atomic_fetch_add(&software_interrupt_counts_sigsegv[worker_thread_idx], 1);
#endif
}
static inline void
software_interrupt_counts_sigusr_increment()
{

@ -1,102 +0,0 @@
#pragma once
#include <assert.h>
#include <errno.h>
#include <netdb.h>
#include <stdint.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include "debuglog.h"
#include "likely.h"
/*
* Defines the listen backlog, the queue length for completely established socketeds waiting to be accepted
* If this value is greater than the value in /proc/sys/net/core/somaxconn (typically 128), then it is silently
* truncated to this value. See man listen(2) for info
*
* When configuring the number of sockets to handle, the queue length of incomplete sockets defined in
* /proc/sys/net/ipv4/tcp_max_syn_backlog should also be considered. Optionally, enabling syncookies removes this
* maximum logical length. See tcp(7) for more info.
*/
#define TCP_SERVER_MAX_PENDING_CLIENT_REQUESTS 128
#if TCP_SERVER_MAX_PENDING_CLIENT_REQUESTS > 128
#warning \
"TCP_SERVER_MAX_PENDING_CLIENT_REQUESTS likely exceeds the value in /proc/sys/net/core/somaxconn and thus may be silently truncated";
#endif
/* L4 TCP State */
struct tcp_server {
uint16_t port;
struct sockaddr_in socket_address;
int socket_descriptor;
};
static inline void
tcp_server_init(struct tcp_server *server, uint16_t port)
{
server->port = port;
server->socket_descriptor = -1;
}
/**
* Start the module as a server listening at module->port
* @param module
* @returns 0 on success, -1 on error
*/
static inline int
tcp_server_listen(struct tcp_server *server)
{
int rc;
int optval = 1;
/* Allocate a new TCP/IP socket, setting it to be non-blocking */
int socket_descriptor = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (unlikely(socket_descriptor < 0)) goto err_create_socket;
/* Socket should never have returned on fd 0, 1, or 2 */
assert(socket_descriptor != STDIN_FILENO);
assert(socket_descriptor != STDOUT_FILENO);
assert(socket_descriptor != STDERR_FILENO);
/* Configure the socket to allow multiple sockets to bind to the same host and port */
rc = setsockopt(socket_descriptor, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
if (unlikely(rc < 0)) goto err_set_socket_option;
optval = 1;
rc = setsockopt(socket_descriptor, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
if (unlikely(rc < 0)) goto err_set_socket_option;
/* Bind name [all addresses]:[module->port] to socket */
server->socket_descriptor = socket_descriptor;
server->socket_address.sin_family = AF_INET;
server->socket_address.sin_addr.s_addr = htonl(INADDR_ANY);
server->socket_address.sin_port = htons((unsigned short)server->port);
rc = bind(socket_descriptor, (struct sockaddr *)&server->socket_address, sizeof(server->socket_address));
if (unlikely(rc < 0)) goto err_bind_socket;
/* Listen to the interface */
rc = listen(socket_descriptor, TCP_SERVER_MAX_PENDING_CLIENT_REQUESTS);
if (unlikely(rc < 0)) goto err_listen;
rc = 0;
done:
return rc;
err_listen:
err_bind_socket:
server->socket_descriptor = -1;
err_set_socket_option:
close(socket_descriptor);
err_create_socket:
err:
debuglog("Socket Error: %s", strerror(errno));
rc = -1;
goto done;
}
static inline int
tcp_server_close(struct tcp_server *server)
{
return close(server->socket_descriptor);
}

@ -1,86 +0,0 @@
#pragma once
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "debuglog.h"
#include "likely.h"
#include "panic.h"
static inline void
tcp_session_close(int client_socket, struct sockaddr *client_address)
{
/* Should never close 0, 1, or 2 */
assert(client_socket != STDIN_FILENO);
assert(client_socket != STDOUT_FILENO);
assert(client_socket != STDERR_FILENO);
if (unlikely(close(client_socket) < 0)) {
char client_address_text[INET6_ADDRSTRLEN] = {'\0'};
if (unlikely(inet_ntop(AF_INET, &client_address, client_address_text, INET6_ADDRSTRLEN) == NULL)) {
debuglog("Failed to log client_address: %s", strerror(errno));
}
debuglog("Error closing client socket %d associated with %s - %s", client_socket, client_address_text,
strerror(errno));
}
}
typedef void (*void_star_cb)(void *);
/**
* Writes buffer to the client socket
* @param client_socket - the client
* @param buffer - buffer to write to socket
* @param on_eagain - cb to execute when client socket returns EAGAIN. If NULL, error out
* @returns nwritten on success, -errno, -EAGAIN on block
*/
static inline ssize_t
tcp_session_send(int client_socket, const char *buffer, size_t buffer_len, void_star_cb on_eagain, void *dataptr)
{
assert(buffer != NULL);
assert(buffer_len > 0);
ssize_t sent = write(client_socket, buffer, buffer_len);
if (sent < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (on_eagain != NULL) on_eagain(dataptr);
return -EAGAIN;
} else {
return -errno;
}
}
return sent;
}
/**
* Writes buffer to the client socket
* @param client_socket - the client
* @param buffer - buffer to reach the socket into
* @param buffer_len - buffer to reach the socket into
* @param on_eagain - cb to execute when client socket returns EAGAIN. If NULL, error out
* @returns nwritten on success, -errno on error, -eagain on block
*/
static inline ssize_t
tcp_session_recv(int client_socket, char *buffer, size_t buffer_len, void_star_cb on_eagain, void *dataptr)
{
assert(buffer != NULL);
assert(buffer_len > 0);
ssize_t received = read(client_socket, buffer, buffer_len);
if (received < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
if (on_eagain != NULL) on_eagain(dataptr);
return -EAGAIN;
} else {
return -errno;
}
}
return received;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save