feat: add context information to requests
							parent
							
								
									6d4b94c55e
								
							
						
					
					
						commit
						dfd27b579d
					
				
							
								
								
									
										2
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										2
									
								
								Makefile
								
								
								
								
							|  | @ -48,7 +48,7 @@ objs: $(OBJS) | |||
| .PHONY: bin | ||||
| bin: $(BIN) | ||||
| $(BIN): $(OBJS) | ||||
| 	$(CC) $(INTERNALCFLAGS) -o $@ $^ | ||||
| 	$(CC) $(INTERNALCFLAGS) $(LDFLAGS) -o $@ $^ | ||||
| 
 | ||||
| $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c | ||||
| 	mkdir -p $(dir $@) | ||||
|  |  | |||
|  | @ -6,36 +6,35 @@ | |||
| #include <stdlib.h> | ||||
| 
 | ||||
| #include "http_req.h" | ||||
| #include "trie.h" | ||||
| 
 | ||||
| // Size of the read and write buffers for each connection, in bytes
 | ||||
| #define EVENT_LOOP_BUFFER_SIZE 1024 | ||||
| 
 | ||||
| typedef struct event_loop_gctx { | ||||
|   Trie *trie; | ||||
| } event_loop_gctx; | ||||
| 
 | ||||
| event_loop_gctx *event_loop_gctx_init(); | ||||
| 
 | ||||
| typedef struct event_loop_ctx { | ||||
|   http_request req; | ||||
|   event_loop_gctx *g; | ||||
| } event_loop_ctx; | ||||
| 
 | ||||
| event_loop_ctx *event_loop_ctx_init(event_loop_gctx *g); | ||||
| 
 | ||||
| void event_loop_ctx_free(event_loop_ctx *ctx); | ||||
| 
 | ||||
| /**
 | ||||
|  * Represents an active connection managed by the event loop | ||||
|  */ | ||||
| typedef struct event_loop_conn event_loop_conn; | ||||
| 
 | ||||
| typedef enum { | ||||
|   event_loop_conn_state_req = 0, | ||||
|   event_loop_conn_state_res = 1, | ||||
|   event_loop_conn_state_end = 2, | ||||
| } event_loop_conn_state; | ||||
| 
 | ||||
| /*
 | ||||
|  * Main struct object representing the event loop | ||||
|  */ | ||||
| typedef struct event_loop event_loop; | ||||
| 
 | ||||
| /*
 | ||||
|  * Initialize a new event loop | ||||
|  */ | ||||
| event_loop *event_loop_init(); | ||||
| 
 | ||||
| /*
 | ||||
|  * Run the event loop. This function never returns. | ||||
|  */ | ||||
| void event_loop_run(event_loop *el, int port); | ||||
| 
 | ||||
