Folkert de Vries, SYCL 2024
written in Rust
Proof of Concept
roc platform
web server
systems programming
🔁
in production
you pay for resources
locally
resource are infinite
🌄
💸
running out of memory is bad for your users
"have you tried turning it off and on again" is not great ux
you are at the mercy of your runtime
haskell/java/js/ruby/... runtimes are mediocre at everything.
heuristics
not guarantees
hard to find optimal settings
⚖️
get a firm upper bound on the amount of memory used by our webserver
🧠
a memory-hungry request should not impact any other request
pub fn reserve(
&mut self, additional: usize
);
pub fn try_reserve(
&mut self, additional: usize,
) -> Result<(), TryReserveError>;
in its lane, flourishing
the malloc that broke the cammel's back
✖
impossible to track down problematic requests
refcounting, so no GC pauses
roc is (relatively) fast
🚀
♻️
using mmap
(or similar)
don't want to recompile
get a firm upper bound on the amount of memory used by our webserver
our memory consumption is always maximal
⛰️
a memory-hungry request should not impact other requests
idea: each request gets its own bump arena
the arena is cleared when a request is done
we now run into a hard limit: there needs to be a bucket available in order to serve a request.
🪣
but we can do MATH!
bucket size is fixed: memory is used most effectively if most requests do a roughly equal amount of allocating
⚖️
↩️
setjmp/longjmp
back up the stack
send error 500
log request
#include < setjmp.h >
void main() {
jmp_buf env;
int i;
i = setjmp(env);
printf("i = %d\n", i);
if (i != 0) { exit(0) };
longjmp(env, 2);
printf("Never gets here\n");
}
setjmp:
mov [rdi], rbx ; // Store caller saved registers
mov [rdi+8], rbp ; // ^
mov [rdi+16], r12 ; // ^
mov [rdi+24], r13 ; // ^
mov [rdi+32], r14 ; // ^
mov [rdi+40], r15 ; // ^
lea rdx, [rsp+8] ; // go one value up (as if setjmp wasn't called)
mov [rdi+48], rdx ; // Store the new rsp pointer in env[7]
mov rdx, [rsp] ; // go one value up (as if setjmp wasn't called)
mov [rdi+56], rdx ; // Store the address we will resume at in env[8]
xor eax, eax ; // Always return 0
ret
longjmp:
xor eax, eax ; // set eax to 0
cmp esi, 1 ; // CF = val ? 0 : 1
adc eax, esi ; // eax = val + !val
mov rbx, [rdi] ; // Load in caller saved registers
mov rbp, [rdi+8] ; // ^
mov r12, [rdi+16] ; // ^
mov r13, [rdi+24] ; // ^
mov r14, [rdi+32] ; // ^
mov r15, [rdi+40] ; // ^
mov rsp, [rdi+48] ; // Value of rsp before setjmp call
jmp [rdi+56] ; // goto saved address without altering rsp
loop {
work = queue.pop().await?;
match setjmp(&mut jmp_buf) {
0 => {
// snapshot has been made
call_roc(work)
}
_ => {
// allocation failed
todo!()
}
}
}
fn roc_alloc() -> *mut c_void {
let ptr = ...;
if ptr.is_null() {
longjmp(&jmp_buf, 1)
} else {
ptr
}
}
setjmp/longjmp are so unsafe, even an unsafe block cannot contain them
#include < setjmp.h >
void main() {
jmp_buf env;
int i;
i = setjmp(env);
printf("i = %d\n", i);
if (i != 0) { exit(0) };
longjmp(env, 2);
printf("Never gets here\n");
}
async IO makes sense when for a webserver
🧵
async fn foo() {
// ...
tcp_stream.flush().await;
}
rust has no idea how to actually run this
roc platforms combine the convenient and performant
custom allocators are a superpower
hard bounds mean we can do MATH!
NEver Allocate!
github: folkertdev
@folkertdev@hachyderm.io
> Array(8).join("wat" - 1) + " Batman!";
> Array(8).join("wat" - 1) + " Batman!";
< 'NaNNaNNaNNaNNaNNaNNaN Batman!'
Inductive Vector (A : Type) : nat -> Type :=
| VNil : Vector A 0
| VCons : forall (n : nat), A -> Vector A n -> Vector A (S n).