From de22768cd82187811730dffc4b629e9f2387ad95 Mon Sep 17 00:00:00 2001 From: BennyFranciscus <268274351+BennyFranciscus@users.noreply.github.com> Date: Sat, 28 Mar 2026 23:16:24 +0000 Subject: [PATCH 1/3] Add libreactor (C) engine entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libreactor v3.0.0 — high-performance C event-driven HTTP library by Fredrik Widlund. Uses epoll + picohttpparser for minimal-overhead HTTP handling. One of the top performers on TechEmpower benchmarks. Engine entry with baseline, pipelined, and limited-conn tests. - Single-threaded epoll event loop via libreactor's reactor core - HTTP parsing via bundled picohttpparser - Built from source with -O3 -march=native -flto - Handles GET/POST with query params, Content-Length and chunked body - All 7 validation checks pass --- frameworks/libreactor/Dockerfile | 31 +++++++ frameworks/libreactor/README.md | 25 ++++++ frameworks/libreactor/meta.json | 14 +++ frameworks/libreactor/server.c | 150 +++++++++++++++++++++++++++++++ 4 files changed, 220 insertions(+) create mode 100644 frameworks/libreactor/Dockerfile create mode 100644 frameworks/libreactor/README.md create mode 100644 frameworks/libreactor/meta.json create mode 100644 frameworks/libreactor/server.c diff --git a/frameworks/libreactor/Dockerfile b/frameworks/libreactor/Dockerfile new file mode 100644 index 00000000..3d3308f2 --- /dev/null +++ b/frameworks/libreactor/Dockerfile @@ -0,0 +1,31 @@ +FROM ubuntu:24.04 AS build + +RUN apt-get update && apt-get install -y \ + gcc make autoconf automake libtool pkg-config \ + libssl-dev git ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Build libreactor from source (v3.0.0) +RUN git clone --depth 1 https://github.com/fredrikwidlund/libreactor.git /tmp/libreactor \ + && cd /tmp/libreactor \ + && ./autogen.sh \ + && ./configure \ + && make -j$(nproc) \ + && make install \ + && ldconfig \ + && rm -rf /tmp/libreactor + +WORKDIR /app +COPY server.c . + +RUN gcc -O3 -march=native -flto -o server server.c \ + $(pkg-config --cflags --libs libreactor) -lssl -lcrypto + +FROM ubuntu:24.04 +RUN apt-get update && apt-get install -y libssl3t64 && rm -rf /var/lib/apt/lists/* +COPY --from=build /usr/lib/libreactor* /usr/lib/ +COPY --from=build /app/server /server +RUN ldconfig + +EXPOSE 8080 +CMD ["/server"] diff --git a/frameworks/libreactor/README.md b/frameworks/libreactor/README.md new file mode 100644 index 00000000..325ae809 --- /dev/null +++ b/frameworks/libreactor/README.md @@ -0,0 +1,25 @@ +# libreactor + +[libreactor](https://github.com/fredrikwidlund/libreactor) is a high-performance C event-driven library built around epoll with zero-copy patterns and picohttpparser for HTTP parsing. One of the top performers on TechEmpower benchmarks. + +## Implementation + +- **Engine entry** — raw C HTTP handling, no framework abstractions +- **Event loop**: epoll via libreactor's reactor core +- **HTTP parsing**: picohttpparser (bundled in libreactor) +- **Compiler flags**: `-O3 -march=native -flto` + +## Endpoints + +| Endpoint | Method | Description | +|---|---|---| +| `/baseline11` | GET | Sum query params `a` + `b` | +| `/baseline11` | POST | Sum query params `a` + `b` + body (Content-Length or chunked) | +| `/pipeline` | GET | Returns `ok` as `text/plain` | + +## Build + +```bash +docker build -t httparena-libreactor frameworks/libreactor +docker run -p 8080:8080 httparena-libreactor +``` diff --git a/frameworks/libreactor/meta.json b/frameworks/libreactor/meta.json new file mode 100644 index 00000000..52dbc6e8 --- /dev/null +++ b/frameworks/libreactor/meta.json @@ -0,0 +1,14 @@ +{ + "display_name": "libreactor", + "language": "C", + "type": "engine", + "engine": "epoll", + "description": "High-performance C event-driven HTTP library using epoll and picohttpparser. Top performer on TechEmpower benchmarks.", + "repo": "https://github.com/fredrikwidlund/libreactor", + "enabled": true, + "tests": [ + "baseline", + "pipelined", + "limited-conn" + ] +} diff --git a/frameworks/libreactor/server.c b/frameworks/libreactor/server.c new file mode 100644 index 00000000..0c050474 --- /dev/null +++ b/frameworks/libreactor/server.c @@ -0,0 +1,150 @@ +#include +#include +#include +#include +#include + +#include + +/* ── helpers ─────────────────────────────────────────────────────── */ + +static int parse_int(const char *s, int len) +{ + int n = 0; + for (int i = 0; i < len; i++) + { + if (s[i] < '0' || s[i] > '9') + break; + n = n * 10 + (s[i] - '0'); + } + return n; +} + +static int sum_query_params(const char *qs, int qs_len) +{ + int sum = 0; + const char *end = qs + qs_len; + const char *p = qs; + + while (p < end) + { + const char *eq = memchr(p, '=', end - p); + if (!eq) + break; + eq++; + const char *amp = memchr(eq, '&', end - eq); + int vlen = amp ? (int)(amp - eq) : (int)(end - eq); + sum += parse_int(eq, vlen); + p = amp ? amp + 1 : end; + } + return sum; +} + +/* ── request handler ─────────────────────────────────────────────── */ + +static void callback(reactor_event *event) +{ + server_request *request = (server_request *) event->data; + const char *target = data_base(request->target); + size_t target_size = data_size(request->target); + const char *method = data_base(request->method); + size_t method_size = data_size(request->method); + + /* GET /pipeline — fast path */ + if (target_size == 9 && memcmp(target, "/pipeline", 9) == 0) + { + server_ok(request, data_string("text/plain"), data_string("ok")); + return; + } + + /* /baseline11 — must start with /baseline11 */ + if (target_size >= 11 && memcmp(target, "/baseline11", 11) == 0) + { + int sum = 0; + + /* parse query string */ + const char *qmark = memchr(target, '?', target_size); + if (qmark) + { + int qs_len = (int)(target_size - (qmark + 1 - target)); + sum = sum_query_params(qmark + 1, qs_len); + } + + /* parse body for POST */ + if (method_size == 4 && memcmp(method, "POST", 4) == 0) + { + data content_length_val = http_field_lookup( + request->fields, request->fields_count, + data_string("Content-Length")); + data transfer_encoding_val = http_field_lookup( + request->fields, request->fields_count, + data_string("Transfer-Encoding")); + + if (!data_empty(transfer_encoding_val) && + memmem(data_base(transfer_encoding_val), + data_size(transfer_encoding_val), "chunked", 7)) + { + /* chunked: skip chunk size line, parse chunk data */ + data body = request->target; /* placeholder — we need raw body */ + /* For chunked encoding, the body after headers contains: + \r\n\r\n0\r\n\r\n + We need to find it in the stream data */ + data req_data = request->data; + const char *body_base = data_base(req_data); + size_t body_size = data_size(req_data); + if (body_size > 0) + { + /* Find chunk data: skip the chunk size line */ + const char *crlf = memmem(body_base, body_size, "\r\n", 2); + if (crlf) + { + const char *chunk_data = crlf + 2; + size_t remaining = body_size - (chunk_data - body_base); + const char *chunk_end = memmem(chunk_data, remaining, "\r\n", 2); + int chunk_len = chunk_end ? (int)(chunk_end - chunk_data) : (int)remaining; + if (chunk_len > 0) + sum += parse_int(chunk_data, chunk_len); + } + } + } + else if (!data_empty(content_length_val)) + { + /* Content-Length body */ + int cl = parse_int(data_base(content_length_val), + (int)data_size(content_length_val)); + data req_data = request->data; + const char *body_base = data_base(req_data); + size_t body_size = data_size(req_data); + if (cl > 0 && body_size > 0) + { + int blen = cl < (int)body_size ? cl : (int)body_size; + sum += parse_int(body_base, blen); + } + } + } + + char body_buf[16]; + int body_len = snprintf(body_buf, sizeof(body_buf), "%d", sum); + server_ok(request, data_string("text/plain"), + data_construct(body_buf, body_len)); + return; + } + + server_not_found(request); +} + +int main(void) +{ + server s; + + reactor_construct(); + server_construct(&s, callback, &s); + server_open(&s, + net_socket(net_resolve("0.0.0.0", "8080", AF_INET, SOCK_STREAM, AI_PASSIVE)), + NULL); + fprintf(stderr, "libreactor listening on :8080\n"); + reactor_loop(); + server_destruct(&s); + reactor_destruct(); + return 0; +} From f1e31a1600d6f91c360989196c25f4be5801640d Mon Sep 17 00:00:00 2001 From: BennyFranciscus <268274351+BennyFranciscus@users.noreply.github.com> Date: Sat, 28 Mar 2026 23:29:40 +0000 Subject: [PATCH 2/3] fix: fork workers per-CPU with core affinity libreactor was running a single reactor loop on one core (~100% CPU). Now forks one worker per available CPU, each pinned to its own core via sched_setaffinity. libreactor's net_socket already sets SO_REUSEPORT, so each child binds independently. --- frameworks/libreactor/server.c | 36 +++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/frameworks/libreactor/server.c b/frameworks/libreactor/server.c index 0c050474..83cf9068 100644 --- a/frameworks/libreactor/server.c +++ b/frameworks/libreactor/server.c @@ -1,8 +1,12 @@ +#define _GNU_SOURCE #include #include #include #include #include +#include +#include +#include #include @@ -133,18 +137,44 @@ static void callback(reactor_event *event) server_not_found(request); } -int main(void) +static void run_worker(int cpu) { - server s; + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(cpu, &cpuset); + sched_setaffinity(0, sizeof(cpuset), &cpuset); + server s; reactor_construct(); server_construct(&s, callback, &s); server_open(&s, net_socket(net_resolve("0.0.0.0", "8080", AF_INET, SOCK_STREAM, AI_PASSIVE)), NULL); - fprintf(stderr, "libreactor listening on :8080\n"); reactor_loop(); server_destruct(&s); reactor_destruct(); +} + +int main(void) +{ + int cpus = sysconf(_SC_NPROCESSORS_ONLN); + if (cpus < 1) cpus = 1; + + fprintf(stderr, "libreactor: spawning %d workers on :8080\n", cpus); + + for (int i = 1; i < cpus; i++) + { + pid_t pid = fork(); + if (pid == 0) + { + run_worker(i); + _exit(0); + } + } + + run_worker(0); + + /* wait for children (shouldn't reach here) */ + while (wait(NULL) > 0); return 0; } From d7e1d19d74ee9debfaec32064d84c118f778e48d Mon Sep 17 00:00:00 2001 From: BennyFranciscus <268274351+BennyFranciscus@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:04:43 +0000 Subject: [PATCH 3/3] fix: hardcode 64 workers, remove CPU pinning Per MDA2AV's request: set worker count to exactly 64 and remove sched_setaffinity CPU pinning. --- frameworks/libreactor/server.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/frameworks/libreactor/server.c b/frameworks/libreactor/server.c index 83cf9068..90043891 100644 --- a/frameworks/libreactor/server.c +++ b/frameworks/libreactor/server.c @@ -1,11 +1,9 @@ -#define _GNU_SOURCE #include #include #include #include #include #include -#include #include #include @@ -137,13 +135,8 @@ static void callback(reactor_event *event) server_not_found(request); } -static void run_worker(int cpu) +static void run_worker(void) { - cpu_set_t cpuset; - CPU_ZERO(&cpuset); - CPU_SET(cpu, &cpuset); - sched_setaffinity(0, sizeof(cpuset), &cpuset); - server s; reactor_construct(); server_construct(&s, callback, &s); @@ -157,8 +150,7 @@ static void run_worker(int cpu) int main(void) { - int cpus = sysconf(_SC_NPROCESSORS_ONLN); - if (cpus < 1) cpus = 1; + int cpus = 64; fprintf(stderr, "libreactor: spawning %d workers on :8080\n", cpus); @@ -167,12 +159,12 @@ int main(void) pid_t pid = fork(); if (pid == 0) { - run_worker(i); + run_worker(); _exit(0); } } - run_worker(0); + run_worker(); /* wait for children (shouldn't reach here) */ while (wait(NULL) > 0);