Browse Source

Libreactor: Add optimized platform implementation as new default (#5681)

* Rename current implementation to libreactor-server

* Add an optimized default implementation

This one is labled as a "platform" implementation

* Add gcc -g flag to libreactor-server.dockerfile

Adds debug symbols without affecting performance. Needed for profiling.
Marc Richards 5 years ago
parent
commit
a890e01260

+ 2 - 3
frameworks/C/libreactor/Makefile

@@ -1,7 +1,6 @@
 PROG    = libreactor
-OBJS    = src/setup.o src/main.o
-CFLAGS  = -std=gnu11 -Wall -Wextra -Wpedantic -O3
-LDFLAGS = -pthread
+OBJS    = src/setup.o src/helpers.o src/main.o
+CFLAGS  = -std=gnu11 -Wall -Wextra -Wpedantic -O3 -g
 LDADD   = -lreactor -ldynamic -lclo -flto
 
 $(PROG): $(OBJS)

+ 11 - 0
frameworks/C/libreactor/Makefile-server

@@ -0,0 +1,11 @@
+PROG    = libreactor
+OBJS    = src/setup.o src/main.o
+CFLAGS  = -std=gnu11 -Wall -Wextra -Wpedantic -O3 -g
+LDFLAGS = -pthread
+LDADD   = -lreactor -ldynamic -lclo -flto
+
+$(PROG): $(OBJS)
+	$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) $(LDADD)
+
+clean:
+	rm -f $(PROG) $(OBJS)

+ 22 - 1
frameworks/C/libreactor/benchmark_config.json

@@ -7,7 +7,7 @@
         "plaintext_url": "/plaintext",
         "port": 8080,
         "approach": "Realistic",
-        "classification": "Micro",
+        "classification": "Platform",
         "database": "None",
         "framework": "libreactor",
         "language": "C",
@@ -21,6 +21,27 @@
         "notes": "",
         "versus": "None"
       }
+    },
+    {
+      "server": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "None",
+        "framework": "libreactor",
+        "language": "C",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "libreactor",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "libreactor",
+        "notes": "",
+        "versus": "None"
+      }
     }
   ]
 }

+ 46 - 0
frameworks/C/libreactor/libreactor-server.dockerfile

@@ -0,0 +1,46 @@
+FROM ubuntu:18.04 as builder
+
+RUN apt-get update -yqq
+RUN apt-get install -yqq wget make automake libtool file gcc-8 g++-8
+
+WORKDIR /libreactor
+
+ENV CC=gcc-8 AR=gcc-ar-8 NM=gcc-nm-8 RANLIB=gcc-ranlib-8
+
+RUN wget -q https://github.com/akheron/jansson/archive/v2.12.tar.gz -O jansson-2.12.tar.gz && \
+    tar xfz jansson-2.12.tar.gz && \
+    cd jansson-2.12 && \
+    autoreconf -fi && \
+    ./configure && \
+    make install
+
+RUN wget -q https://github.com/fredrikwidlund/libdynamic/releases/download/v1.3.0/libdynamic-1.3.0.tar.gz && \
+    tar xfz libdynamic-1.3.0.tar.gz && \
+    cd libdynamic-1.3.0 && \
+    ./configure --prefix=/usr && \
+    make install
+
+RUN wget -q https://github.com/fredrikwidlund/libclo/releases/download/v1.0.0/libclo-1.0.0.tar.gz && \
+    tar xfz libclo-1.0.0.tar.gz && \
+    cd libclo-1.0.0 && \
+    ./configure && \
+    make install
+
+RUN wget -q https://github.com/fredrikwidlund/libreactor/releases/download/v1.0.1/libreactor-1.0.1.tar.gz && \
+    tar xfz libreactor-1.0.1.tar.gz && \
+    cd libreactor-1.0.1 && \
+    ./configure --prefix=/usr CFLAGS="-Wall -Wextra -Wpedantic -O3 -g" && \
+    make install
+
+COPY src-server/ /libreactor/src/
+COPY Makefile-server /libreactor/Makefile
+
+RUN make
+
+
+FROM ubuntu:18.04
+
+WORKDIR /libreactor
+COPY --from=builder /libreactor .
+
+CMD ["./libreactor"]

+ 1 - 1
frameworks/C/libreactor/libreactor.dockerfile

@@ -29,7 +29,7 @@ RUN wget -q https://github.com/fredrikwidlund/libclo/releases/download/v1.0.0/li
 RUN wget -q https://github.com/fredrikwidlund/libreactor/releases/download/v1.0.1/libreactor-1.0.1.tar.gz && \
     tar xfz libreactor-1.0.1.tar.gz && \
     cd libreactor-1.0.1 && \