| typedef struct event_loop_conn { | ||||
|   int fd; | ||||
|   event_loop_conn_state state; | ||||
|  | @ -51,24 +50,34 @@ typedef struct event_loop_conn { | |||
|   // If true, the server will close the connection after the final write buffer
 | ||||
|   // has been written
 | ||||
|   bool close_after_write; | ||||
|   /* void (*process_func)(struct event_loop_conn *); */ | ||||
|   http_request req; | ||||
|   event_loop_ctx *ctx; | ||||
| } event_loop_conn; | ||||
| 
 | ||||
| /*
 | ||||
|  * Initialize a new event_loop_conn struct | ||||
|  */ | ||||
| event_loop_conn *event_loop_conn_init(); | ||||
| event_loop_conn *event_loop_conn_init(event_loop_gctx *g); | ||||
| 
 | ||||
| void event_loop_conn_free(event_loop_conn *conn); | ||||
| 
 | ||||
| /*
 | ||||
|  * Process data on a connection | ||||
|  */ | ||||
| void event_loop_conn_io(event_loop_conn *conn); | ||||
| 
 | ||||
| /*
 | ||||
|  * Main struct object representing the event loop | ||||
|  */ | ||||
| typedef struct event_loop { | ||||
|   event_loop_conn **connections; | ||||
|   size_t connection_count; | ||||
|   event_loop_gctx *gctx; | ||||
| } event_loop; | ||||
| 
 | ||||
| /*
 | ||||
|  * Initialize a new event_loop struct | ||||
|  */ | ||||
| event_loop *event_loop_init(); | ||||
| event_loop *event_loop_init(event_loop_gctx *g); | ||||
| 
 | ||||
| /*
 | ||||
|  * Place a new connection into the event loop's internal array. | ||||
|  | @ -82,6 +91,9 @@ int event_loop_put(event_loop *loop, event_loop_conn *conn); | |||
|  */ | ||||
| int event_loop_accept(event_loop *loop, int fd); | ||||
| 
 | ||||
| void event_loop_conn_io(event_loop_conn *conn); | ||||
| /*
 | ||||
|  * Run the event loop. This function never returns. | ||||
|  */ | ||||
| void event_loop_run(event_loop *el, int port); | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
|  | @ -12,7 +12,6 @@ | |||
| 
 | ||||
| #include "event_loop.h" | ||||
| #include "log.h" | ||||
| #include "picohttpparser.h" | ||||
| 
 | ||||
| static int event_loop_fd_set_nb(int fd) { | ||||
|   int flags = fcntl(fd, F_GETFL); | ||||
|  | @ -24,12 +23,13 @@ static int event_loop_fd_set_nb(int fd) { | |||
|   return 0; | ||||
| } | ||||
| 
 | ||||
| event_loop *event_loop_init() { | ||||
| event_loop *event_loop_init(event_loop_gctx *gctx) { | ||||
|   event_loop *el = calloc(sizeof(event_loop), 1); | ||||
| 
 | ||||
|   // No idea if this is a good starter value
 | ||||
|   el->connections = calloc(sizeof(event_loop_conn), 16); | ||||
|   el->connection_count = 16; | ||||
|   el->gctx = gctx; | ||||
| 
 | ||||
|   return el; | ||||
| } | ||||
|  | @ -52,7 +52,7 @@ int event_loop_put(event_loop *el, event_loop_conn *conn) { | |||
|   return 0; | ||||
| } | ||||
| 
 | ||||
| int event_loop_accept(event_loop *loop, int fd) { | ||||
| int event_loop_accept(event_loop *el, int fd) { | ||||
|   struct sockaddr_in client_addr; | ||||
|   socklen_t socklen = sizeof(client_addr); | ||||
|   int connfd = accept(fd, (struct sockaddr *)&client_addr, &socklen); | ||||
|  | @ -71,7 +71,7 @@ int event_loop_accept(event_loop *loop, int fd) { | |||
|   } | ||||
| 
 | ||||
|   // creating the struct Conn
 | ||||
|   event_loop_conn *conn = calloc(sizeof(event_loop_conn), 1); | ||||
|   event_loop_conn *conn = event_loop_conn_init(el->gctx); | ||||
| 
 | ||||
|   // Close the connectoin if we fail to allocate a connection struct
 | ||||
|   if (conn == NULL) { | ||||
|  | @ -83,7 +83,7 @@ int event_loop_accept(event_loop *loop, int fd) { | |||
|   conn->fd = connfd; | ||||
|   conn->state = event_loop_conn_state_req; | ||||
| 
 | ||||
|   res = event_loop_put(loop, conn); | ||||
|   res = event_loop_put(el, conn); | ||||
| 
 | ||||
|   if (res != 0) { | ||||
|     close(connfd); | ||||
|  | @ -190,7 +190,7 @@ void event_loop_run(event_loop *el, int port) { | |||
|           el->connections[conn->fd] = NULL; | ||||
| 
 | ||||
|           close(conn->fd); | ||||
|           free(conn); | ||||
|           event_loop_conn_free(conn); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  |  | |||
|  | @ -1,165 +1,28 @@ | |||
| #include <errno.h> | ||||
| #include <fcntl.h> | ||||
| #include <netinet/in.h> | ||||
| #include <poll.h> | ||||
| #include <stdbool.h> | ||||
| #include <stdint.h> | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| #include <sys/socket.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| #include "picohttpparser.h" | ||||
| 
 | ||||
| #include "event_loop.h" | ||||
| #include "http.h" | ||||
| 
 | ||||
| void event_loop_conn_io_res(event_loop_conn *conn) { | ||||
|   while (1) { | ||||
|     ssize_t res = 0; | ||||
|     size_t remain = conn->wbuf_size - conn->wbuf_sent; | ||||
| event_loop_gctx *event_loop_gctx_init() { | ||||
|   event_loop_gctx *gctx = calloc(sizeof(event_loop_gctx), 1); | ||||
| 
 | ||||
|     do { | ||||
|       res = write(conn->fd, &conn->wbuf[conn->wbuf_sent], remain); | ||||
|     } while (res < 0 && errno == EINTR); | ||||
| 
 | ||||
|     // EAGAIN doesn't mean there was an error, but rather that there's no more
 | ||||
|     // data right now, but there might be more later, aka "try again later"
 | ||||
|     if (res < 0 && errno == EAGAIN) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // If it's not EGAIN, there was an error writing so we simply end the
 | ||||
|     // request
 | ||||
|     if (res < 0) { | ||||
|       conn->state = event_loop_conn_state_end; | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     conn->wbuf_sent += (size_t)res; | ||||
| 
 | ||||
|     // After writing a response, we switch back to request mode to process a
 | ||||
|     // next request
 | ||||
|     if (conn->wbuf_sent == conn->wbuf_size) { | ||||
|       conn->state = conn->close_after_write ? event_loop_conn_state_end | ||||
|                                             : event_loop_conn_state_req; | ||||
|       /* c->wbuf_sent = 0; */ | ||||
|       /* c->wbuf_size = 0; */ | ||||
| 
 | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
|   return gctx; | ||||
| } | ||||
| 
 | ||||
| bool event_loop_handle_request(event_loop_conn *conn) { | ||||
|   // Prevents the request handler function from looping indefinitely without
 | ||||
|   // ever consuming new data
 | ||||
|   if (conn->rbuf_size - conn->rbuf_read == 0) { | ||||
|     return false; | ||||
|   } | ||||
| event_loop_ctx *event_loop_ctx_init(event_loop_gctx *g) { | ||||
|   event_loop_ctx *ctx = calloc(sizeof(event_loop_ctx), 1); | ||||
|   ctx->g = g; | ||||
| 
 | ||||
|   http_parse_error res = | ||||
|       http_parse_request(&conn->req, (const char *)&conn->rbuf[conn->rbuf_read], | ||||
|                          conn->rbuf_size - conn->rbuf_read); | ||||
| 
 | ||||
|   if (res == http_parse_error_ok) { | ||||
|     conn->rbuf_read += conn->req.len; | ||||
| 
 | ||||
|     memcpy(conn->wbuf, http_404, http_404_len); | ||||
| 
 | ||||
|     conn->state = event_loop_conn_state_res; | ||||
|     conn->wbuf_size = http_404_len; | ||||
|     conn->wbuf_sent = 0; | ||||
| 
 | ||||
|     /* event_loop_conn_io_res(conn); */ | ||||
| 
 | ||||
|   } | ||||
|   // Both in the case of an invalid HTTP request or one that's larger than the
 | ||||
|   // read buffer, we cannot determine when the next, possibly valid, HTTP
 | ||||
|   // request begins in the data stream. Therefore, we close the connection,
 | ||||
|   // even if additional pipelined requests are incoming.
 | ||||
|   else if (res == http_parse_error_invalid || | ||||
|            (res == http_parse_error_incomplete && | ||||
|             conn->rbuf_size == EVENT_LOOP_BUFFER_SIZE)) { | ||||
|     conn->state = event_loop_conn_state_end; | ||||
|   } | ||||
| 
 | ||||
|   // TODO in highly concurrent situations, it might actually be better to always
 | ||||
|   // return false here, as this allows cycling better through all connections
 | ||||
|   return conn->state == event_loop_conn_state_req; | ||||
|   return ctx; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Read new data into the read buffer. This command performs at most one | ||||
|  * successful read syscall. | ||||
|  * | ||||
|  * Returns whether the function should be retried immediately or not. | ||||
|  */ | ||||
| void event_loop_conn_io_req(event_loop_conn *conn) { | ||||
|   do { | ||||
|     // Move remaining data to start of buffer
 | ||||
|     memmove(conn->rbuf, &conn->rbuf[conn->rbuf_read], | ||||
|             conn->rbuf_size - conn->rbuf_read); | ||||
|     conn->rbuf_size -= conn->rbuf_read; | ||||
|     conn->rbuf_read = 0; | ||||
| void event_loop_ctx_free(event_loop_ctx *ctx) { free(ctx); } | ||||
| 
 | ||||
|     ssize_t res; | ||||
|     size_t cap = EVENT_LOOP_BUFFER_SIZE - conn->rbuf_size; | ||||
| event_loop_conn *event_loop_conn_init(event_loop_gctx *g) { | ||||
|   event_loop_conn *conn = calloc(sizeof(event_loop_conn), 1); | ||||
|   conn->ctx = event_loop_ctx_init(g); | ||||
| 
 | ||||
|     // Try to read at most cap bytes from the file descriptor
 | ||||
|     do { | ||||
|       res = read(conn->fd, &conn->rbuf[conn->rbuf_size], cap); | ||||
|     } while (res < 0 && errno == EINTR); | ||||
| 
 | ||||
|     // EGAIN means we try again later
 | ||||
|     if (res < 0 && errno == EAGAIN) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // Any other negative error message means the read errored out
 | ||||
|     if (res < 0) { | ||||
|       conn->state = event_loop_conn_state_end; | ||||
| 
 | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // Output of 0 and no error means we've reached the end of the input. We run
 | ||||
|     // try_one_request one more time and close the connection if this doesn't
 | ||||
|     // change the result.
 | ||||
|     if (res == 0) { | ||||
|       conn->state = event_loop_conn_state_end; | ||||
| 
 | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // We switch to processing mode if we've reached the end of the data stream,
 | ||||
|     // or if the read buffer is filled
 | ||||
|     /* if (res == 0 || c->rbuf_size == MAX_MSG_SIZE) { */ | ||||
|     /*     c->state = STATE_PROCESS; */ | ||||
|     /*     return false; */ | ||||
|     /* } */ | ||||
| 
 | ||||
|     conn->rbuf_size += (size_t)res; | ||||
| 
 | ||||
|     // This loop allows processing multiple requests from a single read buffer
 | ||||
|     while (event_loop_handle_request(conn)) | ||||
|       ; | ||||
|   } | ||||
|   // We can keep reading as long as we're in request mode
 | ||||
|   while (conn->state == event_loop_conn_state_req); | ||||
|   return conn; | ||||
| } | ||||
| 
 | ||||
| void event_loop_conn_io(event_loop_conn *conn) { | ||||
|   switch (conn->state) { | ||||
|   case event_loop_conn_state_req: | ||||
|     event_loop_conn_io_req(conn); | ||||
|     break; | ||||
|   case event_loop_conn_state_res: | ||||
|     event_loop_conn_io_res(conn); | ||||
|     break; | ||||
|   case event_loop_conn_state_end: | ||||
|     printf("we shouldn't be here\n"); | ||||
|     break; | ||||
|   } | ||||
| void event_loop_conn_free(event_loop_conn *conn) { | ||||
|   event_loop_ctx_free(conn->ctx); | ||||
|   free(conn); | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,157 @@ | |||
| #include <errno.h> | ||||
| #include <fcntl.h> | ||||
| #include <netinet/in.h> | ||||
| #include <poll.h> | ||||
| #include <stdbool.h> | ||||
| #include <stdint.h> | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| #include <sys/socket.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| #include "picohttpparser.h" | ||||
| 
 | ||||
| #include "event_loop.h" | ||||
| #include "http.h" | ||||
| 
 | ||||
| void event_loop_conn_io_res(event_loop_conn *conn) { | ||||
|   while (1) { | ||||
|     ssize_t res = 0; | ||||
|     size_t remain = conn->wbuf_size - conn->wbuf_sent; | ||||
| 
 | ||||
|     do { | ||||
|       res = write(conn->fd, &conn->wbuf[conn->wbuf_sent], remain); | ||||
|     } while (res < 0 && errno == EINTR); | ||||
| 
 | ||||
|     // EAGAIN doesn't mean there was an error, but rather that there's no more
 | ||||
|     // data right now, but there might be more later, aka "try again later"
 | ||||
|     if (res < 0 && errno == EAGAIN) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // If it's not EGAIN, there was an error writing so we simply end the
 | ||||
|     // request
 | ||||
|     if (res < 0) { | ||||
|       conn->state = event_loop_conn_state_end; | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     conn->wbuf_sent += (size_t)res; | ||||
| 
 | ||||
|     // After writing a response, we switch back to request mode to process a
 | ||||
|     // next request
 | ||||
|     if (conn->wbuf_sent == conn->wbuf_size) { | ||||
|       conn->state = conn->close_after_write ? event_loop_conn_state_end | ||||
|                                             : event_loop_conn_state_req; | ||||
|       /* c->wbuf_sent = 0; */ | ||||
|       /* c->wbuf_size = 0; */ | ||||
| 
 | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| bool event_loop_handle_request(event_loop_conn *conn) { | ||||
|   // Prevents the request handler function from looping indefinitely without
 | ||||
|   // ever consuming new data
 | ||||
|   if (conn->rbuf_size - conn->rbuf_read == 0) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   http_parse_error res = http_parse_request( | ||||
|       &conn->ctx->req, (const char *)&conn->rbuf[conn->rbuf_read], | ||||
|       conn->rbuf_size - conn->rbuf_read); | ||||
| 
 | ||||
|   if (res == http_parse_error_ok) { | ||||
|     // Perform the request
 | ||||
|     http_route(conn); | ||||
|   } | ||||
|   // Both in the case of an invalid HTTP request or one that's larger than the
 | ||||
|   // read buffer, we cannot determine when the next, possibly valid, HTTP
 | ||||
|   // request begins in the data stream. Therefore, we close the connection,
 | ||||
|   // even if additional pipelined requests are incoming.
 | ||||
|   else if (res == http_parse_error_invalid || | ||||
|            (res == http_parse_error_incomplete && | ||||
|             conn->rbuf_size == EVENT_LOOP_BUFFER_SIZE)) { | ||||
|     conn->state = event_loop_conn_state_end; | ||||
|   } | ||||
| 
 | ||||
|   // TODO in highly concurrent situations, it might actually be better to always
 | ||||
|   // return false here, as this allows cycling better through all connections
 | ||||
|   return conn->state == event_loop_conn_state_req; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Read new data into the read buffer. This command performs at most one | ||||
|  * successful read syscall. | ||||
|  * | ||||
|  * Returns whether the function should be retried immediately or not. | ||||
|  */ | ||||
| void event_loop_conn_io_req(event_loop_conn *conn) { | ||||
|   do { | ||||
|     // Move remaining data to start of buffer
 | ||||
|     memmove(conn->rbuf, &conn->rbuf[conn->rbuf_read], | ||||
|             conn->rbuf_size - conn->rbuf_read); | ||||
|     conn->rbuf_size -= conn->rbuf_read; | ||||
|     conn->rbuf_read = 0; | ||||
| 
 | ||||
|     ssize_t res; | ||||
|     size_t cap = EVENT_LOOP_BUFFER_SIZE - conn->rbuf_size; | ||||
| 
 | ||||
|     // Try to read at most cap bytes from the file descriptor
 | ||||
|     do { | ||||
|       res = read(conn->fd, &conn->rbuf[conn->rbuf_size], cap); | ||||
|     } while (res < 0 && errno == EINTR); | ||||
| 
 | ||||
|     // EGAIN means we try again later
 | ||||
|     if (res < 0 && errno == EAGAIN) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // Any other negative error message means the read errored out
 | ||||
|     if (res < 0) { | ||||
|       conn->state = event_loop_conn_state_end; | ||||
| 
 | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // Output of 0 and no error means we've reached the end of the input. We run
 | ||||
|     // try_one_request one more time and close the connection if this doesn't
 | ||||
|     // change the result.
 | ||||
|     if (res == 0) { | ||||
|       conn->state = event_loop_conn_state_end; | ||||
| 
 | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // We switch to processing mode if we've reached the end of the data stream,
 | ||||
|     // or if the read buffer is filled
 | ||||
|     /* if (res == 0 || c->rbuf_size == MAX_MSG_SIZE) { */ | ||||
|     /*     c->state = STATE_PROCESS; */ | ||||
|     /*     return false; */ | ||||
|     /* } */ | ||||
| 
 | ||||
|     conn->rbuf_size += (size_t)res; | ||||
| 
 | ||||
|     // This loop allows processing multiple requests from a single read buffer
 | ||||
|     while (event_loop_handle_request(conn)) | ||||
|       ; | ||||
|   } | ||||
|   // We can keep reading as long as we're in request mode
 | ||||
|   while (conn->state == event_loop_conn_state_req); | ||||
| } | ||||
| 
 | ||||
| void event_loop_conn_io(event_loop_conn *conn) { | ||||
|   switch (conn->state) { | ||||
|   case event_loop_conn_state_req: | ||||
|     event_loop_conn_io_req(conn); | ||||
|     break; | ||||
|   case event_loop_conn_state_res: | ||||
|     event_loop_conn_io_res(conn); | ||||
|     break; | ||||
|   case event_loop_conn_state_end: | ||||
|     printf("we shouldn't be here\n"); | ||||
|     break; | ||||
|   } | ||||
| } | ||||
|  | @ -1,5 +1,17 @@ | |||
| #include <string.h> | ||||
| 
 | ||||
| #include "event_loop.h" | ||||
| #include "http.h" | ||||
| 
 | ||||
| typedef void (*routing_func)(event_loop_conn *); | ||||
| 
 | ||||
| routing_func http_route(); | ||||
| void http_route(event_loop_conn *conn) { | ||||
|   // TODO routing
 | ||||
| 
 | ||||
|   // Fallthrough is to return a 404
 | ||||
|   memcpy(conn->wbuf, http_404, http_404_len); | ||||
| 
 | ||||
|   conn->state = event_loop_conn_state_res; | ||||
|   conn->wbuf_size = http_404_len; | ||||
|   conn->wbuf_sent = 0; | ||||
| } | ||||
|  |  | |||
							
								
								
									
										16
									
								
								src/main.c
								
								
								
								
							
							
						
						
									
										16
									
								
								src/main.c
								
								
								
								
							|  | @ -1,11 +1,25 @@ | |||
| #include <stdio.h> | ||||
| 
 | ||||
| #include "event_loop.h" | ||||
| #include "log.h" | ||||
| 
 | ||||
| int main() { | ||||
|   setvbuf(stdout, NULL, _IONBF, 0); | ||||
| 
 | ||||
|   event_loop *el = event_loop_init(); | ||||
|   info("Initializing trie"); | ||||
| 
 | ||||
|   Trie *trie = trie_init(); | ||||
|   int count = trie_populate(trie, "lander.data"); | ||||
| 
 | ||||
|   if (count < 0) { | ||||
|     critical(1, "An error occured while populating the trie."); | ||||
|   } | ||||
| 
 | ||||
|   info("Trie initialized and populated with %i entries", count); | ||||
| 
 | ||||
|   event_loop_gctx *gctx = event_loop_gctx_init(); | ||||
|   gctx->trie = trie; | ||||
|   event_loop *el = event_loop_init(gctx); | ||||
| 
 | ||||
|   event_loop_run(el, 8000); | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue