| @@ -0,0 +1,2 @@ | ||
| /benchmark.wt | ||
| /benchmark_postgres.wt |
| @@ -1,3 +1,5 @@ | ||
| *.o | ||
| *~ | ||
| /Makefile | ||
| /main.d | ||
| /ws.duda |
| @@ -0,0 +1,23 @@ | ||
| cmake_minimum_required(VERSION 2.8.0) | ||
| project(h2o_app) | ||
| find_library(H2O_LIB h2o-evloop) | ||
| find_library(MUSTACHE_C_LIB mustache_c) | ||
| find_library(YAJL_LIB yajl) | ||
| find_path(H2O_INCLUDE h2o.h) | ||
| find_path(MUSTACHE_C_INCLUDE mustache.h) | ||
| find_path(YAJL_INCLUDE yajl/yajl_gen.h) | ||
| set(COMMON_OPTIONS -flto -pthread) | ||
| add_compile_options(-std=gnu11 -pedantic -Wall -Wextra ${COMMON_OPTIONS}) | ||
| set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address") | ||
| set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fstack-protector-all -D_FORTIFY_SOURCE=2") | ||
| set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Ofast") | ||
| set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -Ofast") | ||
| add_definitions(-DH2O_USE_LIBUV=0) | ||
| include_directories(src ${H2O_INCLUDE} ${MUSTACHE_C_INCLUDE} ${YAJL_INCLUDE}) | ||
| file(GLOB SOURCES "src/*.c") | ||
| add_executable(${PROJECT_NAME} ${SOURCES}) | ||
| target_link_libraries(${PROJECT_NAME} ${COMMON_OPTIONS}) | ||
| target_link_libraries(${PROJECT_NAME} ${H2O_LIB} ssl crypto pq ${MUSTACHE_C_LIB} ${YAJL_LIB}) | ||
| install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) | ||
| file(GLOB TEMPLATES "template/*") | ||
| install(FILES ${TEMPLATES} DESTINATION share/${PROJECT_NAME}/template) |
| @@ -0,0 +1,11 @@ | ||
| # h2o | ||
| This is a framework implementation using the [H2O](https://h2o.examp1e.net/) HTTP server. | ||
| ## Requirements | ||
| CMake, H2O, libpq, mustache-c, OpenSSL, YAJL | ||
| ## Contact | ||
| Anton Kirilov <antonvkirilov@gmail.com> |
| @@ -0,0 +1,57 @@ | ||
| #!/bin/bash | ||
| fw_depends h2o mustache-c yajl | ||
| H2O_APP_HOME="${IROOT}/h2o_app" | ||
| BUILD_DIR="${H2O_APP_HOME}_build" | ||
| H2O_APP_PROFILE_PORT="54321" | ||
| H2O_APP_PROFILE_URL="http://127.0.0.1:$H2O_APP_PROFILE_PORT" | ||
| build_h2o_app() | ||
| { | ||
| cmake -DCMAKE_INSTALL_PREFIX="$H2O_APP_HOME" -DCMAKE_BUILD_TYPE=Release \ | ||
| -DCMAKE_LIBRARY_PATH="${H2O_HOME}/lib;${MUSTACHE_C_HOME}/lib;${YAJL_HOME}/lib" \ | ||
| -DCMAKE_INCLUDE_PATH="${H2O_HOME}/include;${MUSTACHE_C_HOME}/include;${YAJL_HOME}/include" \ | ||
| -DCMAKE_C_FLAGS="-march=native $1" "$TROOT" | ||
| make -j "$(nproc)" | ||
| } | ||
| run_curl() | ||
| { | ||
| for ((i = 0; i < 10; i++)); do | ||
| curl "${H2O_APP_PROFILE_URL}/$1" > /dev/null 2>&1 | ||
| done | ||
| } | ||
| run_h2o_app() | ||
| { | ||
| "$1/h2o_app" -a2 -f "$2/template/fortunes.mustache" -m2 "$3" "$4" \ | ||
| -d "host=$DBHOST dbname=hello_world user=benchmarkdbuser password=benchmarkdbpass" & | ||
| } | ||
| generate_profile_data() | ||
| { | ||
| run_h2o_app . "${TROOT}" -p$H2O_APP_PROFILE_PORT -t1 | ||
| local -r H2O_APP_PROFILE_PID=$! | ||
| while ! curl ${H2O_APP_PROFILE_URL} > /dev/null 2>&1; do sleep 1; done | ||
| run_curl json | ||
| run_curl db | ||
| run_curl queries?queries=20 | ||
| run_curl fortunes | ||
| run_curl updates?queries=20 | ||
| run_curl plaintext | ||
| kill -s SIGTERM $H2O_APP_PROFILE_PID | ||
| wait $H2O_APP_PROFILE_PID | ||
| } | ||
| install -d "$BUILD_DIR" | ||
| pushd "$BUILD_DIR" | ||
| build_h2o_app "-fprofile-generate" | ||
| generate_profile_data | ||
| make clean | ||
| rm -f CMakeCache.txt | ||
| build_h2o_app "-fprofile-use" | ||
| make -j "$(nproc)" install | ||
| popd | ||
| rm -rf "$BUILD_DIR" | ||
| run_h2o_app "${H2O_APP_HOME}/bin" "${H2O_APP_HOME}/share/h2o_app" |
| @@ -0,0 +1,23 @@ | ||
| ./src/bitset.h | ||
| ./src/database.c | ||
| ./src/database.h | ||
| ./src/error.c | ||
| ./src/error.h | ||
| ./src/event_loop.c | ||
| ./src/event_loop.h | ||
| ./src/fortune.c | ||
| ./src/fortune.h | ||
| ./src/list.h | ||
| ./src/main.c | ||
| ./src/request_handler.c | ||
| ./src/request_handler.h | ||
| ./src/template.c | ||
| ./src/template.h | ||
| ./src/thread.c | ||
| ./src/thread.h | ||
| ./src/tls.c | ||
| ./src/tls.h | ||
| ./src/utility.c | ||
| ./src/utility.h | ||
| ./src/world.c | ||
| ./src/world.h |
| @@ -0,0 +1,60 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #ifndef BITSET_H_ | ||
| #define BITSET_H_ | ||
| #include <assert.h> | ||
| #include <limits.h> | ||
| #include <stdbool.h> | ||
| #include <stdint.h> | ||
| #include "utility.h" | ||
| typedef uint_fast32_t bitset_base_t; | ||
| #define BITSET_ISSET(i, b) bitset_isset((i), (b), sizeof(b) * CHAR_BIT) | ||
| #define BITSET_SET(i, b) bitset_set((i), (b), sizeof(b) * CHAR_BIT) | ||
| // Use a designated initializer to set all array elements to zero. | ||
| #define DEFINE_BITSET(b, s) \ | ||
| assert(s); \ | ||
| bitset_base_t (b)[((s) - 1) / (sizeof(bitset_base_t) * CHAR_BIT) + 1] = {[0] = 0} | ||
| static inline void bitset_set(size_t i, bitset_base_t *b, size_t num) | ||
| { | ||
| assert(i < num); | ||
| IGNORE_FUNCTION_PARAMETER(num); | ||
| const bitset_base_t mask = ((bitset_base_t) 1) << (i % (sizeof(*b) * CHAR_BIT)); | ||
| b[i / (sizeof(*b) * CHAR_BIT)] |= mask; | ||
| } | ||
| static inline bool bitset_isset(size_t i, bitset_base_t *b, size_t num) | ||
| { | ||
| assert(i < num); | ||
| IGNORE_FUNCTION_PARAMETER(num); | ||
| return (b[i / (sizeof(*b) * CHAR_BIT)] >> (i % (sizeof(*b) * CHAR_BIT))) & (bitset_base_t) 1; | ||
| } | ||
| #endif // BITSET_H_ |
| @@ -0,0 +1,88 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #ifndef DATABASE_H_ | ||
| #define DATABASE_H_ | ||
| #include <h2o.h> | ||
| #include <stdint.h> | ||
| #include <postgresql/libpq-fe.h> | ||
| #include "list.h" | ||
| #define COPY_HEADER "PGCOPY\n\377\r\n\0\0\0\0\0\0\0\0\0" | ||
| #define DB_REQ_ERROR "too many concurrent database requests\n" | ||
| #define DB_TIMEOUT_ERROR "database timeout\n" | ||
| #define FORTUNE_TABLE_NAME "Fortune" | ||
| #define ID_FIELD_NAME "id" | ||
| #define IS_PREPARED 1 | ||
| #define IS_SINGLE_ROW 2 | ||
| #define MAX_ID 10000 | ||
| #define MESSAGE_FIELD_NAME "message" | ||
| #define WORLD_TABLE_NAME "World" | ||
| #define UPDATE_QUERY \ | ||
| "CREATE TEMP TABLE input(LIKE World INCLUDING ALL) ON COMMIT DROP;" \ | ||
| "COPY input FROM STDIN WITH (FORMAT binary);" \ | ||
| "UPDATE World SET randomNumber = input.randomNumber FROM input " \ | ||
| "WHERE World." ID_FIELD_NAME " = input." ID_FIELD_NAME ";" | ||
| typedef enum { | ||
| SUCCESS, | ||
| DONE, | ||
| WANT_WRITE | ||
| } result_return_t; | ||
| typedef struct thread_context_t thread_context_t; | ||
| typedef struct db_query_param_t db_query_param_t; | ||
| typedef result_return_t (*on_result_t)(db_query_param_t *, PGresult *); | ||
| struct db_query_param_t { | ||
| list_t l; | ||
| void (*on_error)(db_query_param_t *, const char *); | ||
| on_result_t on_result; | ||
| void (*on_timeout)(db_query_param_t *); | ||
| int (*on_write_ready)(db_query_param_t *, PGconn *); | ||
| const char *command; | ||
| const char * const *paramValues; | ||
| const int *paramLengths; | ||
| const int *paramFormats; | ||
| size_t nParams; | ||
| uint_fast32_t flags; | ||
| int resultFormat; | ||
| }; | ||
| typedef struct { | ||
| list_t *db_conn; | ||
| queue_t queries; | ||
| size_t db_conn_num; | ||
| size_t free_db_conn_num; | ||
| size_t query_num; | ||
| h2o_timeout_t h2o_timeout; | ||
| } db_state_t; | ||
| void connect_to_database(thread_context_t *ctx); | ||
| int execute_query(thread_context_t *ctx, db_query_param_t *param); | ||
| void free_database_state(h2o_loop_t *loop, db_state_t *db_state); | ||
| void initialize_database_state(h2o_loop_t *loop, db_state_t *db_state); | ||
| #endif // DATABASE_H_ |
| @@ -0,0 +1,66 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #include <libgen.h> | ||
| #include <stdarg.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <unistd.h> | ||
| #include <sys/syscall.h> | ||
| #include "error.h" | ||
| void print_error(const char *file, | ||
| unsigned line, | ||
| const char *function, | ||
| const char *error_string, ...) | ||
| { | ||
| char * const file_name = strdup(file); | ||
| if (file_name) | ||
| file = basename(file_name); | ||
| flockfile(stderr); | ||
| fprintf(stderr, "[%d] %s: %u: %s(): ", (int) syscall(SYS_gettid), file, line, function); | ||
| va_list arg; | ||
| va_start(arg, error_string); | ||
| vfprintf(stderr, error_string, arg); | ||
| va_end(arg); | ||
| putc_unlocked('\n', stderr); | ||
| funlockfile(stderr); | ||
| if (file_name) | ||
| free(file_name); | ||
| } | ||
| void print_library_error(const char *file, | ||
| unsigned line, | ||
| const char *function, | ||
| int error_code) | ||
| { | ||
| char error_string[128]; | ||
| if (strerror_r(error_code, error_string, sizeof(error_string))) | ||
| *error_string = '\0'; | ||
| print_error(file, line, function, "%s (%d)", error_string, error_code); | ||
| } |
| @@ -0,0 +1,85 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #ifndef ERROR_H_ | ||
| #define ERROR_H_ | ||
| #include <errno.h> | ||
| #include <stdarg.h> | ||
| #include <yajl/yajl_gen.h> | ||
| #define EPOLL_ERR_MSG "epoll_ctl() failure\n" | ||
| #define MEM_ALLOC_ERR_MSG "memory allocation failure\n" | ||
| #define CHECK_ERRNO(function, ...) \ | ||
| do { \ | ||
| const int error_code = (function)(__VA_ARGS__); \ | ||
| \ | ||
| if (error_code) { \ | ||
| print_library_error(__FILE__, __LINE__, #function, errno); \ | ||
| abort(); \ | ||
| } \ | ||
| } while(0) | ||
| #define CHECK_ERRNO_RETURN(out, function, ...) \ | ||
| do { \ | ||
| const int return_value = (function)(__VA_ARGS__); \ | ||
| \ | ||
| if (return_value == -1) { \ | ||
| print_library_error(__FILE__, __LINE__, #function, errno); \ | ||
| abort(); \ | ||
| } \ | ||
| \ | ||
| (out) = return_value; \ | ||
| } while(0) | ||
| #define CHECK_ERROR(function, ...) \ | ||
| do { \ | ||
| const int error_code = (function)(__VA_ARGS__); \ | ||
| \ | ||
| if (error_code) { \ | ||
| print_library_error(__FILE__, __LINE__, #function, error_code); \ | ||
| abort(); \ | ||
| } \ | ||
| } while(0) | ||
| #define CHECK_YAJL_STATUS(function, ...) \ | ||
| do { \ | ||
| const yajl_gen_status status_code = (function)(__VA_ARGS__); \ | ||
| \ | ||
| if (status_code != yajl_gen_status_ok) { \ | ||
| print_error(__FILE__, __LINE__, #function, "Error (%d)", (int) status_code); \ | ||
| goto error_yajl; \ | ||
| } \ | ||
| } while(0) | ||
| #define ERROR(...) print_error(__FILE__, __LINE__, __func__, __VA_ARGS__) | ||
| #define LIBRARY_ERROR(function) print_library_error(__FILE__, __LINE__, (function), errno) | ||
| void print_error(const char *file, | ||
| unsigned line, | ||
| const char *function, | ||
| const char *error_string, ...); | ||
| void print_library_error(const char *file, | ||
| unsigned line, | ||
| const char *function, | ||
| int error_code); | ||
| #endif // ERROR_H_ |
| @@ -0,0 +1,205 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #include <errno.h> | ||
| #include <fcntl.h> | ||
| #include <h2o.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <unistd.h> | ||
| #include <sys/epoll.h> | ||
| #include <sys/syscall.h> | ||
| #include "error.h" | ||
| #include "event_loop.h" | ||
| #include "thread.h" | ||
| #include "utility.h" | ||
| static void accept_connection(h2o_socket_t *listener, const char *err); | ||
| static void do_epoll_wait(h2o_socket_t *epoll_sock, const char *err); | ||
| static void on_close_connection(void *data); | ||
| static void process_messages(h2o_multithread_receiver_t *receiver, h2o_linklist_t *messages); | ||
| static void shutdown_server(h2o_socket_t *listener, const char *err); | ||
| static void accept_connection(h2o_socket_t *listener, const char *err) | ||
| { | ||
| if (!err) { | ||
| thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t, | ||
| event_loop, | ||
| listener->data); | ||
| if (!ctx->global_data->shutdown) { | ||
| size_t accepted = 0; | ||
| do { | ||
| h2o_socket_t * const sock = h2o_evloop_socket_accept(listener); | ||
| if (!sock) | ||
| break; | ||
| ctx->event_loop.conn_num++; | ||
| sock->on_close.cb = on_close_connection; | ||
| sock->on_close.data = &ctx->event_loop.conn_num; | ||
| h2o_accept(&ctx->event_loop.h2o_accept_ctx, sock); | ||
| } while (++accepted < ctx->global_data->config->max_accept); | ||
| } | ||
| } | ||
| } | ||
| static void do_epoll_wait(h2o_socket_t *epoll_sock, const char *err) | ||
| { | ||
| if (!err) { | ||
| thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t, | ||
| event_loop, | ||
| epoll_sock->data); | ||
| const size_t num_event = ctx->db_state.db_conn_num - ctx->db_state.free_db_conn_num; | ||
| int ready; | ||
| struct epoll_event event[num_event]; | ||
| do | ||
| ready = epoll_wait(ctx->event_loop.epoll_fd, event, num_event, 0); | ||
| while (ready < 0 && errno == EINTR); | ||
| if (ready > 0) | ||
| for (size_t i = 0; i < (size_t) ready; i++) { | ||
| void (** const on_write_ready)(void *) = event[i].data.ptr; | ||
| (*on_write_ready)(on_write_ready); | ||
| } | ||
| } | ||
| } | ||
| static void on_close_connection(void *data) | ||
| { | ||
| size_t * const conn_num = data; | ||
| (*conn_num)--; | ||
| } | ||
| static void process_messages(h2o_multithread_receiver_t *receiver, h2o_linklist_t *messages) | ||
| { | ||
| IGNORE_FUNCTION_PARAMETER(messages); | ||
| thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t, | ||
| event_loop.h2o_receiver, | ||
| receiver); | ||
| h2o_socket_read_stop(ctx->event_loop.h2o_socket); | ||
| } | ||
| static void shutdown_server(h2o_socket_t *listener, const char *err) | ||
| { | ||
| if (!err) { | ||
| thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t, | ||
| event_loop, | ||
| listener->data); | ||
| ctx->global_data->shutdown = true; | ||
| h2o_socket_read_stop(ctx->event_loop.h2o_socket); | ||
| for (size_t i = 1; i < ctx->global_data->config->thread_num; i++) | ||
| h2o_multithread_send_message(&ctx[i].event_loop.h2o_receiver, NULL); | ||
| } | ||
| } | ||
| void event_loop(thread_context_t *ctx) | ||
| { | ||
| ctx->tid = syscall(SYS_gettid); | ||
| ctx->random_seed = ctx->tid; | ||
| while (!ctx->global_data->shutdown || ctx->event_loop.conn_num) | ||
| h2o_evloop_run(ctx->event_loop.h2o_ctx.loop); | ||
| } | ||
| void free_event_loop(event_loop_t *event_loop) | ||
| { | ||
| h2o_multithread_unregister_receiver(event_loop->h2o_ctx.queue, &event_loop->h2o_receiver); | ||
| h2o_socket_close(event_loop->h2o_socket); | ||
| h2o_socket_close(event_loop->epoll_socket); | ||
| h2o_context_dispose(&event_loop->h2o_ctx); | ||
| } | ||
| void initialize_event_loop(bool is_main_thread, | ||
| global_data_t *global_data, | ||
| event_loop_t *loop) | ||
| { | ||
| memset(loop, 0, sizeof(*loop)); | ||
| h2o_context_init(&loop->h2o_ctx, h2o_evloop_create(), &global_data->h2o_config); | ||
| loop->h2o_accept_ctx.ctx = &loop->h2o_ctx; | ||
| loop->h2o_accept_ctx.hosts = global_data->h2o_config.hosts; | ||
| loop->h2o_accept_ctx.ssl_ctx = global_data->ssl_ctx; | ||
| int listener_sd; | ||
| if (is_main_thread) | ||
| listener_sd = global_data->listener_sd; | ||
| else { | ||
| int flags; | ||
| CHECK_ERRNO_RETURN(listener_sd, dup, global_data->listener_sd); | ||
| CHECK_ERRNO_RETURN(flags, fcntl, listener_sd, F_GETFD); | ||
| CHECK_ERRNO(fcntl, listener_sd, F_SETFD, flags | FD_CLOEXEC); | ||
| } | ||
| // Let all the threads race to call accept() on the socket; since the latter is | ||
| // non-blocking, that will effectively act as load balancing. | ||
| loop->h2o_socket = h2o_evloop_socket_create(loop->h2o_ctx.loop, | ||
| listener_sd, | ||
| H2O_SOCKET_FLAG_DONT_READ); | ||
| loop->h2o_socket->data = loop; | ||
| h2o_socket_read_start(loop->h2o_socket, accept_connection); | ||
| h2o_multithread_register_receiver(loop->h2o_ctx.queue, | ||
| &loop->h2o_receiver, | ||
| process_messages); | ||
| // libh2o's event loop does not support write polling unless it | ||
| // controls sending the data as well, so do read polling on the | ||
| // epoll file descriptor as a work-around. | ||
| CHECK_ERRNO_RETURN(loop->epoll_fd, epoll_create1, EPOLL_CLOEXEC); | ||
| loop->epoll_socket = h2o_evloop_socket_create(loop->h2o_ctx.loop, | ||
| loop->epoll_fd, | ||
| H2O_SOCKET_FLAG_DONT_READ); | ||
| loop->epoll_socket->data = loop; | ||
| h2o_socket_read_start(loop->epoll_socket, do_epoll_wait); | ||
| if (is_main_thread) { | ||
| global_data->signals = h2o_evloop_socket_create(loop->h2o_ctx.loop, | ||
| global_data->signal_fd, | ||
| H2O_SOCKET_FLAG_DONT_READ); | ||
| global_data->signals->data = loop; | ||
| h2o_socket_read_start(global_data->signals, shutdown_server); | ||
| } | ||
| } | ||
| int start_write_polling(int fd, | ||
| void (**on_write_ready)(void *), | ||
| bool rearm, | ||
| event_loop_t *event_loop) | ||
| { | ||
| struct epoll_event event; | ||
| memset(&event, 0, sizeof(event)); | ||
| event.data.ptr = on_write_ready; | ||
| event.events = EPOLLET | EPOLLONESHOT | EPOLLOUT | EPOLLRDHUP; | ||
| return epoll_ctl(event_loop->epoll_fd, rearm ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, fd, &event); | ||
| } | ||
| void stop_write_polling(int fd, event_loop_t *event_loop) | ||
| { | ||
| epoll_ctl(event_loop->epoll_fd, EPOLL_CTL_DEL, fd, NULL); | ||
| } |
| @@ -0,0 +1,52 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #ifndef EVENT_LOOP_H_ | ||
| #define EVENT_LOOP_H_ | ||
| #include <h2o.h> | ||
| #include <stdbool.h> | ||
| #include "utility.h" | ||
| typedef struct thread_context_t thread_context_t; | ||
| typedef struct { | ||
| h2o_socket_t *epoll_socket; | ||
| h2o_socket_t *h2o_socket; | ||
| size_t conn_num; | ||
| int epoll_fd; | ||
| h2o_accept_ctx_t h2o_accept_ctx; | ||
| h2o_context_t h2o_ctx; | ||
| h2o_multithread_receiver_t h2o_receiver; | ||
| } event_loop_t; | ||
| void event_loop(thread_context_t *ctx); | ||
| void free_event_loop(event_loop_t *event_loop); | ||
| void initialize_event_loop(bool is_main_thread, | ||
| global_data_t *global_data, | ||
| event_loop_t *loop); | ||
| int start_write_polling(int fd, | ||
| void (**on_write_ready)(void *), | ||
| bool rearm, | ||
| event_loop_t *event_loop); | ||
| void stop_write_polling(int fd, event_loop_t *event_loop); | ||
| #endif // EVENT_LOOP_H_ |
| @@ -0,0 +1,38 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #ifndef FORTUNE_H_ | ||
| #define FORTUNE_H_ | ||
| #include <h2o.h> | ||
| #include <postgresql/libpq-fe.h> | ||
| #include "list.h" | ||
| typedef struct { | ||
| list_t l; | ||
| PGresult *data; | ||
| h2o_iovec_t id; | ||
| h2o_iovec_t message; | ||
| } fortune_t; | ||
| int fortunes(struct st_h2o_handler_t *self, h2o_req_t *req); | ||
| #endif // FORTUNE_H_ |
| @@ -0,0 +1,35 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #ifndef LIST_H_ | ||
| #define LIST_H_ | ||
| typedef struct list_t list_t; | ||
| struct list_t { | ||
| list_t *next; | ||
| }; | ||
| typedef struct { | ||
| list_t *head; | ||
| list_t **tail; | ||
| } queue_t; | ||
| #endif // LIST_H_ |
| @@ -0,0 +1,366 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #include <assert.h> | ||
| #include <errno.h> | ||
| #include <h2o.h> | ||
| #include <mustache.h> | ||
| #include <netdb.h> | ||
| #include <signal.h> | ||
| #include <stdarg.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <unistd.h> | ||
| #include <h2o/serverutil.h> | ||
| #include <netinet/tcp.h> | ||
| #include <sys/resource.h> | ||
| #include <sys/signalfd.h> | ||
| #include <sys/socket.h> | ||
| #include <sys/time.h> | ||
| #include <sys/types.h> | ||
| #include "error.h" | ||
| #include "event_loop.h" | ||
| #include "request_handler.h" | ||
| #include "template.h" | ||
| #include "thread.h" | ||
| #include "tls.h" | ||
| #include "utility.h" | ||
| #define DEFAULT_TCP_FASTOPEN_QUEUE_LEN 4096 | ||
| #define USAGE_MESSAGE \ | ||
| "Usage:\n%s [-a <max connections accepted simultaneously>] [-b <bind address>] " \ | ||
| "[-c <certificate file>] [-d <database connection string>] [-f fortunes template file path] " \ | ||
| "[-k <private key file>] [-l <log path>] " \ | ||
| "[-m <max database connections per thread>] [-p <port>] " \ | ||
| "[-q <max enqueued database queries per thread>] [-r <root directory>] " \ | ||
| "[-t <thread number>]\n" | ||
| static void free_global_data(global_data_t *global_data); | ||
| static int get_listener_socket(const config_t *config); | ||
| static size_t get_maximum_cache_line_size(void); | ||
| static int initialize_global_data(const config_t *config, global_data_t *global_data); | ||
| static int parse_options(int argc, char *argv[], config_t *config); | ||
| static void set_default_options(config_t *config); | ||
| static void setup_process(void); | ||
| static void free_global_data(global_data_t *global_data) | ||
| { | ||
| if (global_data->ctx) | ||
| free_thread_contexts(global_data); | ||
| if (global_data->file_logger) | ||
| global_data->file_logger->dispose(global_data->file_logger); | ||
| if (global_data->fortunes_template) { | ||
| mustache_api_t api = {.freedata = NULL}; | ||
| mustache_free(&api, global_data->fortunes_template); | ||
| } | ||
| h2o_config_dispose(&global_data->h2o_config); | ||
| if (global_data->ssl_ctx) | ||
| cleanup_openssl(global_data); | ||
| } | ||
| static int get_listener_socket(const config_t *config) | ||
| { | ||
| int ret = -1; | ||
| char port[16]; | ||
| if ((size_t) snprintf(port, sizeof(port), "%u", (unsigned) config->port) >= sizeof(port)) { | ||
| print_error(__FILE__, __LINE__, "snprintf", "Truncated output."); | ||
| return ret; | ||
| } | ||
| struct addrinfo *res = NULL; | ||
| struct addrinfo hints = {.ai_socktype = SOCK_STREAM, .ai_flags = AI_PASSIVE}; | ||
| const int error_code = getaddrinfo(config->bind_address, port, &hints, &res); | ||
| if (error_code) { | ||
| print_error(__FILE__, __LINE__, "getaddrinfo", gai_strerror(error_code)); | ||
| return ret; | ||
| } | ||
| struct addrinfo *iter = res; | ||
| for (; iter; iter = iter->ai_next) { | ||
| const int s = socket(iter->ai_family, | ||
| iter->ai_socktype | SOCK_NONBLOCK | SOCK_CLOEXEC, | ||
| iter->ai_protocol); | ||
| if (s == -1) { | ||
| LIBRARY_ERROR("socket"); | ||
| continue; | ||
| } | ||
| #define LOCAL_CHECK_ERRNO(function, ...) \ | ||
| do { \ | ||
| const int error_code = (function)(__VA_ARGS__); \ | ||
| \ | ||
| if (error_code) { \ | ||
| print_library_error(__FILE__, __LINE__, #function, errno); \ | ||
| goto error; \ | ||
| } \ | ||
| } while(0) | ||
| int option = 1; | ||
| LOCAL_CHECK_ERRNO(setsockopt, s, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)); | ||
| LOCAL_CHECK_ERRNO(setsockopt, s, IPPROTO_TCP, TCP_QUICKACK, &option, sizeof(option)); | ||
| option = H2O_DEFAULT_HANDSHAKE_TIMEOUT_IN_SECS; | ||
| LOCAL_CHECK_ERRNO(setsockopt, s, IPPROTO_TCP, TCP_DEFER_ACCEPT, &option, sizeof(option)); | ||
| option = DEFAULT_TCP_FASTOPEN_QUEUE_LEN; | ||
| LOCAL_CHECK_ERRNO(setsockopt, s, IPPROTO_TCP, TCP_FASTOPEN, &option, sizeof(option)); | ||
| LOCAL_CHECK_ERRNO(bind, s, iter->ai_addr, iter->ai_addrlen); | ||
| LOCAL_CHECK_ERRNO(listen, s, INT_MAX); | ||
| ret = s; | ||
| break; | ||
| #undef LOCAL_CHECK_ERRNO | ||
| error: | ||
| close(s); | ||
| } | ||
| freeaddrinfo(res); | ||
| return ret; | ||
| } | ||
| static size_t get_maximum_cache_line_size(void) | ||
| { | ||
| const int name[] = {_SC_LEVEL1_DCACHE_LINESIZE, | ||
| _SC_LEVEL2_CACHE_LINESIZE, | ||
| _SC_LEVEL3_CACHE_LINESIZE, | ||
| _SC_LEVEL4_CACHE_LINESIZE}; | ||
| size_t ret = 0; | ||
| for (size_t i = 0; i < ARRAY_SIZE(name); i++) { | ||
| errno = 0; | ||
| const long rc = sysconf(name[i]); | ||
| if (rc < 0) { | ||
| if (errno) | ||
| LIBRARY_ERROR("sysconf"); | ||
| } | ||
| else if ((size_t) rc > ret) | ||
| ret = rc; | ||
| } | ||
| if (!ret) | ||
| ret = DEFAULT_CACHE_LINE_SIZE; | ||
| return ret; | ||
| } | ||
| static int initialize_global_data(const config_t *config, global_data_t *global_data) | ||
| { | ||
| sigset_t signals; | ||
| memset(global_data, 0, sizeof(*global_data)); | ||
| global_data->config = config; | ||
| global_data->memory_alignment = get_maximum_cache_line_size(); | ||
| assert(global_data->memory_alignment <= DEFAULT_CACHE_LINE_SIZE); | ||
| CHECK_ERRNO(sigemptyset, &signals); | ||
| #ifdef NDEBUG | ||
| CHECK_ERRNO(sigaddset, &signals, SIGINT); | ||
| #endif // NDEBUG | ||
| CHECK_ERRNO(sigaddset, &signals, SIGTERM); | ||
| CHECK_ERRNO_RETURN(global_data->signal_fd, signalfd, -1, &signals, SFD_NONBLOCK | SFD_CLOEXEC); | ||
| global_data->fortunes_template = get_fortunes_template(config->template_path); | ||
| h2o_config_init(&global_data->h2o_config); | ||
| global_data->listener_sd = get_listener_socket(config); | ||
| if (global_data->listener_sd == -1) | ||
| goto error; | ||
| if (config->cert && config->key) | ||
| initialize_openssl(global_data); | ||
| const h2o_iovec_t host = h2o_iovec_init(H2O_STRLIT("default")); | ||
| h2o_hostconf_t * const hostconf = h2o_config_register_host(&global_data->h2o_config, | ||
| host, | ||
| config->port); | ||
| h2o_access_log_filehandle_t *log_handle = NULL; | ||
| if (config->log) { | ||
| log_handle = h2o_access_log_open_handle(config->log, NULL); | ||
| if (!log_handle) | ||
| goto error; | ||
| } | ||
| register_request_handlers(hostconf, log_handle); | ||
| // Must be registered after the rest of the request handlers. | ||
| if (config->root) { | ||
| h2o_pathconf_t * const pathconf = h2o_config_register_path(hostconf, "/", 0); | ||
| h2o_file_register(pathconf, config->root, NULL, NULL, 0); | ||
| if (log_handle) | ||
| global_data->file_logger = h2o_access_log_register(pathconf, log_handle); | ||
| } | ||
| global_data->ctx = initialize_thread_contexts(global_data); | ||
| if (global_data->ctx) | ||
| return EXIT_SUCCESS; | ||
| error: | ||
| free_global_data(global_data); | ||
| return EXIT_FAILURE; | ||
| } | ||
| static int parse_options(int argc, char *argv[], config_t *config) | ||
| { | ||
| memset(config, 0, sizeof(*config)); | ||
| opterr = 0; | ||
| while (1) { | ||
| const int opt = getopt(argc, argv, "?a:b:c:d:f:k:l:m:p:q:r:t:"); | ||
| if (opt == -1) | ||
| break; | ||
| switch (opt) { | ||
| #define PARSE_NUMBER(out) \ | ||
| do { \ | ||
| errno = 0; \ | ||
| \ | ||
| const long long n = strtoll(optarg, NULL, 10); \ | ||
| \ | ||
| if (errno) { \ | ||
| print_library_error(__FILE__, __LINE__, "strtoll", errno); \ | ||
| return EXIT_FAILURE; \ | ||
| } \ | ||
| \ | ||
| (out) = n; \ | ||
| } while(0) | ||
| case 'a': | ||
| PARSE_NUMBER(config->max_accept); | ||
| break; | ||
| case 'b': | ||
| config->bind_address = optarg; | ||
| break; | ||
| case 'c': | ||
| config->cert = optarg; | ||
| break; | ||
| case 'd': | ||
| config->db_host = optarg; | ||
| break; | ||
| case 'f': | ||
| config->template_path = optarg; | ||
| break; | ||
| case 'k': | ||
| config->key = optarg; | ||
| break; | ||
| case 'l': | ||
| config->log = optarg; | ||
| break; | ||
| case 'm': | ||
| PARSE_NUMBER(config->max_db_conn_num); | ||
| break; | ||
| case 'p': | ||
| PARSE_NUMBER(config->port); | ||
| break; | ||
| case 'q': | ||
| PARSE_NUMBER(config->max_query_num); | ||
| break; | ||
| case 'r': | ||
| config->root = optarg; | ||
| break; | ||
| case 't': | ||
| PARSE_NUMBER(config->thread_num); | ||
| break; | ||
| default: | ||
| fprintf(stderr, USAGE_MESSAGE, *argv); | ||
| return EXIT_FAILURE; | ||
| #undef PARSE_NUMBER | ||
| } | ||
| } | ||
| set_default_options(config); | ||
| return EXIT_SUCCESS; | ||
| } | ||
| static void set_default_options(config_t *config) | ||
| { | ||
| if (!config->max_accept) | ||
| config->max_accept = 10; | ||
| if (!config->max_db_conn_num) | ||
| config->max_db_conn_num = 10; | ||
| if (!config->max_query_num) | ||
| config->max_query_num = 10000; | ||
| if (!config->port) | ||
| config->port = 8080; | ||
| if (!config->thread_num) | ||
| config->thread_num = h2o_numproc(); | ||
| } | ||
| static void setup_process(void) | ||
| { | ||
| sigset_t signals; | ||
| CHECK_ERRNO(sigfillset, &signals); | ||
| CHECK_ERRNO(sigdelset, &signals, SIGBUS); | ||
| CHECK_ERRNO(sigdelset, &signals, SIGFPE); | ||
| CHECK_ERRNO(sigdelset, &signals, SIGILL); | ||
| CHECK_ERRNO(sigdelset, &signals, SIGSEGV); | ||
| #ifndef NDEBUG | ||
| CHECK_ERRNO(sigdelset, &signals, SIGINT); | ||
| #endif // NDEBUG | ||
| CHECK_ERROR(pthread_sigmask, SIG_BLOCK, &signals, NULL); | ||
| struct rlimit rlim = {.rlim_cur = 0}; | ||
| CHECK_ERRNO(getrlimit, RLIMIT_NOFILE, &rlim); | ||
| rlim.rlim_cur = rlim.rlim_max; | ||
| CHECK_ERRNO(setrlimit, RLIMIT_NOFILE, &rlim); | ||
| } | ||
| int main(int argc, char *argv[]) | ||
| { | ||
| config_t config; | ||
| int rc = EXIT_FAILURE; | ||
| if (parse_options(argc, argv, &config) == EXIT_SUCCESS) { | ||
| global_data_t global_data; | ||
| if (initialize_global_data(&config, &global_data) == EXIT_SUCCESS) { | ||
| setup_process(); | ||
| start_threads(global_data.ctx); | ||
| connect_to_database(global_data.ctx); | ||
| event_loop(global_data.ctx); | ||
| free_global_data(&global_data); | ||
| rc = EXIT_SUCCESS; | ||
| } | ||
| } | ||
| return rc; | ||
| } |
| @@ -0,0 +1,255 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #include <assert.h> | ||
| #include <h2o.h> | ||
| #include <stdalign.h> | ||
| #include <string.h> | ||
| #include <yajl/yajl_gen.h> | ||
| #include "error.h" | ||
| #include "fortune.h" | ||
| #include "request_handler.h" | ||
| #include "utility.h" | ||
| #include "world.h" | ||
| typedef struct { | ||
| yajl_gen gen; | ||
| h2o_generator_t h2o_generator; | ||
| } json_ctx_t; | ||
| static void cleanup_json_response(struct st_h2o_generator_t *self, h2o_req_t *req); | ||
| static void complete_json_response(struct st_h2o_generator_t *self, h2o_req_t *req); | ||
| static int json_serializer(struct st_h2o_handler_t *self, h2o_req_t *req); | ||
| static int plaintext(struct st_h2o_handler_t *self, h2o_req_t *req); | ||
| static const char *status_code_to_string(http_status_code_t status_code); | ||
| static void cleanup_json_response(struct st_h2o_generator_t *self, h2o_req_t *req) | ||
| { | ||
| IGNORE_FUNCTION_PARAMETER(req); | ||
| json_ctx_t * const json_ctx = H2O_STRUCT_FROM_MEMBER(json_ctx_t, h2o_generator, self); | ||
| yajl_gen_free(json_ctx->gen); | ||
| } | ||
| static void complete_json_response(struct st_h2o_generator_t *self, h2o_req_t *req) | ||
| { | ||
| h2o_send(req, NULL, 0, true); | ||
| cleanup_json_response(self, req); | ||
| } | ||
| static int json_serializer(struct st_h2o_handler_t *self, h2o_req_t *req) | ||
| { | ||
| IGNORE_FUNCTION_PARAMETER(self); | ||
| json_ctx_t * const json_ctx = h2o_mem_alloc_pool(&req->pool, sizeof(*json_ctx)); | ||
| if (json_ctx) { | ||
| memset(json_ctx, 0, sizeof(*json_ctx)); | ||
| json_ctx->gen = get_json_generator(&req->pool); | ||
| json_ctx->h2o_generator.proceed = complete_json_response; | ||
| json_ctx->h2o_generator.stop = cleanup_json_response; | ||
| if (json_ctx->gen) { | ||
| CHECK_YAJL_STATUS(yajl_gen_map_open, json_ctx->gen); | ||
| CHECK_YAJL_STATUS(yajl_gen_string, json_ctx->gen, YAJL_STRLIT("message")); | ||
| CHECK_YAJL_STATUS(yajl_gen_string, json_ctx->gen, YAJL_STRLIT("Hello, World!")); | ||
| CHECK_YAJL_STATUS(yajl_gen_map_close, json_ctx->gen); | ||
| if (!send_json_response(json_ctx->gen, &json_ctx->h2o_generator, req)) | ||
| return 0; | ||
| error_yajl: | ||
| yajl_gen_free(json_ctx->gen); | ||
| } | ||
| } | ||
| send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, req); | ||
| return 0; | ||
| } | ||
| static int plaintext(struct st_h2o_handler_t *self, h2o_req_t *req) | ||
| { | ||
| IGNORE_FUNCTION_PARAMETER(self); | ||
| set_default_response_param(PLAIN, req); | ||
| h2o_send_inline(req, H2O_STRLIT("Hello, World!")); | ||
| return 0; | ||
| } | ||
| static const char *status_code_to_string(http_status_code_t status_code) | ||
| { | ||
| const char *ret; | ||
| switch (status_code) { | ||
| case BAD_GATEWAY: | ||
| ret = "Bad Gateway"; | ||
| break; | ||
| case GATEWAY_TIMEOUT: | ||
| ret = "Gateway Timeout"; | ||
| break; | ||
| case INTERNAL_SERVER_ERROR: | ||
| ret = "Internal Server Error"; | ||
| break; | ||
| case OK: | ||
| ret = "OK"; | ||
| break; | ||
| case SERVICE_UNAVAILABLE: | ||
| ret = "Service Unavailable"; | ||
| break; | ||
| default: | ||
| ret = ""; | ||
| } | ||
| return ret; | ||
| } | ||
| const char *get_query_param(const char *query, | ||
| size_t query_len, | ||
| const char *param, | ||
| size_t param_len) | ||
| { | ||
| const char *ret = NULL; | ||
| while (param_len < query_len) { | ||
| if (!memcmp(param, query, param_len)) { | ||
| ret = query + param_len; | ||
| break; | ||
| } | ||
| const char * const next = memchr(query, '&', query_len); | ||
| if (!next) | ||
| break; | ||
| query_len -= next + 1 - query; | ||
| query = next + 1; | ||
| } | ||
| return ret; | ||
| } | ||
| void register_request_handlers(h2o_hostconf_t *hostconf, h2o_access_log_filehandle_t *log_handle) | ||
| { | ||
| h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, "/json", 0); | ||
| h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler)); | ||
| if (log_handle) | ||
| h2o_access_log_register(pathconf, log_handle); | ||
| handler->on_req = json_serializer; | ||
| pathconf = h2o_config_register_path(hostconf, "/db", 0); | ||
| handler = h2o_create_handler(pathconf, sizeof(*handler)); | ||
| handler->on_req = single_query; | ||
| if (log_handle) | ||
| h2o_access_log_register(pathconf, log_handle); | ||
| pathconf = h2o_config_register_path(hostconf, "/queries", 0); | ||
| handler = h2o_create_handler(pathconf, sizeof(*handler)); | ||
| handler->on_req = multiple_queries; | ||
| if (log_handle) | ||
| h2o_access_log_register(pathconf, log_handle); | ||
| pathconf = h2o_config_register_path(hostconf, "/fortunes", 0); | ||
| handler = h2o_create_handler(pathconf, sizeof(*handler)); | ||
| handler->on_req = fortunes; | ||
| if (log_handle) | ||
| h2o_access_log_register(pathconf, log_handle); | ||
| pathconf = h2o_config_register_path(hostconf, "/updates", 0); | ||
| handler = h2o_create_handler(pathconf, sizeof(*handler)); | ||
| handler->on_req = updates; | ||
| if (log_handle) | ||
| h2o_access_log_register(pathconf, log_handle); | ||
| pathconf = h2o_config_register_path(hostconf, "/plaintext", 0); | ||
| handler = h2o_create_handler(pathconf, sizeof(*handler)); | ||
| handler->on_req = plaintext; | ||
| if (log_handle) | ||
| h2o_access_log_register(pathconf, log_handle); | ||
| } | ||
| void send_error(http_status_code_t status_code, const char *body, h2o_req_t *req) | ||
| { | ||
| h2o_send_error_generic(req, status_code, status_code_to_string(status_code), body, 0); | ||
| } | ||
| int send_json_response(yajl_gen gen, h2o_generator_t *h2o_generator, h2o_req_t *req) | ||
| { | ||
| h2o_iovec_t h2o_iovec = {.len = 0}; | ||
| int ret = EXIT_FAILURE; | ||
| static_assert(sizeof(h2o_iovec.base) == sizeof(const unsigned char *) && | ||
| alignof(h2o_iovec.base) == alignof(const unsigned char *), | ||
| "Types must be compatible."); | ||
| if (yajl_gen_get_buf(gen, | ||
| (const unsigned char **) &h2o_iovec.base, | ||
| &h2o_iovec.len) == yajl_gen_status_ok) { | ||
| set_default_response_param(JSON, req); | ||
| h2o_start_response(req, h2o_generator); | ||
| h2o_send(req, &h2o_iovec, 1, false); | ||
| ret = EXIT_SUCCESS; | ||
| } | ||
| return ret; | ||
| } | ||
| void send_service_unavailable_error(const char *body, h2o_req_t *req) | ||
| { | ||
| h2o_add_header(&req->pool, | ||
| &req->res.headers, | ||
| H2O_TOKEN_RETRY_AFTER, | ||
| H2O_STRLIT(MKSTR(H2O_DEFAULT_HTTP1_REQ_TIMEOUT_IN_SECS))); | ||
| h2o_send_error_503(req, | ||
| status_code_to_string(SERVICE_UNAVAILABLE), | ||
| body, | ||
| H2O_SEND_ERROR_KEEP_HEADERS); | ||
| } | ||
| void set_default_response_param(content_type_t content_type, h2o_req_t *req) | ||
| { | ||
| req->res.status = OK; | ||
| req->res.reason = status_code_to_string(req->res.status); | ||
| switch (content_type) { | ||
| case JSON: | ||
| h2o_add_header(&req->pool, | ||
| &req->res.headers, | ||
| H2O_TOKEN_CONTENT_TYPE, | ||
| H2O_STRLIT("application/json")); | ||
| break; | ||
| case PLAIN: | ||
| h2o_add_header(&req->pool, | ||
| &req->res.headers, | ||
| H2O_TOKEN_CONTENT_TYPE, | ||
| H2O_STRLIT("text/plain")); | ||
| break; | ||
| default: | ||
| h2o_add_header(&req->pool, | ||
| &req->res.headers, | ||
| H2O_TOKEN_CONTENT_TYPE, | ||
| H2O_STRLIT("text/html; charset=utf-8")); | ||
| } | ||
| } |
| @@ -0,0 +1,51 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #ifndef REQUEST_H_ | ||
| #define REQUEST_H_ | ||
| #include <h2o.h> | ||
| #include <yajl/yajl_gen.h> | ||
| typedef enum { | ||
| HTML, | ||
| JSON, | ||
| PLAIN | ||
| } content_type_t; | ||
| typedef enum { | ||
| OK = 200, | ||
| INTERNAL_SERVER_ERROR = 500, | ||
| BAD_GATEWAY = 502, | ||
| SERVICE_UNAVAILABLE = 503, | ||
| GATEWAY_TIMEOUT = 504 | ||
| } http_status_code_t; | ||
| const char *get_query_param(const char *query, | ||
| size_t query_len, | ||
| const char *param, | ||
| size_t param_len); | ||
| void register_request_handlers(h2o_hostconf_t *hostconf, h2o_access_log_filehandle_t *log_handle); | ||
| void send_error(http_status_code_t status_code, const char *body, h2o_req_t *req); | ||
| int send_json_response(yajl_gen gen, h2o_generator_t *h2o_generator, h2o_req_t *req); | ||
| void send_service_unavailable_error(const char *body, h2o_req_t *req); | ||
| void set_default_response_param(content_type_t content_type, h2o_req_t *req); | ||
| #endif // REQUEST_H_ |
| @@ -0,0 +1,138 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #include <errno.h> | ||
| #include <stddef.h> | ||
| #include <stdint.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <mustache.h> | ||
| #include "database.h" | ||
| #include "error.h" | ||
| #include "fortune.h" | ||
| #include "template.h" | ||
| #include "utility.h" | ||
| typedef struct { | ||
| FILE *input; | ||
| const char *name; | ||
| } template_input_t; | ||
| static uintmax_t prerender_section(mustache_api_t *api, | ||
| void *userdata, | ||
| mustache_token_section_t *token); | ||
| static uintmax_t prerender_variable(mustache_api_t *api, | ||
| void *userdata, | ||
| mustache_token_variable_t *token); | ||
| static uintmax_t read_template(mustache_api_t *api, | ||
| void *userdata, | ||
| char *buffer, | ||
| uintmax_t buffer_size); | ||
| static void template_error(mustache_api_t *api, void *userdata, uintmax_t lineno, char *error); | ||
| static uintmax_t prerender_section(mustache_api_t *api, | ||
| void *userdata, | ||
| mustache_token_section_t *token) | ||
| { | ||
| bool * const in_section = userdata; | ||
| uintmax_t ret = 0; | ||
| if (!*in_section && !strcmp(token->name, FORTUNE_TABLE_NAME)) { | ||
| *in_section = true; | ||
| ret = mustache_prerender(api, userdata, token->section); | ||
| *in_section = false; | ||
| } | ||
| return ret; | ||
| } | ||
| static uintmax_t prerender_variable(mustache_api_t *api, | ||
| void *userdata, | ||
| mustache_token_variable_t *token) | ||
| { | ||
| IGNORE_FUNCTION_PARAMETER(api); | ||
| bool * const in_section = userdata; | ||
| uintmax_t ret = 0; | ||
| if (*in_section) { | ||
| if (token->text_length == sizeof(ID_FIELD_NAME) - 1 && | ||
| !memcmp(token->text, ID_FIELD_NAME, sizeof(ID_FIELD_NAME) - 1)) { | ||
| token->userdata = (void *) offsetof(fortune_t, id); | ||
| ret = 1; | ||
| } | ||
| else if (token->text_length == sizeof(MESSAGE_FIELD_NAME) - 1 && | ||
| !memcmp(token->text, MESSAGE_FIELD_NAME, sizeof(MESSAGE_FIELD_NAME) - 1)) { | ||
| token->userdata = (void *) offsetof(fortune_t, message); | ||
| ret = 1; | ||
| } | ||
| } | ||
| return ret; | ||
| } | ||
| static uintmax_t read_template(mustache_api_t *api, | ||
| void *userdata, | ||
| char *buffer, | ||
| uintmax_t buffer_size) | ||
| { | ||
| IGNORE_FUNCTION_PARAMETER(api); | ||
| const template_input_t * const template_input = userdata; | ||
| return fread(buffer, sizeof(*buffer), buffer_size, template_input->input); | ||
| } | ||
| static void template_error(mustache_api_t *api, void *userdata, uintmax_t lineno, char *error) | ||
| { | ||
| IGNORE_FUNCTION_PARAMETER(api); | ||
| const template_input_t * const template_input = userdata; | ||
| print_error(template_input->name, lineno, "mustache_compile", error); | ||
| } | ||
| mustache_template_t *get_fortunes_template(const char *path) | ||
| { | ||
| mustache_template_t *ret = NULL; | ||
| template_input_t template_input = {.input = fopen(path, "rb"), .name = path}; | ||
| if (template_input.input) { | ||
| mustache_api_t api = {.error = template_error, | ||
| .read = read_template, | ||
| .sectget = prerender_section, | ||
| .varget = prerender_variable}; | ||
| bool in_section = false; | ||
| ret = mustache_compile(&api, &template_input); | ||
| if (ret && !mustache_prerender(&api, &in_section, ret)) { | ||
| mustache_free(&api, ret); | ||
| ret = NULL; | ||
| } | ||
| fclose(template_input.input); | ||
| } | ||
| else | ||
| LIBRARY_ERROR("fopen"); | ||
| return ret; | ||
| } |
| @@ -0,0 +1,28 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #ifndef TEMPLATE_H_ | ||
| #define TEMPLATE_H_ | ||
| #include <mustache.h> | ||
| mustache_template_t *get_fortunes_template(const char *path); | ||
| #endif // TEMPLATE_H_ |
| @@ -0,0 +1,107 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #define _GNU_SOURCE | ||
| #include <errno.h> | ||
| #include <h2o.h> | ||
| #include <pthread.h> | ||
| #include <stdlib.h> | ||
| #include <h2o/serverutil.h> | ||
| #include <sys/epoll.h> | ||
| #include "database.h" | ||
| #include "error.h" | ||
| #include "event_loop.h" | ||
| #include "thread.h" | ||
| static void *run_thread(void *arg); | ||
| static void *run_thread(void *arg) | ||
| { | ||
| connect_to_database(arg); | ||
| event_loop(arg); | ||
| pthread_exit(NULL); | ||
| } | ||
| void free_thread_contexts(global_data_t *global_data) | ||
| { | ||
| thread_context_t * const ctx = global_data->ctx; | ||
| for (size_t i = 0; i < ctx->global_data->config->thread_num; i++) { | ||
| if (i) | ||
| CHECK_ERROR(pthread_join, ctx[i].thread, NULL); | ||
| else | ||
| // Even though this is global data, we need to close | ||
| // it before the associated event loop is cleaned up. | ||
| h2o_socket_close(global_data->signals); | ||
| free_database_state(ctx[i].event_loop.h2o_ctx.loop, &ctx[i].db_state); | ||
| free_event_loop(&ctx[i].event_loop); | ||
| } | ||
| free(ctx); | ||
| } | ||
| thread_context_t *initialize_thread_contexts(global_data_t *global_data) | ||
| { | ||
| const size_t sz = global_data->config->thread_num * sizeof(thread_context_t); | ||
| thread_context_t * const ret = aligned_alloc(global_data->memory_alignment, sz); | ||
| if (ret) { | ||
| memset(ret, 0, sz); | ||
| for (size_t i = 0; i < global_data->config->thread_num; i++) { | ||
| ret[i].global_data = global_data; | ||
| initialize_event_loop(!i, global_data, &ret[i].event_loop); | ||
| initialize_database_state(ret[i].event_loop.h2o_ctx.loop, &ret[i].db_state); | ||
| } | ||
| } | ||
| return ret; | ||
| } | ||
| void start_threads(thread_context_t *ctx) | ||
| { | ||
| const size_t num_cpus = h2o_numproc(); | ||
| // The first thread context is used by the main thread. | ||
| ctx->thread = pthread_self(); | ||
| for (size_t i = 1; i < ctx->global_data->config->thread_num; i++) | ||
| CHECK_ERROR(pthread_create, &ctx[i].thread, NULL, run_thread, ctx + i); | ||
| // If the number of threads is not equal to the number of processors, then let the scheduler | ||
| // decide how to balance the load. | ||
| if (ctx->global_data->config->thread_num == num_cpus) { | ||
| const size_t cpusetsize = CPU_ALLOC_SIZE(num_cpus); | ||
| cpu_set_t * const cpuset = CPU_ALLOC(num_cpus); | ||
| if (!cpuset) | ||
| abort(); | ||
| for (size_t i = 0; i < ctx->global_data->config->thread_num; i++) { | ||
| CPU_ZERO_S(cpusetsize, cpuset); | ||
| CPU_SET_S(i, cpusetsize, cpuset); | ||
| CHECK_ERROR(pthread_setaffinity_np, ctx[i].thread, cpusetsize, cpuset); | ||
| } | ||
| CPU_FREE(cpuset); | ||
| } | ||
| } |
| @@ -0,0 +1,56 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #ifndef THREAD_H_ | ||
| #define THREAD_H_ | ||
| #include <assert.h> | ||
| #include <h2o.h> | ||
| #include <pthread.h> | ||
| #include <sys/types.h> | ||
| #include "database.h" | ||
| #include "event_loop.h" | ||
| #include "utility.h" | ||
| #define DEFAULT_CACHE_LINE_SIZE 64 | ||
| typedef struct thread_context_t thread_context_t; | ||
| struct thread_context_t { | ||
| global_data_t *global_data; | ||
| unsigned random_seed; | ||
| pid_t tid; | ||
| db_state_t db_state; | ||
| event_loop_t event_loop; | ||
| pthread_t thread; | ||
| // Align on the cache line size to prevent false sharing. | ||
| char padding[49]; | ||
| }; | ||
| static_assert(!(sizeof(thread_context_t) % DEFAULT_CACHE_LINE_SIZE), | ||
| "The size of the thread_context_t structure must be a " | ||
| "multiple of the cache line size."); | ||
| void free_thread_contexts(global_data_t *global_data); | ||
| thread_context_t *initialize_thread_contexts(global_data_t *global_data); | ||
| void start_threads(thread_context_t *ctx); | ||
| #endif // THREAD_H_ |
| @@ -0,0 +1,169 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #include <assert.h> | ||
| #include <pthread.h> | ||
| #include <stddef.h> | ||
| #include <stdlib.h> | ||
| #include <openssl/conf.h> | ||
| #include <openssl/crypto.h> | ||
| #include <openssl/err.h> | ||
| #include <openssl/ssl.h> | ||
| #include "error.h" | ||
| #include "tls.h" | ||
| #include "utility.h" | ||
| #define CHECK_OPENSSL_ERROR(function, ...) \ | ||
| do { \ | ||
| const int error_code = (function)(__VA_ARGS__); \ | ||
| \ | ||
| if (error_code != 1) { \ | ||
| const unsigned long openssl_error = ERR_get_error(); \ | ||
| char buf[128] = ""; \ | ||
| \ | ||
| ERR_error_string_n(openssl_error, buf, sizeof(buf)); \ | ||
| print_error(__FILE__, __LINE__, #function, "%s (%lu)", buf, openssl_error); \ | ||
| abort(); \ | ||
| } \ | ||
| } while(0) | ||
| struct CRYPTO_dynlock_value { | ||
| pthread_mutex_t mutex; | ||
| }; | ||
| static struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line); | ||
| static void dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line); | ||
| static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line); | ||
| static void locking_function(int mode, int n, const char *file, int line); | ||
| static struct { | ||
| pthread_mutex_t *lock; | ||
| size_t num_lock; | ||
| pthread_mutexattr_t lock_attr; | ||
| } openssl_global_data; | ||
| static struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line) | ||
| { | ||
| struct CRYPTO_dynlock_value *ret = malloc(sizeof(*ret)); | ||
| if (ret) { | ||
| const int error_code = pthread_mutex_init(&ret->mutex, &openssl_global_data.lock_attr); | ||
| if (error_code) { | ||
| print_library_error(file, line, "pthread_mutex_init", error_code); | ||
| free(ret); | ||
| ret = NULL; | ||
| } | ||
| } | ||
| return ret; | ||
| } | ||
| static void dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line) | ||
| { | ||
| const int error_code = pthread_mutex_destroy(&l->mutex); | ||
| if (error_code) | ||
| print_library_error(file, line, "pthread_mutex_destroy", error_code); | ||
| free(l); | ||
| } | ||
| static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line) | ||
| { | ||
| const char *function; | ||
| int error_code; | ||
| if (mode & CRYPTO_LOCK) { | ||
| function = "pthread_mutex_lock"; | ||
| error_code = pthread_mutex_lock(&l->mutex); | ||
| } | ||
| else { | ||
| function = "pthread_mutex_unlock"; | ||
| error_code = pthread_mutex_unlock(&l->mutex); | ||
| } | ||
| if (error_code) { | ||
| print_library_error(file, line, function, error_code); | ||
| abort(); | ||
| } | ||
| } | ||
| static void locking_function(int mode, int n, const char *file, int line) | ||
| { | ||
| assert((size_t) n < openssl_global_data.num_lock); | ||
| static_assert(!offsetof(struct CRYPTO_dynlock_value, mutex), | ||
| "The mutex must be the first field in struct CRYPTO_dynlock_value."); | ||
| dyn_lock_function(mode, (struct CRYPTO_dynlock_value *) (openssl_global_data.lock + n), file, line); | ||
| } | ||
| void cleanup_openssl(global_data_t *global_data) | ||
| { | ||
| SSL_CTX_free(global_data->ssl_ctx); | ||
| CRYPTO_set_locking_callback(NULL); | ||
| CRYPTO_set_id_callback(NULL); | ||
| CRYPTO_set_dynlock_create_callback(NULL); | ||
| CRYPTO_set_dynlock_destroy_callback(NULL); | ||
| CRYPTO_set_dynlock_lock_callback(NULL); | ||
| ERR_remove_state(0); | ||
| ERR_free_strings(); | ||
| CONF_modules_unload(1); | ||
| EVP_cleanup(); | ||
| CRYPTO_cleanup_all_ex_data(); | ||
| for (size_t i = 0; i < openssl_global_data.num_lock; i++) | ||
| CHECK_ERROR(pthread_mutex_destroy, openssl_global_data.lock + i); | ||
| free(openssl_global_data.lock); | ||
| CHECK_ERROR(pthread_mutexattr_destroy, &openssl_global_data.lock_attr); | ||
| } | ||
| void initialize_openssl(global_data_t *global_data) | ||
| { | ||
| SSL_library_init(); | ||
| SSL_load_error_strings(); | ||
| openssl_global_data.num_lock = CRYPTO_num_locks(); | ||
| openssl_global_data.lock = malloc(openssl_global_data.num_lock * | ||
| sizeof(*openssl_global_data.lock)); | ||
| CHECK_ERROR(pthread_mutexattr_init, &openssl_global_data.lock_attr); | ||
| CHECK_ERROR(pthread_mutexattr_settype, | ||
| &openssl_global_data.lock_attr, | ||
| PTHREAD_MUTEX_ADAPTIVE_NP); | ||
| for (size_t i = 0; i < openssl_global_data.num_lock; i++) | ||
| CHECK_ERROR(pthread_mutex_init, | ||
| openssl_global_data.lock + i, | ||
| &openssl_global_data.lock_attr); | ||
| CRYPTO_set_locking_callback(locking_function); | ||
| CRYPTO_set_dynlock_create_callback(dyn_create_function); | ||
| CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function); | ||
| CRYPTO_set_dynlock_lock_callback(dyn_lock_function); | ||
| global_data->ssl_ctx = SSL_CTX_new(TLSv1_2_server_method()); | ||
| CHECK_OPENSSL_ERROR(SSL_CTX_use_certificate_file, | ||
| global_data->ssl_ctx, | ||
| global_data->config->cert, | ||
| SSL_FILETYPE_PEM); | ||
| CHECK_OPENSSL_ERROR(SSL_CTX_use_PrivateKey_file, | ||
| global_data->ssl_ctx, | ||
| global_data->config->key, | ||
| SSL_FILETYPE_PEM); | ||
| } |
| @@ -0,0 +1,29 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #ifndef TLS_H_ | ||
| #define TLS_H_ | ||
| #include "utility.h" | ||
| void cleanup_openssl(global_data_t *global_data); | ||
| void initialize_openssl(global_data_t *global_data); | ||
| #endif // TLS_H_ |
| @@ -0,0 +1,95 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #include <assert.h> | ||
| #include <h2o.h> | ||
| #include <stdalign.h> | ||
| #include <stdint.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <yajl/yajl_gen.h> | ||
| #include "utility.h" | ||
| static void mem_pool_free(void *ctx, void *ptr); | ||
| static void *mem_pool_malloc(void *ctx, size_t sz); | ||
| static void *mem_pool_realloc(void *ctx, void *ptr, size_t sz); | ||
| static void mem_pool_free(void *ctx, void *ptr) | ||
| { | ||
| // The memory pool will free all allocations in one go. | ||
| IGNORE_FUNCTION_PARAMETER(ctx); | ||
| IGNORE_FUNCTION_PARAMETER(ptr); | ||
| } | ||
| static void *mem_pool_malloc(void *ctx, size_t sz) | ||
| { | ||
| size_t * const p = h2o_mem_alloc_pool(ctx, sz + sizeof(*p)); | ||
| void * const ret = p + 1; | ||
| *p = sz; | ||
| // check alignment | ||
| assert(!(((uintptr_t) ret) & (alignof(void *) - 1))); | ||
| return ret; | ||
| } | ||
| static void *mem_pool_realloc(void *ctx, void *ptr, size_t sz) | ||
| { | ||
| void *ret; | ||
| if (ptr) { | ||
| const size_t old_sz = ((const size_t *) ptr)[-1]; | ||
| if (sz > old_sz) { | ||
| ret = mem_pool_malloc(ctx, sz); | ||
| memcpy(ret, ptr, old_sz); | ||
| } | ||
| else | ||
| ret = ptr; | ||
| } | ||
| else | ||
| ret = mem_pool_malloc(ctx, sz); | ||
| return ret; | ||
| } | ||
| yajl_gen get_json_generator(h2o_mem_pool_t *pool) | ||
| { | ||
| const yajl_alloc_funcs mem_pool_alloc_funcs = {mem_pool_malloc, | ||
| mem_pool_realloc, | ||
| mem_pool_free, | ||
| pool}; | ||
| return yajl_gen_alloc(&mem_pool_alloc_funcs); | ||
| } | ||
| uint32_t get_random_number(uint32_t max_rand, unsigned int *seed) | ||
| { | ||
| // In general, RAND_MAX + 1 is not a multiple of max_rand, | ||
| // so rand_r() % max_rand would be biased. | ||
| const unsigned bucket_size = (RAND_MAX + 1U) / max_rand; | ||
| const unsigned unbiased_rand_max = bucket_size * max_rand; | ||
| unsigned ret; | ||
| do | ||
| ret = rand_r(seed); | ||
| while (ret >= unbiased_rand_max); | ||
| return ret / bucket_size; | ||
| } |
| @@ -0,0 +1,73 @@ | ||
| /* | ||
| Copyright (c) 2016 Anton Valentinov Kirilov | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | ||
| associated documentation files (the "Software"), to deal in the Software without restriction, | ||
| including without limitation the rights to use, copy, modify, merge, publish, distribute, | ||
| sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all copies or | ||
| substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | ||
| NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
| DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | ||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| */ | ||
| #ifndef UTILITY_H_ | ||
| #define UTILITY_H_ | ||
| #include <h2o.h> | ||
| #include <stdint.h> | ||
| #include <openssl/ssl.h> | ||
| #include <stdbool.h> | ||
| #include <yajl/yajl_gen.h> | ||
| #include <mustache.h> | ||
| #define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) | ||
| // mainly used to silence compiler warnings about unused function parameters | ||
| #define IGNORE_FUNCTION_PARAMETER(p) ((void) (p)) | ||
| #define MIN(x, y) ((x) < (y) ? (x) : (y)) | ||
| #define MKSTR(x) TOSTRING(x) | ||
| #define TOSTRING(x) # x | ||
| #define YAJL_STRLIT(s) (const unsigned char *) (s), sizeof(s) - 1 | ||
| typedef struct thread_context_t thread_context_t; | ||
| typedef struct { | ||
| const char *bind_address; | ||
| const char *cert; | ||
| const char *db_host; | ||
| const char *key; | ||
| const char *log; | ||
| const char *root; | ||
| const char *template_path; | ||
| size_t max_accept; | ||
| size_t max_db_conn_num; | ||
| size_t max_query_num; | ||
| size_t thread_num; | ||
| uint16_t port; | ||
| } config_t; | ||
| typedef struct { | ||
| const config_t *config; | ||
| thread_context_t *ctx; | ||
| h2o_logger_t *file_logger; | ||
| mustache_template_t *fortunes_template; | ||
| h2o_socket_t *signals; | ||
| SSL_CTX *ssl_ctx; | ||
| size_t memory_alignment; | ||
| int listener_sd; | ||
| int signal_fd; | ||
| bool shutdown; | ||
| h2o_globalconf_t h2o_config; | ||
| } global_data_t; | ||
| yajl_gen get_json_generator(h2o_mem_pool_t *pool); | ||
| uint32_t get_random_number(uint32_t max_rand, unsigned int *seed); | ||
| #endif // UTILITY_H_ |