-    ./configure --prefix=/usr CFLAGS="-Wall -Wextra -Wpedantic -O3" && \
+    ./configure --prefix=/usr CFLAGS="-Wall -Wextra -Wpedantic -O3 -g" && \
     make install
 
 COPY src/ /libreactor/src/

+ 47 - 0
frameworks/C/libreactor/src-server/main.c

@@ -0,0 +1,47 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <string.h>
+#include <err.h>
+
+#include <dynamic.h>
+#include <reactor.h>
+#include <clo.h>
+
+#include "setup.h"
+
+static reactor_status tfb(reactor_event *event)
+{
+  reactor_server_session *session = (reactor_server_session *) event->data;
+
+  if (reactor_vector_equal(session->request->target, reactor_vector_string("/json"))) {
+    char json_msg[32];
+    (void) clo_encode((clo[]) {clo_object({"message", clo_string("Hello, World!")})}, json_msg, sizeof(json_msg));
+    reactor_server_ok(session, reactor_vector_string("application/json"), reactor_vector_string(json_msg));
+    return REACTOR_OK;
+  }
+  else if (reactor_vector_equal(session->request->target, reactor_vector_string("/plaintext"))) {
+    reactor_server_ok(session, reactor_vector_string("text/plain"), reactor_vector_string("Hello, World!"));
+    return REACTOR_OK;
+  }
+  else {
+    reactor_server_ok(session, reactor_vector_string("text/plain"), reactor_vector_string("Hello from libreactor!\n"));
+    return REACTOR_OK;
+  }
+}
+
+
+int main()
+{
+  reactor_server server;
+
+  setup();
+  reactor_construct();
+  reactor_server_construct(&server, NULL, NULL);
+  reactor_server_route(&server, tfb, NULL);
+  (void) reactor_server_open(&server, "0.0.0.0", "8080");
+
+  reactor_run();
+  reactor_destruct();
+}

+ 88 - 0
frameworks/C/libreactor/src-server/setup.c

@@ -0,0 +1,88 @@
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <err.h>
+
+#include <dynamic.h>
+
+struct cpu
+{
+  int       id;
+  pid_t     pid;
+  cpu_set_t set;
+};
+
+static void cpu_list(vector *list, int use_all_cores)
+{
+  struct cpu cpu;
+  char path[1024], buf[1024];
+  size_t i, id;
+  ssize_t n;
+  int fd;
+
+  vector_construct(list, sizeof cpu);
+  for (i = 0;; i ++)
+    {
+      snprintf(path, sizeof path, "/sys/devices/system/cpu/cpu%lu/topology/thread_siblings_list", i);
+      fd = open(path, O_RDONLY);
+      if (fd == -1)
+        break;
+      n = read(fd, buf, sizeof buf - 1);
+      if (n == -1)
+        err(1, "read");
+      buf[n] = 0;
+      close(fd);
+
+      id = strtoul(buf, NULL, 0);
+
+      if (use_all_cores){
+        id = i; /* add every cpu (whether physical or logical) to the list */
+      }
+      else if (id != i){
+        continue; /* ignore sibling CPUs */
+      }
+
+      cpu = (struct cpu) {0};
+      cpu.id = id;
+      CPU_ZERO(&cpu.set);
+      CPU_SET(id, &cpu.set);
+      vector_push_back(list, &cpu);
+    }
+}
+
+void setup()
+{
+  vector list;
+  struct cpu *cpu;
+  size_t i;
+  int e;
+  pid_t cpid;
+
+  signal(SIGPIPE, SIG_IGN);
+  cpu_list(&list, 1);
+
+  for (i = 0; i < vector_size(&list); i++)
+    {
+      cpu = vector_at(&list, i);
+      cpid = fork();
+      if (cpid == -1)
+        err(1, "fork");
+      if (cpid == 0)
+        {
+          e = sched_setaffinity(0, sizeof cpu->set, &cpu->set);
+          if (e == -1)
+            err(1, "sched_setaffinity");
+          return;
+        }
+      cpu->pid = cpid;
+    }
+  wait(NULL);
+  vector_destruct(&list, NULL);
+}

+ 6 - 0
frameworks/C/libreactor/src-server/setup.h

@@ -0,0 +1,6 @@
+#ifndef SETUP_H_INCLUDED
+#define SETUP_H_INCLUDED
+
+void setup();
+
+#endif /* SETUP_H_INCLUDED */

+ 102 - 0
frameworks/C/libreactor/src/helpers.c

@@ -0,0 +1,102 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <time.h>
+#include <err.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/epoll.h>
+
+#include <dynamic.h>
+#include <reactor.h>
+#include <clo.h>
+
+static char date_header[] = "Date: Thu, 01 Jan 1970 00:00:00 GMT\r\n";
+static size_t date_header_size = sizeof(date_header) - 1;
+
+
+// Updates the date string
+void update_date()
+{
+  static const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+  static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+
+  time_t t;
+  struct tm tm;
+
+  (void) time(&t);
+  (void) gmtime_r(&t, &tm);
+  (void) strftime(date_header, date_header_size, "Date: ---, %d --- %Y %H:%M:%S GMT\r\n", &tm);
+  memcpy(date_header + 6, days[tm.tm_wday], 3);
+  memcpy(date_header + 14, months[tm.tm_mon], 3);
+}
+
+// Copies the date string to the preamble
+void copy_date(char *target, size_t position)
+{
+  memcpy(target + position, date_header, date_header_size);
+}
+
+// Custom version of reactor_net_bind that doesn't use reactor_resolver/reactor_pool to resolve the address
+// We just call getaddrinfo here directly instead. It is called at start we don't need to worry about it blocking
+// By not using reactor_net_bind we can avoid reactor_pool altogether and compile without -pthread. This seems to
+// provide some additional performance
+reactor_status custom_reactor_net_bind(reactor_net *net, char *node, char *service)
+{
+  int e, fileno;
+  struct addrinfo *ai;
+  struct addrinfo hints;
+
+  if (reactor_fd_active(&net->fd))
+    return REACTOR_ERROR;
+
+  net->options |= REACTOR_NET_OPTION_PASSIVE;
+
+  hints = (struct addrinfo) {.ai_family = 0, .ai_socktype = SOCK_STREAM, .ai_flags = AI_PASSIVE};
+  (void) getaddrinfo(node, service, &hints, &ai);
+
+  if (!ai)
+    err(1, "error resolving address");
+
+  fileno = socket(ai->ai_family, SOCK_STREAM | SOCK_NONBLOCK, 0);
+  reactor_assert_int_not_equal(fileno, -1);
+
+  if (net->options & REACTOR_NET_OPTION_PASSIVE)
+    {
+      if (net->options & REACTOR_NET_OPTION_REUSEPORT)
+        (void) setsockopt(fileno, SOL_SOCKET, SO_REUSEPORT, (int[]){1}, sizeof(int));
+      if (net->options & REACTOR_NET_OPTION_REUSEADDR)
+        (void) setsockopt(fileno, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));
+
+      e = bind(fileno, ai->ai_addr, ai->ai_addrlen);
+      if (e == -1)
+        {
+          (void) close(fileno);
+          err(1, "error binding socket");
+        }
+
+      e = listen(fileno, INT_MAX);
+      reactor_assert_int_not_equal(e, -1);
+      reactor_fd_open(&net->fd, fileno, EPOLLIN | EPOLLET);
+    }
+  else
+    {
+     e = connect(fileno, ai->ai_addr, ai->ai_addrlen);
+      if (e == -1 && errno != EINPROGRESS)
+        {
+          (void) close(fileno);
+          err(1, "error connecting socket");
+        }
+
+      reactor_fd_open(&net->fd, fileno, EPOLLOUT | EPOLLET);
+    }
+
+  freeaddrinfo(ai);
+
+  return REACTOR_OK;
+}

+ 10 - 0
frameworks/C/libreactor/src/helpers.h

@@ -0,0 +1,10 @@
+#ifndef HELPERS_H_INCLUDED
+#define HELPERS_H_INCLUDED
+
+void update_date();
+
+void copy_date(char *target, size_t position);
+
+reactor_status custom_reactor_net_bind(reactor_net *net, char *node, char *service);
+
+#endif /* HELPERS_H_INCLUDED */

+ 117 - 20
frameworks/C/libreactor/src/main.c

@@ -1,8 +1,9 @@
 #include <stdio.h>
-#include <stdlib.h>
 #include <stdint.h>
+#include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
+#include <time.h>
 #include <err.h>
 
 #include <dynamic.h>
@@ -10,38 +11,134 @@
 #include <clo.h>
 
 #include "setup.h"
+#include "helpers.h"
+
+enum response_type
+{
+ JSON = 0,
+ PLAINTEXT = 1
+};
+typedef enum response_type response_type;
 
-static reactor_status tfb(reactor_event *event)
+struct http_server
 {
-  reactor_server_session *session = (reactor_server_session *) event->data;
+  reactor_net    net;
+  list           connections;
+};
+
+static char json_preamble[] = "HTTP/1.1 200 OK\r\n"
+                              "Server: libreactor\r\n"
+                              "Content-Type: application/json\r\n"
+                              "Date: Thu, 01 Jan 1970 00:00:00 GMT\r\n"
+                              "Content-Length: ";
+
+static char plaintext_preamble[] = "HTTP/1.1 200 OK\r\n"
+                                   "Server: libreactor\r\n"
+                                   "Content-Type: text/plain\r\n"
+                                   "Date: Thu, 01 Jan 1970 00:00:00 GMT\r\n"
+                                   "Content-Length: ";
+
+static size_t json_preamble_size = sizeof(json_preamble) - 1;
+static size_t plaintext_preamble_size = sizeof(plaintext_preamble) - 1;
+
 
-  if (reactor_vector_equal(session->request->target, reactor_vector_string("/json"))) {
-    char json_msg[32];
-    (void) clo_encode((clo[]) {clo_object({"message", clo_string("Hello, World!")})}, json_msg, sizeof(json_msg));
-    reactor_server_ok(session, reactor_vector_string("application/json"), reactor_vector_string(json_msg));
-    return REACTOR_OK;
+static int send_response(reactor_http *http_connection, char *response, response_type type)
+{
+  reactor_vector body = reactor_vector_string(response);
+  reactor_vector content_length = reactor_utility_u32tov(body.size);
+
+  if (type == JSON){
+    reactor_stream_write(&http_connection->stream, json_preamble, json_preamble_size);
+  }
+  else {
+    reactor_stream_write(&http_connection->stream, plaintext_preamble, plaintext_preamble_size);
   }
-  else if (reactor_vector_equal(session->request->target, reactor_vector_string("/plaintext"))) {
-    reactor_server_ok(session, reactor_vector_string("text/plain"), reactor_vector_string("Hello, World!"));
-    return REACTOR_OK;
+
+  reactor_stream_write(&http_connection->stream, content_length.base, content_length.size);
+  reactor_stream_write(&http_connection->stream, "\r\n\r\n", 4);
+  reactor_stream_write(&http_connection->stream, body.base, body.size);
+  return REACTOR_OK;
+}
+
+static reactor_status http_handler(reactor_event *event)
+{
+  reactor_http *http_connection = event->state;
+  reactor_http_request *request;
+  char json_msg[4096];
+
+  if (event->type == REACTOR_HTTP_EVENT_REQUEST){
+    request = (reactor_http_request *) event->data;
+
+    if (reactor_vector_equal(request->target, reactor_vector_string("/json"))){
+      (void) clo_encode((clo[]) {clo_object({"message", clo_string("Hello, World!")})}, json_msg, sizeof(json_msg));
+      return send_response(http_connection, json_msg, JSON);
+    }
+    else if (reactor_vector_equal(request->target, reactor_vector_string("/plaintext"))){
+      return send_response(http_connection, "Hello, World!", PLAINTEXT);
+    }
+    else{
+      return send_response(http_connection, "Hello from libreactor!\n", PLAINTEXT);
+    }
   }
   else {
-    reactor_server_ok(session, reactor_vector_string("text/plain"), reactor_vector_string("Hello from libreactor!\n"));
-    return REACTOR_OK;
+    reactor_http_destruct(http_connection);
+    list_erase(http_connection, NULL);
+    return REACTOR_ABORT;
   }
 }
 
+static reactor_status net_handler(reactor_event *event)
+{
+  struct http_server *server = event->state;
+  reactor_http connection_initializer = (reactor_http) {0};
+  reactor_http *http_connection;
+
+  switch (event->type)
+    {
+    case REACTOR_NET_EVENT_ACCEPT:
+      http_connection = list_push_back(&server->connections, &connection_initializer, sizeof (reactor_http));
+      reactor_http_construct(http_connection, http_handler, http_connection);
+      reactor_http_set_mode(http_connection, REACTOR_HTTP_MODE_REQUEST);
+      reactor_http_open(http_connection, event->data);
+      return REACTOR_OK;
+    default:
+      reactor_net_destruct(&server->net);
+      return REACTOR_ABORT;
+    }
+}
+
+static void set_date()
+{
+  update_date();
+  copy_date(json_preamble, 69);
+  copy_date(plaintext_preamble, 63);
+}
+
+static reactor_status http_date_timer_handler(reactor_event *event)
+{
+  (void) event;
+  set_date();
+  return REACTOR_OK;
+}
+
 
 int main()
 {
-  reactor_server server;
+  struct http_server server = {0};
+  reactor_timer timer;
 
   setup();
-  reactor_construct();
-  reactor_server_construct(&server, NULL, NULL);
-  reactor_server_route(&server, tfb, NULL);
-  (void) reactor_server_open(&server, "0.0.0.0", "8080");
+  list_construct(&server.connections);
+  reactor_core_construct();
+
+  // set the correct date in the preambles before the server starts. Timer then updates them every second
+  set_date();
+  reactor_timer_construct(&timer, http_date_timer_handler, &timer);
+  reactor_timer_set(&timer, 1, 1000000000);
+
+  reactor_net_construct(&server.net, net_handler, &server);
+  (void) custom_reactor_net_bind(&server.net, "0.0.0.0", "8080");
 
-  reactor_run();
-  reactor_destruct();
+  reactor_core_run();
+  reactor_core_destruct();
 }